Оно существует
This commit is contained in:
commit
8a13a99713
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
build/
|
||||
unpacked_assets/
|
||||
.gradle/
|
||||
.idea/
|
74
build.gradle.kts
Normal file
74
build.gradle.kts
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "1.6.10"
|
||||
java
|
||||
application
|
||||
}
|
||||
|
||||
group = "ru.dbotthepony"
|
||||
version = "0.1-SNAPSHOT"
|
||||
|
||||
val lwjglVersion = "3.3.0"
|
||||
val lwjglNatives = "natives-windows"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("ru.dbotthepony.kstarbound.MainKt")
|
||||
}
|
||||
|
||||
java.toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
|
||||
tasks.compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
|
||||
implementation("org.apache.logging.log4j:log4j-api:2.17.1")
|
||||
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
|
||||
implementation("com.google.code.gson:gson:2.8.9")
|
||||
|
||||
implementation("it.unimi.dsi:fastutil:8.5.6")
|
||||
|
||||
implementation("com.google.guava:guava:31.0.1-jre")
|
||||
|
||||
implementation(platform("org.lwjgl:lwjgl-bom:$lwjglVersion"))
|
||||
|
||||
implementation("org.lwjgl", "lwjgl")
|
||||
implementation("org.lwjgl", "lwjgl-assimp")
|
||||
implementation("org.lwjgl", "lwjgl-bgfx")
|
||||
implementation("org.lwjgl", "lwjgl-glfw")
|
||||
implementation("org.lwjgl", "lwjgl-nanovg")
|
||||
implementation("org.lwjgl", "lwjgl-nuklear")
|
||||
implementation("org.lwjgl", "lwjgl-openal")
|
||||
implementation("org.lwjgl", "lwjgl-opengl")
|
||||
implementation("org.lwjgl", "lwjgl-opus")
|
||||
implementation("org.lwjgl", "lwjgl-par")
|
||||
implementation("org.lwjgl", "lwjgl-stb")
|
||||
implementation("org.lwjgl", "lwjgl-vulkan")
|
||||
runtimeOnly("org.lwjgl", "lwjgl", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-bgfx", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-glfw", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-nanovg", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-nuklear", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-openal", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-opengl", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-opus", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-par", classifier = lwjglNatives)
|
||||
runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = lwjglNatives)
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
1
gradle.properties
Normal file
1
gradle.properties
Normal file
@ -0,0 +1 @@
|
||||
kotlin.code.style=official
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
185
gradlew
vendored
Normal file
185
gradlew
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
2
settings.gradle.kts
Normal file
2
settings.gradle.kts
Normal file
@ -0,0 +1,2 @@
|
||||
rootProject.name = "KStarBound"
|
||||
|
13
src/main/kotlin/ru/dbotthepony/kstarbound/GameRegistry.kt
Normal file
13
src/main/kotlin/ru/dbotthepony/kstarbound/GameRegistry.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
class GameRegistry<T> {
|
||||
private val table = HashMap<String, T>()
|
||||
val access: Map<String, T> by table
|
||||
|
||||
var frozen = false
|
||||
private set
|
||||
|
||||
fun freeze() {
|
||||
frozen = true
|
||||
}
|
||||
}
|
207
src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
Normal file
207
src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt
Normal file
@ -0,0 +1,207 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.Version
|
||||
import org.lwjgl.glfw.Callbacks.glfwFreeCallbacks
|
||||
import org.lwjgl.glfw.GLFW.*
|
||||
import org.lwjgl.glfw.GLFWErrorCallback
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import org.lwjgl.system.MemoryStack.stackPush
|
||||
import org.lwjgl.system.MemoryUtil.NULL
|
||||
import ru.dbotthepony.kstarbound.gl.*
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kstarbound.render.Camera
|
||||
import ru.dbotthepony.kstarbound.render.ChunkRenderer
|
||||
import ru.dbotthepony.kstarbound.render.TileRenderer
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.ChunkTile
|
||||
import java.io.File
|
||||
import kotlin.math.PI
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private const val TEST = false
|
||||
|
||||
var viewportWidth = 800
|
||||
private set
|
||||
var viewportHeight = 600
|
||||
private set
|
||||
|
||||
var viewportMatrixGUI = updateViewportMatrixA()
|
||||
private set
|
||||
|
||||
var viewportMatrixGame = updateViewportMatrixB()
|
||||
private set
|
||||
|
||||
private fun updateViewportMatrixA(): Matrix4f {
|
||||
return Matrix4f.ortho(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f)
|
||||
}
|
||||
|
||||
private fun updateViewportMatrixB(): Matrix4f {
|
||||
return Matrix4f.orthoDirect(0f, viewportWidth.toFloat(), 0f, viewportHeight.toFloat(), 0.1f, 100f)
|
||||
}
|
||||
|
||||
var window = 0L
|
||||
private set
|
||||
|
||||
fun main() {
|
||||
LOGGER.info("Running LWJGL ${Version.getVersion()}")
|
||||
|
||||
if (!TEST) {
|
||||
try {
|
||||
init()
|
||||
loop()
|
||||
} finally {
|
||||
if (window != NULL) {
|
||||
glfwFreeCallbacks(window)
|
||||
glfwDestroyWindow(window)
|
||||
}
|
||||
|
||||
glfwTerminate()
|
||||
glfwSetErrorCallback(null)?.free()
|
||||
}
|
||||
} else {
|
||||
Starbound.addFilePath(File("./unpacked_assets/"))
|
||||
Starbound.loadTileDefinition("alienrock")
|
||||
}
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
GLFWErrorCallback.create { error, description ->
|
||||
LOGGER.error("LWJGL error {}: {}", error, description)
|
||||
}.set()
|
||||
|
||||
check(glfwInit()) { "Unable to initialize GLFW" }
|
||||
|
||||
glfwDefaultWindowHints()
|
||||
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4)
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6)
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
|
||||
|
||||
window = glfwCreateWindow(viewportWidth, viewportHeight, "LWJGL Window!", NULL, NULL)
|
||||
require(window != NULL) { "Unable to create GLFW window" }
|
||||
|
||||
glfwSetKeyCallback(window) { window, key, scancode, action, mods ->
|
||||
if (key == GLFW_KEY_ESCAPE || key == GLFW_RELEASE) {
|
||||
glfwSetWindowShouldClose(window, true)
|
||||
}
|
||||
}
|
||||
|
||||
glfwSetFramebufferSizeCallback(window) { _, w, h ->
|
||||
viewportWidth = w
|
||||
viewportHeight = h
|
||||
viewportMatrixGUI = updateViewportMatrixA()
|
||||
viewportMatrixGame = updateViewportMatrixB()
|
||||
glViewport(0, 0, w, h)
|
||||
}
|
||||
|
||||
val stack = stackPush()
|
||||
|
||||
try {
|
||||
val pWidth = stack.mallocInt(1)
|
||||
val pHeight = stack.mallocInt(1)
|
||||
|
||||
glfwGetWindowSize(window, pWidth, pHeight)
|
||||
|
||||
val vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor())!!
|
||||
|
||||
glfwSetWindowPos(
|
||||
window,
|
||||
(vidmode.width() - pWidth[0]) / 2,
|
||||
(vidmode.height() - pHeight[0]) / 2
|
||||
)
|
||||
} finally {
|
||||
stack.close()
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window)
|
||||
|
||||
// vsync
|
||||
glfwSwapInterval(1)
|
||||
|
||||
glfwShowWindow(window)
|
||||
|
||||
Starbound.addFilePath(File("./unpacked_assets/"))
|
||||
Starbound.loadTileDefinition("alienrock")
|
||||
}
|
||||
|
||||
private fun loop() {
|
||||
val state = GLStateTracker()
|
||||
val camera = Camera()
|
||||
|
||||
// Set the clear color
|
||||
glClearColor(0.75f, 0.75f, 0.75f, 0.75f)
|
||||
|
||||
state.blend = true
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
val rock = ChunkTile(Starbound.loadTileDefinition("alienrock"))
|
||||
val chunk = Starbound.world.setTile(Vector2i(2, 2), rock)
|
||||
|
||||
chunk[3, 2] = rock
|
||||
chunk[4, 2] = rock
|
||||
chunk[4, 3] = rock
|
||||
chunk[4, 4] = rock
|
||||
chunk[3, 4] = rock
|
||||
chunk[5, 4] = rock
|
||||
|
||||
|
||||
for (x in 0 until 1) {
|
||||
for (y in 0 until 4) {
|
||||
//chunk[x, y] = ChunkTile(Starbound.loadTileDefinition("alienrock"))
|
||||
}
|
||||
}
|
||||
|
||||
val chunkRenderer = ChunkRenderer(state, chunk)
|
||||
chunkRenderer.tesselateStatic()
|
||||
chunkRenderer.uploadStatic()
|
||||
|
||||
// Run the rendering loop until the user has attempted to close
|
||||
// the window or has pressed the ESCAPE key.
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) // clear the framebuffer
|
||||
|
||||
state.matrixStack.clear(viewportMatrixGame.toMutableMatrix())
|
||||
|
||||
// program.use()
|
||||
|
||||
// val time = glfwGetTime()
|
||||
// program["globalColor"] = Uniform3f(sin(time).toFloat(), cos(time).toFloat(), sin(time).toFloat())
|
||||
// program["ourTexture"] = 0
|
||||
|
||||
// texture.bind()
|
||||
// vao.bind()
|
||||
// ebo.bind()
|
||||
// glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
|
||||
// checkForGLError()
|
||||
|
||||
//tileRenderer.renderPiece()
|
||||
|
||||
//state.shaderVertexTexture.use()
|
||||
//state.shaderVertexTexture["_texture"] = 0
|
||||
////state.shaderVertexTexture["_transform"] = Vector3f.FORWARD.rotateAroundThis(PI * glfwGetTime())
|
||||
////state.shaderVertexTexture["_transform"] = state.matrixStack.push().scale((glfwGetTime() % 1000.0).toFloat(), (glfwGetTime() % 1000.0).toFloat(), (glfwGetTime() % 1000.0).toFloat()).last
|
||||
////state.shaderVertexTexture["_transform"] = Matrix4f.perspective(((glfwGetTime() * 16.0) % 180.0).toFloat(), 0.1f, 100f)
|
||||
//state.shaderVertexTexture["_transform"] = state.matrixStack.push().scale(x = 10f, y = 10f).translateWithScale(10f, 10f).last
|
||||
//texture.bind()
|
||||
//texture.textureMinFilter = GL_NEAREST
|
||||
//texture.textureMagFilter = GL_NEAREST
|
||||
//chunkRenderer.bind()
|
||||
//glDrawElements(GL_TRIANGLES, chunkRenderer.indexCount, GL_UNSIGNED_INT, 0)
|
||||
//checkForGLError()
|
||||
|
||||
state.matrixStack.push().scale(x = 100f, y = 100f).translateWithScale(0f, 0f)
|
||||
chunkRenderer.render()
|
||||
//state.matrixStack.translateWithScale(18f)
|
||||
//chunkRenderer.render()
|
||||
|
||||
glfwSwapBuffers(window) // swap the color buffers
|
||||
|
||||
// Poll for window events. The key callback above will only be
|
||||
// invoked during this call.
|
||||
glfwPollEvents()
|
||||
}
|
||||
}
|
55
src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
Normal file
55
src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt
Normal file
@ -0,0 +1,55 @@
|
||||
package ru.dbotthepony.kstarbound
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinitionBuilder
|
||||
import ru.dbotthepony.kstarbound.defs.TileRenderTemplate
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
object Starbound {
|
||||
val tiles = HashMap<String, TileDefinition>()
|
||||
val world = World()
|
||||
|
||||
private val _filepath = ArrayList<File>()
|
||||
val filepath = object : List<File> by _filepath {}
|
||||
|
||||
fun addFilePath(path: File) {
|
||||
_filepath.add(path)
|
||||
}
|
||||
|
||||
fun findFile(path: File): File {
|
||||
if (path.exists()) {
|
||||
return path.canonicalFile
|
||||
}
|
||||
|
||||
for (sPath in _filepath) {
|
||||
val newPath = File(sPath.path, path.path)
|
||||
|
||||
if (newPath.exists()) {
|
||||
return newPath
|
||||
}
|
||||
}
|
||||
|
||||
throw FileNotFoundException("Unable to find $path in any of known file paths")
|
||||
}
|
||||
|
||||
fun findFile(path: String) = findFile(File(path))
|
||||
|
||||
fun loadJson(path: String): JsonElement {
|
||||
if (path[0] == '/')
|
||||
return JsonParser.parseReader(findFile(path.substring(1)).bufferedReader())
|
||||
|
||||
return JsonParser.parseReader(findFile(path).bufferedReader())
|
||||
}
|
||||
|
||||
fun loadTileDefinition(name: String): TileDefinition {
|
||||
return tiles.computeIfAbsent(name) {
|
||||
val foundPath = findFile("tiles/materials/$name.material")
|
||||
return@computeIfAbsent TileDefinitionBuilder.fromJson(JsonParser.parseReader(foundPath.bufferedReader()) as JsonObject).build(foundPath.parent)
|
||||
}
|
||||
}
|
||||
}
|
14
src/main/kotlin/ru/dbotthepony/kstarbound/api/Structs.kt
Normal file
14
src/main/kotlin/ru/dbotthepony/kstarbound/api/Structs.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package ru.dbotthepony.kstarbound.api
|
||||
|
||||
interface IStruct2f {
|
||||
operator fun component1(): Float
|
||||
operator fun component2(): Float
|
||||
}
|
||||
|
||||
interface IStruct3f : IStruct2f {
|
||||
operator fun component3(): Float
|
||||
}
|
||||
|
||||
interface IStruct4F : IStruct3f {
|
||||
operator fun component4(): Float
|
||||
}
|
477
src/main/kotlin/ru/dbotthepony/kstarbound/defs/TileDefinition.kt
Normal file
477
src/main/kotlin/ru/dbotthepony/kstarbound/defs/TileDefinition.kt
Normal file
@ -0,0 +1,477 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||
import ru.dbotthepony.kstarbound.util.Color
|
||||
import ru.dbotthepony.kstarbound.world.IChunk
|
||||
import java.io.File
|
||||
|
||||
data class TileDefinition(
|
||||
val materialId: Int,
|
||||
val materialName: String,
|
||||
val particleColor: Color,
|
||||
val itemDrop: String?,
|
||||
val description: String,
|
||||
val shortdescription: String,
|
||||
|
||||
val racialDescription: ImmutableMap<String, String>,
|
||||
val footstepSound: String?,
|
||||
val health: Int,
|
||||
val category: String,
|
||||
|
||||
val render: TileRenderDefinition
|
||||
) {
|
||||
init {
|
||||
require(materialId >= 0) { "Material ID must be positive ($materialId given) ($materialName)" }
|
||||
require(materialId != 0) { "Material ID 0 is reserved ($materialName)" }
|
||||
}
|
||||
}
|
||||
|
||||
class TileDefinitionBuilder {
|
||||
var materialId = 0
|
||||
var materialName = "unknown_tile"
|
||||
var particleColor = Color.WHITE
|
||||
var itemDrop: String? = "unknown"
|
||||
var description = "..."
|
||||
var shortdescription = "..."
|
||||
|
||||
val racialDescription = ArrayList<Pair<String, String>>()
|
||||
|
||||
var footstepSound: String? = null
|
||||
var health = 0
|
||||
var category = "generic"
|
||||
|
||||
val render = TileRenderDefinitionBuilder()
|
||||
|
||||
fun build(directory: String? = null): TileDefinition {
|
||||
return TileDefinition(
|
||||
racialDescription = ImmutableMap.builder<String, String>().also {
|
||||
for ((k, v) in this.racialDescription) {
|
||||
it.put(k, v)
|
||||
}
|
||||
}.build(),
|
||||
|
||||
materialId = materialId,
|
||||
materialName = materialName,
|
||||
particleColor = particleColor,
|
||||
itemDrop = itemDrop,
|
||||
description = description,
|
||||
shortdescription = shortdescription,
|
||||
|
||||
footstepSound = footstepSound,
|
||||
health = health,
|
||||
category = category,
|
||||
|
||||
render = render.build(directory)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJson(input: JsonObject): TileDefinitionBuilder {
|
||||
val builder = TileDefinitionBuilder()
|
||||
|
||||
try {
|
||||
builder.materialName = input["materialName"].asString
|
||||
builder.materialId = input["materialId"].asInt
|
||||
|
||||
require(builder.materialId >= 0) { "Invalid materialId ${builder.materialId}" }
|
||||
|
||||
builder.particleColor = Color(input["particleColor"].asJsonArray)
|
||||
builder.itemDrop = input["itemDrop"].asString
|
||||
builder.description = input["description"].asString
|
||||
builder.shortdescription = input["shortdescription"].asString
|
||||
builder.footstepSound = input["footstepSound"]?.asString
|
||||
builder.health = input["health"].asInt
|
||||
builder.category = input["category"].asString
|
||||
|
||||
for (key in input.keySet()) {
|
||||
if (key.endsWith("Description") && key.length != "Description".length) {
|
||||
builder.racialDescription.add(key.substring(0, key.length - "Description".length) to input[key].asString)
|
||||
}
|
||||
}
|
||||
|
||||
input["renderParameters"]?.asJsonObject?.let {
|
||||
builder.render.texture = it["texture"].asString
|
||||
builder.render.variants = it["variants"].asInt
|
||||
builder.render.lightTransparent = it["lightTransparent"].asBoolean
|
||||
builder.render.occludesBelow = it["occludesBelow"].asBoolean
|
||||
builder.render.multiColored = it["multiColored"].asBoolean
|
||||
builder.render.zLevel = it["zLevel"].asInt
|
||||
}
|
||||
|
||||
builder.render.renderTemplate = input["renderTemplate"]?.asString?.let renderTemplate@{
|
||||
return@renderTemplate TileRenderTemplate.load(it)
|
||||
}
|
||||
} catch(err: Throwable) {
|
||||
throw IllegalArgumentException("Failed reading tile definition ${builder.materialName}", err)
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Кусочек рендера тайла
|
||||
*
|
||||
* root.pieces[]
|
||||
*/
|
||||
data class TileRenderPiece(
|
||||
val name: String,
|
||||
val texture: File?,
|
||||
val textureSize: Vector2i,
|
||||
val texturePosition: Vector2i,
|
||||
|
||||
val colorStride: Vector2i?,
|
||||
val variantStride: Vector2i?,
|
||||
) {
|
||||
companion object {
|
||||
fun fromJson(name: String, input: JsonObject): TileRenderPiece {
|
||||
val texture = input["texture"]?.asString?.let {
|
||||
if (it[0] != '/') {
|
||||
throw UnsupportedOperationException("Render piece has not absolute texture path: $it")
|
||||
}
|
||||
|
||||
return@let File(it.substring(1))
|
||||
}
|
||||
|
||||
val textureSize = Vector2i.fromJson(input["textureSize"].asJsonArray)
|
||||
val texturePosition = Vector2i.fromJson(input["texturePosition"].asJsonArray)
|
||||
|
||||
val colorStride = input["colorStride"]?.asJsonArray?.let { Vector2i.fromJson(it) }
|
||||
val variantStride = input["variantStride"]?.asJsonArray?.let { Vector2i.fromJson(it) }
|
||||
|
||||
return TileRenderPiece(name, texture, textureSize, texturePosition, colorStride, variantStride)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Кусочек правила рендера тайла
|
||||
*
|
||||
* root.rules.`name`.entries[]
|
||||
*/
|
||||
sealed class RenderRule(params: Map<String, Any>) {
|
||||
val matchHue = params["matchHue"] as? Boolean ?: false
|
||||
val inverse = params["inverse"] as? Boolean ?: false
|
||||
|
||||
abstract fun test(getter: IChunk, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean
|
||||
|
||||
companion object {
|
||||
fun factory(name: String, params: Map<String, Any>): RenderRule {
|
||||
return when (name) {
|
||||
"EqualsSelf" -> RenderRuleEqualsSelf(params)
|
||||
"Shadows" -> RenderRuleShadows(params)
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown tile render rule $name")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJson(input: JsonObject): RenderRule {
|
||||
val params = ImmutableMap.builder<String, Any>()
|
||||
|
||||
for (key in input.keySet()) {
|
||||
if (key != "type") {
|
||||
val value = input[key] as? JsonPrimitive
|
||||
|
||||
if (value != null) {
|
||||
if (value.isBoolean) {
|
||||
params.put(key, value.asBoolean)
|
||||
} else if (value.isNumber) {
|
||||
params.put(key, value.asDouble)
|
||||
} else {
|
||||
params.put(key, value.asString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return factory(input["type"].asString, params.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RenderRuleEqualsSelf(params: Map<String, Any>) : RenderRule(params) {
|
||||
override fun test(getter: IChunk, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
||||
val otherTile = getter[thisPos + offsetPos] ?: return inverse
|
||||
|
||||
if (inverse)
|
||||
return otherTile.def != thisRef
|
||||
|
||||
return otherTile.def == thisRef
|
||||
}
|
||||
}
|
||||
|
||||
class RenderRuleShadows(params: Map<String, Any>) : RenderRule(params) {
|
||||
override fun test(getter: IChunk, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
||||
return false // TODO
|
||||
}
|
||||
}
|
||||
|
||||
enum class RenderRuleCombination {
|
||||
ALL,
|
||||
ANY
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Правило рендера тайла
|
||||
*
|
||||
* root.rules[]
|
||||
*/
|
||||
data class TileRenderRule(
|
||||
val name: String,
|
||||
val join: RenderRuleCombination,
|
||||
val pieces: List<RenderRule>
|
||||
) {
|
||||
fun test(getter: IChunk, thisRef: TileDefinition, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
||||
if (join == RenderRuleCombination.ANY) {
|
||||
for (piece in pieces) {
|
||||
if (piece.test(getter, thisRef, thisPos, offsetPos)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
for (piece in pieces) {
|
||||
if (!piece.test(getter, thisRef, thisPos, offsetPos)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJson(name: String, input: JsonObject): TileRenderRule {
|
||||
val join = input["join"]?.asString?.let {
|
||||
when (it) {
|
||||
"any" -> RenderRuleCombination.ANY
|
||||
else -> RenderRuleCombination.ALL
|
||||
}
|
||||
} ?: RenderRuleCombination.ALL
|
||||
|
||||
val jEntries = input["entries"] as JsonArray
|
||||
val pieces = ArrayList<RenderRule>(jEntries.size())
|
||||
|
||||
for (elem in jEntries) {
|
||||
pieces.add(RenderRule.fromJson(elem.asJsonObject))
|
||||
}
|
||||
|
||||
return TileRenderRule(name, join, ImmutableList.copyOf(pieces))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TileRenderMatchedPiece(
|
||||
val piece: TileRenderPiece,
|
||||
val offset: Vector2i
|
||||
) {
|
||||
companion object {
|
||||
fun fromJson(input: JsonArray, tilePieces: Map<String, TileRenderPiece>): TileRenderMatchedPiece {
|
||||
val piece = input[0].asString.let {
|
||||
return@let tilePieces[it] ?: throw IllegalArgumentException("Unable to find render piece $it")
|
||||
}
|
||||
|
||||
val offset = Vector2i.fromJson(input[1].asJsonArray)
|
||||
return TileRenderMatchedPiece(piece, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TileRenderMatchPositioned(
|
||||
val condition: TileRenderRule,
|
||||
val offset: Vector2i
|
||||
) {
|
||||
/**
|
||||
* Состояние [condition] на [thisPos] с [offset]
|
||||
*/
|
||||
fun test(getter: IChunk, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
|
||||
return condition.test(getter, thisRef, thisPos, offset)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJson(input: JsonArray, rulePieces: Map<String, TileRenderRule>): TileRenderMatchPositioned {
|
||||
val offset = Vector2i.fromJson(input[0].asJsonArray)
|
||||
val condition = rulePieces[input[1].asString] ?: throw IllegalArgumentException("Rule ${input[1].asString} is missing!")
|
||||
|
||||
return TileRenderMatchPositioned(condition, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TileRenderMatchPiece(
|
||||
val pieces: List<TileRenderMatchedPiece>,
|
||||
val matchAllPoints: List<TileRenderMatchPositioned>,
|
||||
val subMatches: List<TileRenderMatchPiece>
|
||||
) {
|
||||
/**
|
||||
* Возвращает, сработали ли ВСЕ [matchAllPoints]
|
||||
*
|
||||
* Если хотя бы один из них вернул false, весь тест возвращает false
|
||||
*
|
||||
* [subMatches] стоит итерировать только если это вернуло true
|
||||
*/
|
||||
fun test(getter: IChunk, thisRef: TileDefinition, thisPos: Vector2i): Boolean {
|
||||
for (matcher in matchAllPoints) {
|
||||
if (!matcher.test(getter, thisRef, thisPos)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJson(input: JsonObject, tilePieces: Map<String, TileRenderPiece>, rulePieces: Map<String, TileRenderRule>): TileRenderMatchPiece {
|
||||
val pieces = input["pieces"]?.asJsonArray?.let {
|
||||
val list = ArrayList<TileRenderMatchedPiece>()
|
||||
|
||||
for (thisPiece in it) {
|
||||
list.add(TileRenderMatchedPiece.fromJson(thisPiece.asJsonArray, tilePieces))
|
||||
}
|
||||
|
||||
return@let ImmutableList.copyOf(list)
|
||||
} ?: listOf()
|
||||
|
||||
val matchAllPoints = input["matchAllPoints"]?.asJsonArray?.let {
|
||||
val list = ArrayList<TileRenderMatchPositioned>()
|
||||
|
||||
for (thisPiece in it) {
|
||||
list.add(TileRenderMatchPositioned.fromJson(thisPiece.asJsonArray, rulePieces))
|
||||
}
|
||||
|
||||
return@let ImmutableList.copyOf(list)
|
||||
} ?: listOf()
|
||||
|
||||
val subMatches = input["subMatches"]?.asJsonArray?.let {
|
||||
val list = ArrayList<TileRenderMatchPiece>()
|
||||
|
||||
for (thisPiece in it) {
|
||||
list.add(fromJson(thisPiece.asJsonObject, tilePieces, rulePieces))
|
||||
}
|
||||
|
||||
return@let ImmutableList.copyOf(list)
|
||||
} ?: listOf()
|
||||
|
||||
return TileRenderMatchPiece(pieces, matchAllPoints, subMatches)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TileRenderMatch(
|
||||
val name: String,
|
||||
val pieces: List<TileRenderMatchPiece>,
|
||||
) {
|
||||
companion object {
|
||||
fun fromJson(input: JsonArray, tilePieces: Map<String, TileRenderPiece>, rulePieces: Map<String, TileRenderRule>): TileRenderMatch {
|
||||
val name = input[0].asString
|
||||
val pieces = ArrayList<TileRenderMatchPiece>()
|
||||
|
||||
for (elem in input[1].asJsonArray) {
|
||||
pieces.add(TileRenderMatchPiece.fromJson(elem.asJsonObject, tilePieces, rulePieces))
|
||||
}
|
||||
|
||||
return TileRenderMatch(name, ImmutableList.copyOf(pieces))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TileRenderTemplate(
|
||||
val representativePiece: String,
|
||||
val pieces: Map<String, TileRenderPiece>,
|
||||
val rules: Map<String, TileRenderRule>,
|
||||
val matches: Map<String, TileRenderMatch>,
|
||||
) {
|
||||
companion object {
|
||||
val map = HashMap<String, TileRenderTemplate>()
|
||||
|
||||
fun load(path: String): TileRenderTemplate {
|
||||
return map.computeIfAbsent(path) {
|
||||
val json = Starbound.loadJson(path).asJsonObject
|
||||
return@computeIfAbsent fromJson(json)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromJson(input: JsonObject): TileRenderTemplate {
|
||||
val representativePiece = input["representativePiece"].asString
|
||||
|
||||
val pieces = HashMap<String, TileRenderPiece>()
|
||||
val rules = HashMap<String, TileRenderRule>()
|
||||
val matches = HashMap<String, TileRenderMatch>()
|
||||
|
||||
val jPieces = input["pieces"] as JsonObject
|
||||
|
||||
for (key in jPieces.keySet()) {
|
||||
pieces[key] = TileRenderPiece.fromJson(key, jPieces[key] as JsonObject)
|
||||
}
|
||||
|
||||
val jRules = input["rules"] as JsonObject
|
||||
|
||||
for (key in jRules.keySet()) {
|
||||
rules[key] = TileRenderRule.fromJson(key, jRules[key] as JsonObject)
|
||||
}
|
||||
|
||||
val jMatches = input["matches"] as JsonArray
|
||||
|
||||
for (instance in jMatches) {
|
||||
val deserialized = TileRenderMatch.fromJson(instance.asJsonArray, pieces, rules)
|
||||
matches[deserialized.name] = deserialized
|
||||
}
|
||||
|
||||
return TileRenderTemplate(representativePiece, ImmutableMap.copyOf(pieces), ImmutableMap.copyOf(rules), ImmutableMap.copyOf(matches))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TileRenderDefinition(
|
||||
val texture: File,
|
||||
val variants: Int,
|
||||
val lightTransparent: Boolean,
|
||||
val occludesBelow: Boolean,
|
||||
val multiColored: Boolean,
|
||||
val zLevel: Int,
|
||||
val renderTemplate: TileRenderTemplate?
|
||||
)
|
||||
|
||||
class TileRenderDefinitionBuilder {
|
||||
var texture = ""
|
||||
var variants = 1
|
||||
var lightTransparent = false
|
||||
var occludesBelow = false
|
||||
var multiColored = false
|
||||
var zLevel = 0
|
||||
var renderTemplate: TileRenderTemplate? = null
|
||||
|
||||
fun build(directory: String? = null): TileRenderDefinition {
|
||||
val newtexture: File
|
||||
|
||||
if (texture[0] == '/') {
|
||||
// путь абсолютен
|
||||
newtexture = File(texture)
|
||||
} else {
|
||||
if (directory != null) {
|
||||
newtexture = File(directory, texture)
|
||||
} else {
|
||||
newtexture = File(texture)
|
||||
}
|
||||
}
|
||||
|
||||
return TileRenderDefinition(
|
||||
texture = newtexture,
|
||||
variants = variants,
|
||||
lightTransparent = lightTransparent,
|
||||
occludesBelow = occludesBelow,
|
||||
multiColored = multiColored,
|
||||
zLevel = zLevel,
|
||||
renderTemplate = renderTemplate,
|
||||
)
|
||||
}
|
||||
}
|
34
src/main/kotlin/ru/dbotthepony/kstarbound/gl/ErrorCheck.kt
Normal file
34
src/main/kotlin/ru/dbotthepony/kstarbound/gl/ErrorCheck.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
|
||||
// GL_INVALID_ENUM
|
||||
// GL_INVALID_VALUE
|
||||
// GL_INVALID_OPERATION
|
||||
// GL_STACK_OVERFLOW
|
||||
// GL_STACK_UNDERFLOW
|
||||
// GL_OUT_OF_MEMORY
|
||||
|
||||
sealed class OpenGLError(message: String, val statusCode: Int) : Throwable(message)
|
||||
|
||||
class OpenGLUnknownError(statusCode: Int, message: String = "Unknown OpenGL error occured: $statusCode") : OpenGLError(message, statusCode)
|
||||
|
||||
class OpenGLInvalidEnumException(message: String = "Invalid enum provided") : OpenGLError(message, GL_INVALID_ENUM)
|
||||
class OpenGLInvalidValueException(message: String = "Invalid value provided") : OpenGLError(message, GL_INVALID_VALUE)
|
||||
class OpenGLInvalidOperationException(message: String = "Invalid operation in this context or invalid arguments provided") : OpenGLError(message, GL_INVALID_OPERATION)
|
||||
class OpenGLStackOverflowException(message: String = "Stack overflow in OpenGL") : OpenGLError(message, GL_STACK_OVERFLOW)
|
||||
class OpenGLStackUnderflowException(message: String = "Stack underflow in OpenGL") : OpenGLError(message, GL_STACK_UNDERFLOW)
|
||||
class OpenGLOutOfMemoryException(message: String = "Out of Memory in OpenGL") : OpenGLError(message, GL_OUT_OF_MEMORY)
|
||||
|
||||
fun checkForGLError() {
|
||||
when (val errorCode = glGetError()) {
|
||||
GL_NO_ERROR -> {}
|
||||
GL_INVALID_ENUM -> throw OpenGLInvalidEnumException()
|
||||
GL_INVALID_VALUE -> throw OpenGLInvalidValueException()
|
||||
GL_INVALID_OPERATION -> throw OpenGLInvalidOperationException()
|
||||
GL_STACK_OVERFLOW -> throw OpenGLStackOverflowException()
|
||||
GL_STACK_UNDERFLOW -> throw OpenGLStackUnderflowException()
|
||||
GL_OUT_OF_MEMORY -> throw OpenGLOutOfMemoryException()
|
||||
else -> throw OpenGLUnknownError(errorCode)
|
||||
}
|
||||
}
|
138
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLAttributeList.kt
Normal file
138
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLAttributeList.kt
Normal file
@ -0,0 +1,138 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
|
||||
enum class GLType(val identity: Int, val typeIndentity: Int, val byteSize: Int, val logicalSize: Int) {
|
||||
INT(GL_INT, GL_INT, 4, 1),
|
||||
UINT(GL_UNSIGNED_INT, GL_UNSIGNED_INT, 4, 1),
|
||||
FLOAT(GL_FLOAT, GL_FLOAT, 4, 1),
|
||||
DOUBLE(GL_DOUBLE, GL_DOUBLE, 8, 1),
|
||||
|
||||
VEC2F(GL_FLOAT_VEC2, GL_FLOAT, 8, 2),
|
||||
VEC3F(GL_FLOAT_VEC3, GL_FLOAT, 12, 3),
|
||||
VEC4F(GL_FLOAT_VEC4, GL_FLOAT, 16, 4),
|
||||
|
||||
VEC2I(GL_INT_VEC2, GL_INT, 8, 2),
|
||||
VEC3I(GL_INT_VEC3, GL_INT, 12, 3),
|
||||
VEC4I(GL_INT_VEC4, GL_INT, 16, 4),
|
||||
|
||||
MAT2F(GL_FLOAT_MAT2, GL_FLOAT, 2 * 2 * 4, 2 * 2),
|
||||
MAT3F(GL_FLOAT_MAT3, GL_FLOAT, 3 * 3 * 4, 3 * 3),
|
||||
MAT4F(GL_FLOAT_MAT4, GL_FLOAT, 4 * 4 * 4, 4 * 4),
|
||||
}
|
||||
|
||||
interface IGLAttributeList {
|
||||
fun apply(target: GLVertexArrayObject, enable: Boolean = false)
|
||||
}
|
||||
|
||||
data class AttributeListPosition(val name: String, val index: Int, val glType: GLType, val stride: Int, val offset: Long)
|
||||
|
||||
/**
|
||||
* Хранит список аттрибутов для применения к Vertex Array Object
|
||||
*
|
||||
* Аттрибуты плотно упакованы и идут один за другим
|
||||
*
|
||||
* Создаётся через [GLFlatAttributeListBuilder]
|
||||
*/
|
||||
class GLFlatAttributeList(builder: GLFlatAttributeListBuilder) : IGLAttributeList {
|
||||
val attributes: List<AttributeListPosition>
|
||||
val size get() = attributes.size
|
||||
val stride: Int
|
||||
|
||||
operator fun get(index: Int) = attributes[index]
|
||||
|
||||
fun vertexBuilder(vertexType: VertexType) = VertexBuilder(this, vertexType)
|
||||
|
||||
init {
|
||||
val buildList = ArrayList<AttributeListPosition>()
|
||||
|
||||
var offset = 0L
|
||||
var stride = 0
|
||||
|
||||
for (i in builder.attributes) {
|
||||
stride += i.second.byteSize
|
||||
}
|
||||
|
||||
this.stride = stride
|
||||
|
||||
// val counter = mutableMapOf<Int, Int>()
|
||||
|
||||
for (i in builder.attributes.indices) {
|
||||
val value = builder.attributes[i].second
|
||||
buildList.add(AttributeListPosition(builder.attributes[i].first, i, value, stride, offset))
|
||||
offset += value.byteSize
|
||||
|
||||
// counter[value.typeIndentity] = (counter[value.typeIndentity] ?: 0) + 1
|
||||
}
|
||||
|
||||
attributes = ImmutableList.copyOf(buildList)
|
||||
}
|
||||
|
||||
override fun apply(target: GLVertexArrayObject, enable: Boolean) {
|
||||
for (i in attributes.indices) {
|
||||
val value = attributes[i]
|
||||
target.attribute(i, value.glType.logicalSize, value.glType.typeIndentity, false, value.stride, value.offset)
|
||||
|
||||
if (enable) {
|
||||
target.enableAttribute(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val VEC3F = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F)}.build()
|
||||
val VERTEX_TEXTURE = GLFlatAttributeListBuilder().also {it.push(GLType.VEC3F).push(GLType.VEC2F)}.build()
|
||||
}
|
||||
}
|
||||
|
||||
class GLFlatAttributeListBuilder : IGLAttributeList {
|
||||
val attributes = ArrayList<Pair<String, GLType>>()
|
||||
|
||||
private fun findName(name: String): Boolean {
|
||||
for (value in attributes) {
|
||||
if (value.first == name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun push(type: GLType): GLFlatAttributeListBuilder {
|
||||
return push("$type#${attributes.size}", type)
|
||||
}
|
||||
|
||||
fun push(name: String, type: GLType): GLFlatAttributeListBuilder {
|
||||
check(!findName(name)) { "Already has named attribute $name!" }
|
||||
attributes.add(name to type)
|
||||
return this
|
||||
}
|
||||
|
||||
fun build() = GLFlatAttributeList(this)
|
||||
|
||||
@Deprecated("Используй build()")
|
||||
override fun apply(target: GLVertexArrayObject, enable: Boolean) {
|
||||
var offset = 0L
|
||||
var stride = 0
|
||||
|
||||
for (i in attributes) {
|
||||
stride += i.second.byteSize
|
||||
}
|
||||
|
||||
for (i in attributes.indices) {
|
||||
val value = attributes[i].second
|
||||
target.attribute(i, value.logicalSize, value.typeIndentity, false, stride, offset)
|
||||
offset += value.byteSize
|
||||
|
||||
if (enable) {
|
||||
target.enableAttribute(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val VEC3F = GLFlatAttributeList.VEC3F
|
||||
val VERTEX_TEXTURE = GLFlatAttributeList.VERTEX_TEXTURE
|
||||
}
|
||||
}
|
47
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLShader.kt
Normal file
47
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLShader.kt
Normal file
@ -0,0 +1,47 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL20
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import java.io.File
|
||||
import kotlin.RuntimeException
|
||||
|
||||
class ShaderCompilationException(reason: String) : RuntimeException(reason)
|
||||
|
||||
class GLShader(
|
||||
body: String,
|
||||
type: Int
|
||||
) {
|
||||
constructor(body: File, type: Int) : this(body.also { require(it.exists()) { "Shader file does not exists: $body" } }.readText(), type)
|
||||
|
||||
val pointer = glCreateShader(type)
|
||||
var unlinked = false
|
||||
private set
|
||||
|
||||
init {
|
||||
glShaderSource(pointer, body)
|
||||
glCompileShader(pointer)
|
||||
|
||||
val result = intArrayOf(0)
|
||||
glGetShaderiv(pointer, GL_COMPILE_STATUS, result)
|
||||
|
||||
if (result[0] == 0) {
|
||||
throw ShaderCompilationException(glGetShaderInfoLog(pointer))
|
||||
}
|
||||
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
fun unlink(): Boolean {
|
||||
if (unlinked)
|
||||
return false
|
||||
|
||||
glDeleteShader(pointer)
|
||||
checkForGLError()
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun vertex(file: File) = GLShader(file, GL_VERTEX_SHADER)
|
||||
fun fragment(file: File) = GLShader(file, GL_FRAGMENT_SHADER)
|
||||
}
|
||||
}
|
115
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLShaderProgram.kt
Normal file
115
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLShaderProgram.kt
Normal file
@ -0,0 +1,115 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL41
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.api.IStruct3f
|
||||
import ru.dbotthepony.kstarbound.api.IStruct4F
|
||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
||||
|
||||
class ShaderLinkException(reason: String) : RuntimeException(reason)
|
||||
|
||||
data class Uniform4f(val x: Float, val y: Float, val z: Float, val w: Float) : IStruct4F
|
||||
data class Uniform3f(val x: Float, val y: Float, val z: Float) : IStruct3f
|
||||
|
||||
class GLUniformLocation(val program: GLShaderProgram, val name: String, val pointer: Int) {
|
||||
fun set(value: IStruct4F): GLUniformLocation {
|
||||
program.state.ensureSameThread()
|
||||
val (v0, v1, v2, v3) = value
|
||||
glProgramUniform4f(program.pointer, pointer, v0, v1, v2, v3)
|
||||
return this
|
||||
}
|
||||
|
||||
fun set(value: IStruct3f): GLUniformLocation {
|
||||
program.state.ensureSameThread()
|
||||
val (v0, v1, v2) = value
|
||||
glProgramUniform3f(program.pointer, pointer, v0, v1, v2)
|
||||
return this
|
||||
}
|
||||
|
||||
fun set(value: Int): GLUniformLocation {
|
||||
program.state.ensureSameThread()
|
||||
glProgramUniform1i(program.pointer, pointer, value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun set(value: FloatMatrix<*>): GLUniformLocation {
|
||||
program.state.ensureSameThread()
|
||||
|
||||
if (value.rows == 3 && value.columns == 3) {
|
||||
// Матрица 3x3
|
||||
glProgramUniformMatrix3fv(program.pointer, pointer, false, value.toFloatArray())
|
||||
checkForGLError()
|
||||
} else if (value.rows == 4 && value.columns == 4) {
|
||||
// Матрица 4x4
|
||||
glProgramUniformMatrix4fv(program.pointer, pointer, false, value.toFloatArray())
|
||||
checkForGLError()
|
||||
} else {
|
||||
throw IllegalArgumentException("Can not use matrix with these dimensions: ${value.rows}x${value.columns}")
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
class GLShaderProgram(val state: GLStateTracker, vararg shaders: GLShader) {
|
||||
val pointer = glCreateProgram()
|
||||
var linked = false
|
||||
private set
|
||||
|
||||
private val attached = HashSet<GLShader>()
|
||||
val access = object : Collection<GLShader> by attached {}
|
||||
|
||||
operator fun get(name: String): GLUniformLocation? {
|
||||
state.ensureSameThread()
|
||||
check(linked) { "Shader program is not linked!" }
|
||||
|
||||
val location = glGetUniformLocation(pointer, name)
|
||||
|
||||
if (location == -1)
|
||||
return null
|
||||
|
||||
return GLUniformLocation(this, name, location)
|
||||
}
|
||||
|
||||
operator fun set(name: String, value: Uniform4f) = this[name]?.set(value)
|
||||
operator fun set(name: String, value: Uniform3f) = this[name]?.set(value)
|
||||
operator fun set(name: String, value: Int) = this[name]?.set(value)
|
||||
operator fun set(name: String, value: FloatMatrix<*>) = this[name]?.set(value)
|
||||
|
||||
fun attach(shader: GLShader) {
|
||||
state.ensureSameThread()
|
||||
check(!linked) { "Already linked!" }
|
||||
|
||||
if (!attached.add(shader)) {
|
||||
throw IllegalStateException("Already attached! $shader")
|
||||
}
|
||||
|
||||
glAttachShader(pointer, shader.pointer)
|
||||
}
|
||||
|
||||
fun link() {
|
||||
check(!linked) { "Already linked!" }
|
||||
glLinkProgram(pointer)
|
||||
|
||||
val success = intArrayOf(0)
|
||||
glGetProgramiv(pointer, GL_LINK_STATUS, success)
|
||||
|
||||
if (success[0] == 0) {
|
||||
throw ShaderLinkException(glGetShaderInfoLog(pointer))
|
||||
}
|
||||
|
||||
glGetError()
|
||||
|
||||
linked = true
|
||||
}
|
||||
|
||||
fun use() = state.use(this)
|
||||
|
||||
fun unlinkChildren() {
|
||||
attached.forEach(GLShader::unlink)
|
||||
}
|
||||
|
||||
init {
|
||||
shaders.forEach(this::attach)
|
||||
}
|
||||
}
|
216
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLStateTracker.kt
Normal file
216
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLStateTracker.kt
Normal file
@ -0,0 +1,216 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||
import ru.dbotthepony.kstarbound.math.Matrix4fStack
|
||||
import ru.dbotthepony.kstarbound.render.TileRenderer
|
||||
import ru.dbotthepony.kstarbound.render.TileRenderers
|
||||
import java.io.File
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
private class GLStateSwitchTracker(private val enum: Int, private var value: Boolean = false) {
|
||||
operator fun getValue(glStateTracker: GLStateTracker, property: KProperty<*>): Boolean {
|
||||
return value
|
||||
}
|
||||
|
||||
operator fun setValue(glStateTracker: GLStateTracker, property: KProperty<*>, value: Boolean) {
|
||||
glStateTracker.ensureSameThread()
|
||||
|
||||
if (value == this.value)
|
||||
return
|
||||
|
||||
if (value) {
|
||||
glEnable(enum)
|
||||
} else {
|
||||
glDisable(enum)
|
||||
}
|
||||
|
||||
checkForGLError()
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
class GLStateTracker {
|
||||
init {
|
||||
// This line is critical for LWJGL's interoperation with GLFW's
|
||||
// OpenGL context, or any context that is managed externally.
|
||||
// LWJGL detects the context that is current in the current thread,
|
||||
// creates the GLCapabilities instance and makes the OpenGL
|
||||
// bindings available for use.
|
||||
GL.createCapabilities()
|
||||
}
|
||||
|
||||
var blend by GLStateSwitchTracker(GL_BLEND)
|
||||
|
||||
var VBO: GLVertexBufferObject? = null
|
||||
set(value) {
|
||||
ensureSameThread()
|
||||
if (field === value) return
|
||||
field = value
|
||||
|
||||
if (value == null) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
checkForGLError()
|
||||
return
|
||||
}
|
||||
|
||||
if (!value.isArray) throw IllegalArgumentException("Provided buffer object is not of Array type")
|
||||
glBindBuffer(GL_ARRAY_BUFFER, value.pointer)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
var EBO: GLVertexBufferObject? = null
|
||||
set(value) {
|
||||
ensureSameThread()
|
||||
if (field === value) return
|
||||
field = value
|
||||
|
||||
if (value == null) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
|
||||
checkForGLError()
|
||||
return
|
||||
}
|
||||
|
||||
if (!value.isElementArray) throw IllegalArgumentException("Provided buffer object is not of Array type")
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, value.pointer)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
var VAO: GLVertexArrayObject? = null
|
||||
set(value) {
|
||||
ensureSameThread()
|
||||
if (field === value) return
|
||||
field = value
|
||||
|
||||
if (value == null) {
|
||||
glBindVertexArray(0)
|
||||
checkForGLError()
|
||||
return
|
||||
}
|
||||
|
||||
glBindVertexArray(value.pointer)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
var program: GLShaderProgram? = null
|
||||
private set
|
||||
|
||||
var texture2D: GLTexture2D? = null
|
||||
set(value) {
|
||||
ensureSameThread()
|
||||
if (field === value) return
|
||||
field = value
|
||||
if (value == null) return
|
||||
glBindTexture(GL_TEXTURE_2D, value.pointer)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
var activeTexture = 0
|
||||
set(value) {
|
||||
ensureSameThread()
|
||||
if (field == value) return
|
||||
require(value >= 0) { "Invalid texture block $value" }
|
||||
require(value < 80) { "Too big texture block index $value, OpenGL 4.6 guarantee only 80!" }
|
||||
field = value
|
||||
glActiveTexture(GL_TEXTURE0 + value)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
init {
|
||||
glActiveTexture(GL_TEXTURE0)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
val thread = Thread.currentThread()
|
||||
val tileRenderers = TileRenderers(this)
|
||||
|
||||
fun ensureSameThread() {
|
||||
if (thread !== Thread.currentThread()) {
|
||||
throw IllegalAccessException("Trying to access $this outside of $thread!")
|
||||
}
|
||||
}
|
||||
|
||||
fun isSameThread() = thread === Thread.currentThread()
|
||||
|
||||
fun program(vararg shaders: GLShader): GLShaderProgram {
|
||||
return GLShaderProgram(this, *shaders)
|
||||
}
|
||||
|
||||
fun newVBO(type: VBOType = VBOType.ARRAY): GLVertexBufferObject {
|
||||
return GLVertexBufferObject(this, type)
|
||||
}
|
||||
|
||||
fun newEBO() = newVBO(VBOType.ELEMENT_ARRAY)
|
||||
fun newVAO() = GLVertexArrayObject(this)
|
||||
fun newTexture(name: String = "<unknown>") = GLTexture2D(this, name)
|
||||
|
||||
private val named2DTextures = HashMap<String, GLTexture2D>()
|
||||
|
||||
fun loadNamedTexture(path: File, memoryFormat: Int = GL_RGBA, fileFormat: Int = GL_RGBA): GLTexture2D {
|
||||
val found = Starbound.findFile(path)
|
||||
|
||||
return named2DTextures.computeIfAbsent(found.absolutePath) {
|
||||
return@computeIfAbsent newTexture(found.absolutePath).upload(found, memoryFormat, fileFormat).generateMips()
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(obj: GLVertexBufferObject): GLVertexBufferObject {
|
||||
if (obj.type == VBOType.ARRAY)
|
||||
VBO = obj
|
||||
else
|
||||
EBO = obj
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
fun unbind(obj: GLVertexBufferObject): GLVertexBufferObject {
|
||||
if (obj.type == VBOType.ARRAY)
|
||||
if (obj == VBO)
|
||||
VBO = null
|
||||
else
|
||||
if (obj == EBO)
|
||||
EBO = null
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
fun bind(obj: GLVertexArrayObject): GLVertexArrayObject {
|
||||
VAO = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
fun unbind(obj: GLVertexArrayObject): GLVertexArrayObject {
|
||||
if (obj == VAO)
|
||||
VAO = null
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
fun use(obj: GLShaderProgram): GLShaderProgram {
|
||||
ensureSameThread()
|
||||
|
||||
if (obj == program) {
|
||||
return obj
|
||||
}
|
||||
|
||||
check(obj.linked) { "Program is not linked!" }
|
||||
|
||||
program = obj
|
||||
glUseProgram(obj.pointer)
|
||||
checkForGLError()
|
||||
return obj
|
||||
}
|
||||
|
||||
val shaderVertexTexture = program(
|
||||
GLShader.fragment(File("./src/main/resources/shaders/f_texture.glsl")),
|
||||
GLShader.vertex(File("./src/main/resources/shaders/v_vertex_texture.glsl"))
|
||||
).also {
|
||||
it.link()
|
||||
it.unlinkChildren()
|
||||
it["_transform"] = Matrix4f.IDENTITY
|
||||
}
|
||||
|
||||
val matrixStack = Matrix4fStack()
|
||||
}
|
146
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLTexture.kt
Normal file
146
src/main/kotlin/ru/dbotthepony/kstarbound/gl/GLTexture.kt
Normal file
@ -0,0 +1,146 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import org.lwjgl.stb.STBImage
|
||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class TextureLoadingException(message: String) : Throwable(message)
|
||||
|
||||
data class UVCoord(val u: Float, val v: Float)
|
||||
|
||||
class GLTexturePropertyTracker(private val flag: Int, var value: Int) {
|
||||
operator fun getValue(thisRef: GLTexture2D, property: KProperty<*>): Int {
|
||||
return value
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: GLTexture2D, property: KProperty<*>, value: Int) {
|
||||
thisRef.state.ensureSameThread()
|
||||
if (this.value == value) return
|
||||
this.value = value
|
||||
glTextureParameteri(thisRef.pointer, flag, value)
|
||||
checkForGLError()
|
||||
}
|
||||
}
|
||||
|
||||
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") {
|
||||
val pointer = glGenTextures()
|
||||
|
||||
var width = 0
|
||||
private set
|
||||
|
||||
var height = 0
|
||||
private set
|
||||
|
||||
var uploaded = false
|
||||
private set
|
||||
|
||||
private var mipsWarning = 2
|
||||
|
||||
var textureMinFilter by GLTexturePropertyTracker(GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
||||
var textureMagFilter by GLTexturePropertyTracker(GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
|
||||
var textureWrapS by GLTexturePropertyTracker(GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
var textureWrapT by GLTexturePropertyTracker(GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||
|
||||
fun bind(): GLTexture2D {
|
||||
if (mipsWarning == 1) {
|
||||
LOGGER.warn("(Likely) Trying to use texture {} before generated it's mips, this probably won't work!", this)
|
||||
mipsWarning = 0
|
||||
} else if (mipsWarning == 2) {
|
||||
mipsWarning = 1
|
||||
}
|
||||
|
||||
state.texture2D = this
|
||||
return this
|
||||
}
|
||||
|
||||
fun generateMips(): GLTexture2D {
|
||||
state.ensureSameThread()
|
||||
glGenerateTextureMipmap(pointer)
|
||||
checkForGLError()
|
||||
mipsWarning = 0
|
||||
return this
|
||||
}
|
||||
|
||||
fun pixelToUV(x: Float, y: Float): UVCoord {
|
||||
check(uploaded) { "Texture is not uploaded to be used" }
|
||||
return UVCoord(x / width, y / height)
|
||||
}
|
||||
|
||||
fun pixelToUV(x: Int, y: Int): UVCoord {
|
||||
check(uploaded) { "Texture is not uploaded to be used" }
|
||||
return UVCoord(x.toFloat() / width, y.toFloat() / height)
|
||||
}
|
||||
|
||||
fun pixelToUV(pos: Vector2i) = pixelToUV(pos.x, pos.y)
|
||||
|
||||
private fun upload(mipmap: Int, loadedFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
|
||||
bind()
|
||||
|
||||
require(width > 0) { "Invalid width $width" }
|
||||
require(height > 0) { "Invalid height $height" }
|
||||
this.width = width
|
||||
this.height = height
|
||||
glTexImage2D(GL_TEXTURE_2D, mipmap, loadedFormat, width, height, 0, bufferFormat, dataFormat, data)
|
||||
checkForGLError()
|
||||
uploaded = true
|
||||
return this
|
||||
}
|
||||
|
||||
private fun upload(mipmap: Int, memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||
bind()
|
||||
|
||||
require(width > 0) { "Invalid width $width" }
|
||||
require(height > 0) { "Invalid height $height" }
|
||||
|
||||
this.width = width
|
||||
this.height = height
|
||||
glTexImage2D(GL_TEXTURE_2D, mipmap, memoryFormat, width, height, 0, bufferFormat, dataFormat, data)
|
||||
checkForGLError()
|
||||
uploaded = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun upload(memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: IntArray): GLTexture2D {
|
||||
return upload(0, memoryFormat, width, height, bufferFormat, dataFormat, data)
|
||||
}
|
||||
|
||||
fun upload(memoryFormat: Int, width: Int, height: Int, bufferFormat: Int, dataFormat: Int, data: ByteBuffer): GLTexture2D {
|
||||
return upload(0, memoryFormat, width, height, bufferFormat, dataFormat, data)
|
||||
}
|
||||
|
||||
fun upload(path: File, memoryFormat: Int, bufferFormat: Int): GLTexture2D {
|
||||
state.ensureSameThread()
|
||||
|
||||
if (!path.exists()) {
|
||||
throw FileNotFoundException("${path.absolutePath} does not exist")
|
||||
}
|
||||
|
||||
if (!path.isFile) {
|
||||
throw FileNotFoundException("${path.absolutePath} is not a file")
|
||||
}
|
||||
|
||||
val getwidth = intArrayOf(0)
|
||||
val getheight = intArrayOf(0)
|
||||
val getchannels = intArrayOf(0)
|
||||
|
||||
val bytes = STBImage.stbi_load(path.absolutePath, getwidth, getheight, getchannels, 0) ?: throw TextureLoadingException("Unable to load ${path.absolutePath}. Is it a valid image?")
|
||||
|
||||
require(getwidth[0] > 0) { "Image ${path.absolutePath} has bad width of ${getwidth[0]}" }
|
||||
require(getheight[0] > 0) { "Image ${path.absolutePath} has bad height of ${getheight[0]}" }
|
||||
|
||||
upload(memoryFormat, getwidth[0], getheight[0], bufferFormat, GL_UNSIGNED_BYTE, bytes)
|
||||
STBImage.stbi_image_free(bytes)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import java.io.Closeable
|
||||
|
||||
class GLVertexArrayObject(val state: GLStateTracker) : Closeable {
|
||||
val pointer = glGenVertexArrays()
|
||||
|
||||
fun bind(): GLVertexArrayObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
|
||||
return state.bind(this)
|
||||
}
|
||||
|
||||
fun unbind(): GLVertexArrayObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
|
||||
return state.unbind(this)
|
||||
}
|
||||
|
||||
fun attribute(position: Int, size: Int, type: Int, normalize: Boolean, stride: Int, offset: Long = 0L): GLVertexArrayObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
|
||||
state.ensureSameThread()
|
||||
glVertexAttribPointer(position, size, type, normalize, stride, offset)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
fun enableAttribute(position: Int): GLVertexArrayObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexArrayObject" }
|
||||
state.ensureSameThread()
|
||||
glEnableVertexArrayAttrib(pointer, position)
|
||||
//glEnableVertexAttribArray(position)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
var isValid = true
|
||||
private set
|
||||
|
||||
override fun close() {
|
||||
state.ensureSameThread()
|
||||
if (isValid) return
|
||||
|
||||
if (state.VAO == this) {
|
||||
state.VAO = null
|
||||
}
|
||||
|
||||
glDeleteVertexArrays(pointer)
|
||||
isValid = false
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import java.io.Closeable
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
enum class VBOType(val value: Int) {
|
||||
ARRAY(GL_ARRAY_BUFFER),
|
||||
ELEMENT_ARRAY(GL_ELEMENT_ARRAY_BUFFER),
|
||||
}
|
||||
|
||||
class GLVertexBufferObject(val state: GLStateTracker, val type: VBOType = VBOType.ARRAY) : Closeable {
|
||||
val pointer = glGenBuffers()
|
||||
|
||||
val isArray get() = type == VBOType.ARRAY
|
||||
val isElementArray get() = type == VBOType.ELEMENT_ARRAY
|
||||
|
||||
fun bind(): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.bind(this)
|
||||
return this
|
||||
}
|
||||
|
||||
fun unbind(): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.unbind(this)
|
||||
return this
|
||||
}
|
||||
|
||||
fun bufferData(data: ByteBuffer, usage: Int): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.ensureSameThread()
|
||||
glNamedBufferData(pointer, data, usage)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
fun bufferData(data: IntArray, usage: Int): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.ensureSameThread()
|
||||
glNamedBufferData(pointer, data, usage)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
fun bufferData(data: FloatArray, usage: Int): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.ensureSameThread()
|
||||
glNamedBufferData(pointer, data, usage)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
fun bufferData(data: DoubleArray, usage: Int): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.ensureSameThread()
|
||||
glNamedBufferData(pointer, data, usage)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
fun bufferData(data: LongArray, usage: Int): GLVertexBufferObject {
|
||||
check(isValid) { "Tried to use NULL GLVertexBufferObject" }
|
||||
state.ensureSameThread()
|
||||
glNamedBufferData(pointer, data, usage)
|
||||
checkForGLError()
|
||||
return this
|
||||
}
|
||||
|
||||
var isValid = true
|
||||
private set
|
||||
|
||||
override fun close() {
|
||||
state.ensureSameThread()
|
||||
if (!isValid) return
|
||||
|
||||
if (state.VBO == this) {
|
||||
state.VBO = null
|
||||
}
|
||||
|
||||
glDeleteBuffers(pointer)
|
||||
isValid = false
|
||||
}
|
||||
}
|
202
src/main/kotlin/ru/dbotthepony/kstarbound/gl/VertexBuilder.kt
Normal file
202
src/main/kotlin/ru/dbotthepony/kstarbound/gl/VertexBuilder.kt
Normal file
@ -0,0 +1,202 @@
|
||||
package ru.dbotthepony.kstarbound.gl
|
||||
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
enum class VertexType(val elements: Int, val indicies: IntArray) {
|
||||
TRIANGLES(3, intArrayOf(0, 1, 2)),
|
||||
QUADS(4, intArrayOf(0, 1, 2, 1, 2, 3))
|
||||
}
|
||||
|
||||
typealias VertexTransformer = (VertexBuilder.Vertex, Int) -> VertexBuilder.Vertex
|
||||
private val emptyTransform: VertexTransformer = { it, _ -> it }
|
||||
|
||||
object VertexTransformers {
|
||||
fun uv(u0: Float,
|
||||
v0: Float,
|
||||
u1: Float,
|
||||
v1: Float,
|
||||
lambda: VertexTransformer = emptyTransform): VertexTransformer {
|
||||
return transformer@{ it, index ->
|
||||
when (index) {
|
||||
0 -> it.pushVec2f(u0, v0)
|
||||
1 -> it.pushVec2f(u1, v0)
|
||||
2 -> it.pushVec2f(u0, v1)
|
||||
3 -> it.pushVec2f(u1, v1)
|
||||
}
|
||||
|
||||
return@transformer lambda(it, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VertexBuilder(val attributes: GLFlatAttributeList, private val type: VertexType) {
|
||||
private val verticies = ArrayList<Vertex>()
|
||||
val indexCount get() = (verticies.size / type.elements) * type.indicies.size
|
||||
|
||||
fun begin(): VertexBuilder {
|
||||
verticies.clear()
|
||||
return this
|
||||
}
|
||||
|
||||
fun vertex(): Vertex {
|
||||
return Vertex()
|
||||
}
|
||||
|
||||
fun quadZ(
|
||||
x0: Float,
|
||||
y0: Float,
|
||||
x1: Float,
|
||||
y1: Float,
|
||||
z: Float,
|
||||
lambda: VertexTransformer = emptyTransform
|
||||
): VertexBuilder {
|
||||
check(type == VertexType.QUADS) { "Currently building $type" }
|
||||
|
||||
lambda(Vertex().pushVec3f(x0, y0, z), 0).end()
|
||||
lambda(Vertex().pushVec3f(x1, y0, z), 1).end()
|
||||
lambda(Vertex().pushVec3f(x0, y1, z), 2).end()
|
||||
lambda(Vertex().pushVec3f(x1, y1, z), 3).end()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun checkValid() {
|
||||
for (vertex in verticies) {
|
||||
vertex.checkValid()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает буфер в указанные VBO и EBO
|
||||
*
|
||||
* операция создаёт мусор вне кучи и довольно медленная
|
||||
*/
|
||||
fun upload(vbo: GLVertexBufferObject, ebo: GLVertexBufferObject, drawType: Int = GL_DYNAMIC_DRAW) {
|
||||
require(vbo.isArray) { "$vbo is not an array" }
|
||||
require(ebo.isElementArray) { "$vbo is not an element array" }
|
||||
|
||||
checkValid()
|
||||
|
||||
check(verticies.size % type.elements == 0) { "Not fully built (expected ${type.elements} verticies to be present for each element, last element has only ${verticies.size % type.elements})" }
|
||||
|
||||
vbo.bind()
|
||||
ebo.bind()
|
||||
|
||||
if (verticies.size == 0) {
|
||||
vbo.bufferData(intArrayOf(), drawType)
|
||||
ebo.bufferData(intArrayOf(), drawType)
|
||||
return
|
||||
}
|
||||
|
||||
val bytes = ByteBuffer.allocateDirect(verticies.size * attributes.stride)
|
||||
bytes.order(ByteOrder.nativeOrder())
|
||||
|
||||
for (vertex in verticies) {
|
||||
vertex.upload(bytes)
|
||||
}
|
||||
|
||||
check(bytes.position() == bytes.capacity()) { "Buffer is not fully filled (position: ${bytes.position()}; capacity: ${bytes.capacity()})" }
|
||||
|
||||
bytes.position(0)
|
||||
vbo.bufferData(bytes, drawType)
|
||||
|
||||
val elementIndicies = IntArray((verticies.size / type.elements) * type.indicies.size)
|
||||
var offset = 0
|
||||
var offsetVertex = 0
|
||||
|
||||
for (i in 0 until verticies.size / type.elements) {
|
||||
for (i2 in type.indicies.indices) {
|
||||
elementIndicies[offset + i2] = type.indicies[i2] + offsetVertex
|
||||
}
|
||||
|
||||
offset += type.indicies.size
|
||||
offsetVertex += type.elements
|
||||
}
|
||||
|
||||
ebo.bufferData(elementIndicies, drawType)
|
||||
}
|
||||
|
||||
inner class Vertex {
|
||||
init {
|
||||
verticies.add(this)
|
||||
}
|
||||
|
||||
private val store = arrayOfNulls<Any>(attributes.size)
|
||||
private var index = 0
|
||||
|
||||
fun upload(bytes: ByteBuffer) {
|
||||
for (element in store) {
|
||||
when (element) {
|
||||
is FloatArray -> for (i in element) bytes.putFloat(i)
|
||||
is IntArray -> for (i in element) bytes.putInt(i)
|
||||
is ByteArray -> for (i in element) bytes.put(i)
|
||||
is DoubleArray -> for (i in element) bytes.putDouble(i)
|
||||
else -> throw IllegalStateException("Unknown element $element")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Vertex(${store.map {
|
||||
return@map when (it) {
|
||||
is FloatArray -> it.joinToString(", ")
|
||||
is IntArray -> it.joinToString(", ")
|
||||
is ByteArray -> it.joinToString(", ")
|
||||
is DoubleArray -> it.joinToString(", ")
|
||||
else -> "null"
|
||||
} }.joinToString("; ")})"
|
||||
}
|
||||
|
||||
fun expect(name: String): Vertex {
|
||||
if (index >= attributes.size) {
|
||||
throw IllegalStateException("Reached end of attribute list early, expected $name")
|
||||
}
|
||||
|
||||
if (attributes[index].name != name) {
|
||||
throw IllegalStateException("Expected $name, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun expect(type: GLType): Vertex {
|
||||
if (index >= attributes.size) {
|
||||
throw IllegalStateException("Reached end of attribute list early, expected type $type")
|
||||
}
|
||||
|
||||
if (attributes[index].glType != type) {
|
||||
throw IllegalStateException("Expected $type, got ${attributes[index].name}[${attributes[index].glType}] (at position $index)")
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun pushVec3f(x: Float, y: Float, z: Float): Vertex {
|
||||
expect(GLType.VEC3F)
|
||||
store[index++] = floatArrayOf(x, y, z)
|
||||
return this
|
||||
}
|
||||
|
||||
fun pushVec2f(x: Float, y: Float): Vertex {
|
||||
expect(GLType.VEC2F)
|
||||
store[index++] = floatArrayOf(x, y)
|
||||
return this
|
||||
}
|
||||
|
||||
fun checkValid() {
|
||||
for (elem in store.indices) {
|
||||
if (store[elem] == null) {
|
||||
throw IllegalStateException("Vertex element at position $elem is null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun end(): VertexBuilder {
|
||||
checkValid()
|
||||
return this@VertexBuilder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
src/main/kotlin/ru/dbotthepony/kstarbound/math/Angle.kt
Normal file
50
src/main/kotlin/ru/dbotthepony/kstarbound/math/Angle.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package ru.dbotthepony.kstarbound.math
|
||||
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
interface IAngle {
|
||||
val pitch: Double
|
||||
val yaw: Double
|
||||
val roll: Double
|
||||
|
||||
fun matrixX(): Matrix4f {
|
||||
val s = sin(pitch).toFloat()
|
||||
val c = cos(pitch).toFloat()
|
||||
|
||||
return Matrix4f(
|
||||
m11 = c, m12 = -s,
|
||||
m21 = s, m22 = c,
|
||||
)
|
||||
}
|
||||
|
||||
fun matrixY(): Matrix4f {
|
||||
val s = sin(yaw).toFloat()
|
||||
val c = cos(yaw).toFloat()
|
||||
|
||||
return Matrix4f(
|
||||
m00 = c, m02 = s,
|
||||
m20 = -s, m22 = c
|
||||
)
|
||||
}
|
||||
|
||||
fun matrixZ(): Matrix4f {
|
||||
val s = sin(roll).toFloat()
|
||||
val c = cos(roll).toFloat()
|
||||
|
||||
return Matrix4f(
|
||||
m00 = c, m01 = -s,
|
||||
m10 = s, m11 = c
|
||||
)
|
||||
}
|
||||
|
||||
fun matrixXYZ(): Matrix4f {
|
||||
return matrixX() * matrixY() * matrixZ()
|
||||
}
|
||||
}
|
||||
|
||||
data class Angle(
|
||||
override val pitch: Double = 0.0,
|
||||
override val yaw: Double = 0.0,
|
||||
override val roll: Double = 0.0,
|
||||
) : IAngle
|
851
src/main/kotlin/ru/dbotthepony/kstarbound/math/Matrix.kt
Normal file
851
src/main/kotlin/ru/dbotthepony/kstarbound/math/Matrix.kt
Normal file
@ -0,0 +1,851 @@
|
||||
package ru.dbotthepony.kstarbound.math
|
||||
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.tan
|
||||
|
||||
interface IMatrixLike {
|
||||
val columns: Int
|
||||
val rows: Int
|
||||
}
|
||||
|
||||
interface IMatrixLikeGetterI {
|
||||
operator fun get(row: Int, column: Int): Int
|
||||
}
|
||||
|
||||
interface IMatrixLikeFloat : IMatrixLike {
|
||||
operator fun get(row: Int, column: Int): Float
|
||||
}
|
||||
|
||||
interface IMatrix : IMatrixLike {
|
||||
operator fun plus(other: IMatrix): IMatrix
|
||||
operator fun minus(other: IMatrix): IMatrix
|
||||
operator fun times(other: IMatrix): IMatrix
|
||||
}
|
||||
|
||||
interface FloatMatrix<T : FloatMatrix<T>> : IMatrix, IMatrixLikeFloat {
|
||||
operator fun plus(other: Float): T
|
||||
operator fun minus(other: Float): T
|
||||
operator fun times(other: Float): T
|
||||
operator fun div(other: Float): T
|
||||
|
||||
override operator fun plus(other: IMatrix): T
|
||||
override operator fun minus(other: IMatrix): T
|
||||
override operator fun times(other: IMatrix): T
|
||||
|
||||
/**
|
||||
* Если матрица больше или меньше, считать что всевозможные остальные координаты равны единице (не менять)
|
||||
*/
|
||||
fun scale(x: Float, y: Float = 1f, z: Float = 1f, w: Float = 1f): T
|
||||
|
||||
fun scale(value: Vector4f) = scale(value.x, value.y, value.z, value.w)
|
||||
|
||||
fun translate(x: Float = 0f, y: Float = 0f, z: Float = 0f): T
|
||||
fun translate(vector3f: Vector3f) = translate(vector3f.x, vector3f.y, vector3f.z)
|
||||
|
||||
fun translateWithScale(x: Float = 0f, y: Float = 0f, z: Float = 0f): T
|
||||
fun translateWithScale(vector3f: Vector3f) = translateWithScale(vector3f.x, vector3f.y, vector3f.z)
|
||||
|
||||
/**
|
||||
* Выдает массив в готовом для OpenGL виде (строка -> столбец) по умолчанию
|
||||
*/
|
||||
fun toFloatArray(columnMajor: Boolean = true): FloatArray {
|
||||
val buff = FloatArray(rows * columns)
|
||||
|
||||
if (columnMajor) {
|
||||
for (row in 0 until rows) {
|
||||
for (column in 0 until columns) {
|
||||
buff[row + rows * column] = this[row, column]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (row in 0 until rows) {
|
||||
for (column in 0 until columns) {
|
||||
buff[row * columns + column] = this[row, column]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buff
|
||||
}
|
||||
}
|
||||
|
||||
interface MutableFloatMatrix<T : MutableFloatMatrix<T>> : FloatMatrix<T> {
|
||||
operator fun set(row: Int, column: Int, value: Float)
|
||||
}
|
||||
|
||||
abstract class AbstractMatrix4f<T : AbstractMatrix4f<T>> : FloatMatrix<T> {
|
||||
abstract val m00: Float; abstract val m01: Float; abstract val m02: Float; abstract val m03: Float
|
||||
abstract val m10: Float; abstract val m11: Float; abstract val m12: Float; abstract val m13: Float
|
||||
abstract val m20: Float; abstract val m21: Float; abstract val m22: Float; abstract val m23: Float
|
||||
abstract val m30: Float; abstract val m31: Float; abstract val m32: Float; abstract val m33: Float
|
||||
|
||||
override val columns: Int
|
||||
get() = 4
|
||||
|
||||
override val rows: Int
|
||||
get() = 4
|
||||
|
||||
override fun get(row: Int, column: Int): Float {
|
||||
return when (column) {
|
||||
0 -> when (row) {
|
||||
0 -> m00
|
||||
1 -> m10
|
||||
2 -> m20
|
||||
3 -> m30
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
1 -> when (row) {
|
||||
0 -> m01
|
||||
1 -> m11
|
||||
2 -> m21
|
||||
3 -> m31
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
2 -> when (row) {
|
||||
0 -> m02
|
||||
1 -> m12
|
||||
2 -> m22
|
||||
3 -> m32
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
3 -> when (row) {
|
||||
0 -> m03
|
||||
1 -> m13
|
||||
2 -> m23
|
||||
3 -> m33
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
else -> throw IndexOutOfBoundsException("Column: $column")
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun createOrModify(
|
||||
m00: Float, m01: Float, m02: Float, m03: Float,
|
||||
m10: Float, m11: Float, m12: Float, m13: Float,
|
||||
m20: Float, m21: Float, m22: Float, m23: Float,
|
||||
m30: Float, m31: Float, m32: Float, m33: Float,
|
||||
): T
|
||||
|
||||
override fun plus(other: IMatrix): T {
|
||||
if (other !is FloatMatrix<*>) {
|
||||
throw IllegalArgumentException("Can not use $other for addition")
|
||||
}
|
||||
|
||||
if (other.columns != 4 || other.rows != 4) {
|
||||
throw IllegalArgumentException("Concrete Matrix4f can only use 4x4 matrixes")
|
||||
}
|
||||
|
||||
val m00: Float; val m01: Float; val m02: Float; val m03: Float;
|
||||
val m10: Float; val m11: Float; val m12: Float; val m13: Float;
|
||||
val m20: Float; val m21: Float; val m22: Float; val m23: Float;
|
||||
val m30: Float; val m31: Float; val m32: Float; val m33: Float;
|
||||
|
||||
if (other is Matrix4f) {
|
||||
m00 = other.m00; m01 = other.m01; m02 = other.m02; m03 = other.m03;
|
||||
m10 = other.m10; m11 = other.m11; m12 = other.m12; m13 = other.m13;
|
||||
m20 = other.m20; m21 = other.m21; m22 = other.m22; m23 = other.m23;
|
||||
m30 = other.m30; m31 = other.m31; m32 = other.m32; m33 = other.m33;
|
||||
} else {
|
||||
m00 = other[0, 0]; m01 = other[0, 1]; m02 = other[0, 2]; m03 = other[0, 3];
|
||||
m10 = other[1, 0]; m11 = other[1, 1]; m12 = other[1, 2]; m13 = other[1, 3];
|
||||
m20 = other[2, 0]; m21 = other[2, 1]; m22 = other[2, 2]; m23 = other[2, 3];
|
||||
m30 = other[3, 0]; m31 = other[3, 1]; m32 = other[3, 2]; m33 = other[3, 3];
|
||||
}
|
||||
|
||||
return createOrModify(
|
||||
this.m00 + m00, this.m01 + m01, this.m02 + m02, this.m03 + m03,
|
||||
this.m10 + m10, this.m11 + m11, this.m12 + m12, this.m13 + m13,
|
||||
this.m20 + m20, this.m21 + m21, this.m22 + m22, this.m23 + m23,
|
||||
this.m30 + m30, this.m31 + m31, this.m32 + m32, this.m33 + m33,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun minus(other: IMatrix): T {
|
||||
if (other !is FloatMatrix<*>) {
|
||||
throw IllegalArgumentException("Can not use $other for subtraction")
|
||||
}
|
||||
|
||||
if (other.columns != 4 || other.rows != 4) {
|
||||
throw IllegalArgumentException("Concrete Matrix4f can only use 4x4 matrices")
|
||||
}
|
||||
|
||||
val m00: Float; val m01: Float; val m02: Float; val m03: Float;
|
||||
val m10: Float; val m11: Float; val m12: Float; val m13: Float;
|
||||
val m20: Float; val m21: Float; val m22: Float; val m23: Float;
|
||||
val m30: Float; val m31: Float; val m32: Float; val m33: Float;
|
||||
|
||||
if (other is Matrix4f) {
|
||||
m00 = other.m00; m01 = other.m01; m02 = other.m02; m03 = other.m03;
|
||||
m10 = other.m10; m11 = other.m11; m12 = other.m12; m13 = other.m13;
|
||||
m20 = other.m20; m21 = other.m21; m22 = other.m22; m23 = other.m23;
|
||||
m30 = other.m30; m31 = other.m31; m32 = other.m32; m33 = other.m33;
|
||||
} else {
|
||||
m00 = other[0, 0]; m01 = other[0, 1]; m02 = other[0, 2]; m03 = other[0, 3];
|
||||
m10 = other[1, 0]; m11 = other[1, 1]; m12 = other[1, 2]; m13 = other[1, 3];
|
||||
m20 = other[2, 0]; m21 = other[2, 1]; m22 = other[2, 2]; m23 = other[2, 3];
|
||||
m30 = other[3, 0]; m31 = other[3, 1]; m32 = other[3, 2]; m33 = other[3, 3];
|
||||
}
|
||||
|
||||
return createOrModify(
|
||||
this.m00 - m00, this.m01 - m01, this.m02 - m02, this.m03 - m03,
|
||||
this.m10 - m10, this.m11 - m11, this.m12 - m12, this.m13 - m13,
|
||||
this.m20 - m20, this.m21 - m21, this.m22 - m22, this.m23 - m23,
|
||||
this.m30 - m30, this.m31 - m31, this.m32 - m32, this.m33 - m33,
|
||||
)
|
||||
}
|
||||
|
||||
override fun plus(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 + other, this.m01 + other, this.m02 + other, this.m03 + other,
|
||||
this.m10 + other, this.m11 + other, this.m12 + other, this.m13 + other,
|
||||
this.m20 + other, this.m21 + other, this.m22 + other, this.m23 + other,
|
||||
this.m30 + other, this.m31 + other, this.m32 + other, this.m33 + other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun minus(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 - other, this.m01 - other, this.m02 - other, this.m03 - other,
|
||||
this.m10 - other, this.m11 - other, this.m12 - other, this.m13 - other,
|
||||
this.m20 - other, this.m21 - other, this.m22 - other, this.m23 - other,
|
||||
this.m30 - other, this.m31 - other, this.m32 - other, this.m33 - other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun times(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 * other, this.m01 * other, this.m02 * other, this.m03 * other,
|
||||
this.m10 * other, this.m11 * other, this.m12 * other, this.m13 * other,
|
||||
this.m20 * other, this.m21 * other, this.m22 * other, this.m23 * other,
|
||||
this.m30 * other, this.m31 * other, this.m32 * other, this.m33 * other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun scale(x: Float, y: Float, z: Float, w: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 * x, this.m01, this.m02, this.m03,
|
||||
this.m10, this.m11 * y, this.m12, this.m13,
|
||||
this.m20, this.m21, this.m22 * z, this.m23,
|
||||
this.m30, this.m31, this.m32, this.m33 * w,
|
||||
)
|
||||
}
|
||||
|
||||
override fun translate(x: Float, y: Float, z: Float): T {
|
||||
return createOrModify(
|
||||
this.m00, this.m01, this.m02, this.m03 + x,
|
||||
this.m10, this.m11, this.m12, this.m13 + y,
|
||||
this.m20, this.m21, this.m22, this.m23 + z,
|
||||
this.m30, this.m31, this.m32, this.m33,
|
||||
)
|
||||
}
|
||||
|
||||
override fun div(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 / other, this.m01 / other, this.m02 / other, this.m03 / other,
|
||||
this.m10 / other, this.m11 / other, this.m12 / other, this.m13 / other,
|
||||
this.m20 / other, this.m21 / other, this.m22 / other, this.m23 / other,
|
||||
this.m30 / other, this.m31 / other, this.m32 / other, this.m33 / other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun times(other: IMatrix): T {
|
||||
if (other !is FloatMatrix<*>) {
|
||||
throw IllegalArgumentException("Can not use $other for multiplication")
|
||||
}
|
||||
|
||||
if (other.columns != 4 || other.rows != 4) {
|
||||
throw IllegalArgumentException("Concrete Matrix4f can only use 4x4 matrices")
|
||||
}
|
||||
|
||||
val m00: Float; val m01: Float; val m02: Float; val m03: Float;
|
||||
val m10: Float; val m11: Float; val m12: Float; val m13: Float;
|
||||
val m20: Float; val m21: Float; val m22: Float; val m23: Float;
|
||||
val m30: Float; val m31: Float; val m32: Float; val m33: Float;
|
||||
|
||||
if (other is Matrix4f) {
|
||||
m00 = other.m00; m01 = other.m01; m02 = other.m02; m03 = other.m03;
|
||||
m10 = other.m10; m11 = other.m11; m12 = other.m12; m13 = other.m13;
|
||||
m20 = other.m20; m21 = other.m21; m22 = other.m22; m23 = other.m23;
|
||||
m30 = other.m30; m31 = other.m31; m32 = other.m32; m33 = other.m33;
|
||||
} else {
|
||||
m00 = other[0, 0]; m01 = other[0, 1]; m02 = other[0, 2]; m03 = other[0, 3];
|
||||
m10 = other[1, 0]; m11 = other[1, 1]; m12 = other[1, 2]; m13 = other[1, 3];
|
||||
m20 = other[2, 0]; m21 = other[2, 1]; m22 = other[2, 2]; m23 = other[2, 3];
|
||||
m30 = other[3, 0]; m31 = other[3, 1]; m32 = other[3, 2]; m33 = other[3, 3];
|
||||
}
|
||||
|
||||
// первый столбец
|
||||
val newm00 =
|
||||
this.m00 * m00 +
|
||||
this.m01 * m10 +
|
||||
this.m02 * m20 +
|
||||
this.m03 * m30
|
||||
|
||||
val newm10 =
|
||||
this.m10 * m00 +
|
||||
this.m11 * m10 +
|
||||
this.m12 * m20 +
|
||||
this.m13 * m30
|
||||
|
||||
val newm20 =
|
||||
this.m20 * m00 +
|
||||
this.m21 * m10 +
|
||||
this.m22 * m20 +
|
||||
this.m23 * m30
|
||||
|
||||
val newm30 =
|
||||
this.m30 * m00 +
|
||||
this.m31 * m10 +
|
||||
this.m32 * m20 +
|
||||
this.m33 * m30
|
||||
|
||||
// второй столбец
|
||||
val newm01 =
|
||||
this.m00 * m01 +
|
||||
this.m01 * m11 +
|
||||
this.m02 * m21 +
|
||||
this.m03 * m31
|
||||
|
||||
val newm11 =
|
||||
this.m10 * m01 +
|
||||
this.m11 * m11 +
|
||||
this.m12 * m21 +
|
||||
this.m13 * m31
|
||||
|
||||
val newm21 =
|
||||
this.m20 * m01 +
|
||||
this.m21 * m11 +
|
||||
this.m22 * m21 +
|
||||
this.m23 * m31
|
||||
|
||||
val newm31 =
|
||||
this.m30 * m01 +
|
||||
this.m31 * m11 +
|
||||
this.m32 * m21 +
|
||||
this.m33 * m31
|
||||
|
||||
// третий столбец
|
||||
val newm02 =
|
||||
this.m00 * m02 +
|
||||
this.m01 * m12 +
|
||||
this.m02 * m22 +
|
||||
this.m03 * m32
|
||||
|
||||
val newm12 =
|
||||
this.m10 * m02 +
|
||||
this.m11 * m12 +
|
||||
this.m12 * m22 +
|
||||
this.m13 * m32
|
||||
|
||||
val newm22 =
|
||||
this.m20 * m02 +
|
||||
this.m21 * m12 +
|
||||
this.m22 * m22 +
|
||||
this.m23 * m32
|
||||
|
||||
val newm32 =
|
||||
this.m30 * m02 +
|
||||
this.m31 * m12 +
|
||||
this.m32 * m22 +
|
||||
this.m33 * m32
|
||||
|
||||
// четвёртый столбец
|
||||
val newm03 =
|
||||
this.m00 * m03 +
|
||||
this.m01 * m13 +
|
||||
this.m02 * m23 +
|
||||
this.m03 * m33
|
||||
|
||||
val newm13 =
|
||||
this.m10 * m03 +
|
||||
this.m11 * m13 +
|
||||
this.m12 * m23 +
|
||||
this.m13 * m33
|
||||
|
||||
val newm23 =
|
||||
this.m20 * m03 +
|
||||
this.m21 * m13 +
|
||||
this.m22 * m23 +
|
||||
this.m23 * m33
|
||||
|
||||
val newm33 =
|
||||
this.m30 * m03 +
|
||||
this.m31 * m13 +
|
||||
this.m32 * m23 +
|
||||
this.m33 * m33
|
||||
|
||||
return createOrModify(
|
||||
newm00, newm01, newm02, newm03,
|
||||
newm10, newm11, newm12, newm13,
|
||||
newm20, newm21, newm22, newm23,
|
||||
newm30, newm31, newm32, newm33,
|
||||
)
|
||||
}
|
||||
|
||||
override fun translateWithScale(x: Float, y: Float, z: Float): T {
|
||||
return createOrModify(
|
||||
m00, m01, m02, m03 + x * m00 + y * m01 + z * m02,
|
||||
m10, m11, m12, m13 + x * m10 + y * m11 + z * m12,
|
||||
m20, m21, m22, m23 + x * m20 + y * m21 + z * m22,
|
||||
m30, m31, m32, m33,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Matrix4f(
|
||||
override val m00: Float = 1f, override val m01: Float = 0f, override val m02: Float = 0f, override val m03: Float = 0f,
|
||||
override val m10: Float = 0f, override val m11: Float = 1f, override val m12: Float = 0f, override val m13: Float = 0f,
|
||||
override val m20: Float = 0f, override val m21: Float = 0f, override val m22: Float = 1f, override val m23: Float = 0f,
|
||||
override val m30: Float = 0f, override val m31: Float = 0f, override val m32: Float = 0f, override val m33: Float = 1f,
|
||||
) : AbstractMatrix4f<Matrix4f>() {
|
||||
override fun createOrModify(
|
||||
m00: Float, m01: Float, m02: Float, m03: Float,
|
||||
m10: Float, m11: Float, m12: Float, m13: Float,
|
||||
m20: Float, m21: Float, m22: Float, m23: Float,
|
||||
m30: Float, m31: Float, m32: Float, m33: Float,
|
||||
): Matrix4f {
|
||||
return Matrix4f(
|
||||
m00 = m00, m01 = m01, m02 = m02, m03 = m03,
|
||||
m10 = m10, m11 = m11, m12 = m12, m13 = m13,
|
||||
m20 = m20, m21 = m21, m22 = m22, m23 = m23,
|
||||
m30 = m30, m31 = m31, m32 = m32, m33 = m33,
|
||||
)
|
||||
}
|
||||
|
||||
fun toMutableMatrix(): MutableMatrix4f {
|
||||
return MutableMatrix4f(
|
||||
m00 = m00, m01 = m01, m02 = m02, m03 = m03,
|
||||
m10 = m10, m11 = m11, m12 = m12, m13 = m13,
|
||||
m20 = m20, m21 = m21, m22 = m22, m23 = m23,
|
||||
m30 = m30, m31 = m31, m32 = m32, m33 = m33,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val IDENTITY = Matrix4f()
|
||||
|
||||
val SCREEN_FLIP = IDENTITY.let {
|
||||
return@let it * Vector3f.FORWARD.rotateAroundThis(-PI / 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает матрицу ортографической проекции, с ИНВЕНТИРОВАННОЙ y координатой, и с добавлением 2f
|
||||
*
|
||||
* Т.е. любое значение компоненты вектора y будет иметь противоположный знак после перемножения на данную матрицу
|
||||
*
|
||||
* Смысл данного изменения знака в преобразовании экранных координат OpenGL к вменяемому виду. Многие примеры указывают Z как отрицательную компоненту,
|
||||
* что так же "убирает" это недоумение, только вот у нас Z это глубина, а не высота
|
||||
*
|
||||
* y = 0 будет соответствовать верхнему левому углу окна
|
||||
*/
|
||||
fun ortho(left: Float, right: Float, bottom: Float, top: Float, zNear: Float, zFar: Float): Matrix4f {
|
||||
return Matrix4f(
|
||||
m00 = 2f / (right - left),
|
||||
m11 = -2f / (top - bottom),
|
||||
m22 = 2f / (zFar - zNear),
|
||||
m03 = -(right + left) / (right - left),
|
||||
m13 = -(top + bottom) / (top - bottom) + 2f,
|
||||
m23 = -(zFar + zNear) / (zFar - zNear)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает матрицу ортографической проекции, без инвентирования
|
||||
*
|
||||
* y = 0 будет соответствовать нижнему левому углу окна
|
||||
*/
|
||||
fun orthoDirect(left: Float, right: Float, bottom: Float, top: Float, zNear: Float, zFar: Float): Matrix4f {
|
||||
return Matrix4f(
|
||||
m00 = 2f / (right - left),
|
||||
m11 = 2f / (top - bottom),
|
||||
m22 = 2f / (zFar - zNear),
|
||||
m03 = -(right + left) / (right - left),
|
||||
m13 = -(top + bottom) / (top - bottom),
|
||||
m23 = -(zFar + zNear) / (zFar - zNear)
|
||||
)
|
||||
}
|
||||
|
||||
fun perspective(fov: Float, zFar: Float, zNear: Float): Matrix4f {
|
||||
val scale = (1.0 / (tan(Math.toRadians(fov.toDouble()) / 2.0))).toFloat()
|
||||
val r = zFar - zNear
|
||||
|
||||
return Matrix4f(
|
||||
m00 = scale,
|
||||
m11 = scale,
|
||||
m22 = -zFar / r,
|
||||
m23 = -1f,
|
||||
m32 = -zFar * zNear / r,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MutableMatrix4f(
|
||||
override var m00: Float = 1f, override var m01: Float = 0f, override var m02: Float = 0f, override var m03: Float = 0f,
|
||||
override var m10: Float = 0f, override var m11: Float = 1f, override var m12: Float = 0f, override var m13: Float = 0f,
|
||||
override var m20: Float = 0f, override var m21: Float = 0f, override var m22: Float = 1f, override var m23: Float = 0f,
|
||||
override var m30: Float = 0f, override var m31: Float = 0f, override var m32: Float = 0f, override var m33: Float = 1f,
|
||||
) : AbstractMatrix4f<MutableMatrix4f>() {
|
||||
override fun createOrModify(
|
||||
m00: Float, m01: Float, m02: Float, m03: Float,
|
||||
m10: Float, m11: Float, m12: Float, m13: Float,
|
||||
m20: Float, m21: Float, m22: Float, m23: Float,
|
||||
m30: Float, m31: Float, m32: Float, m33: Float,
|
||||
): MutableMatrix4f {
|
||||
this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03
|
||||
this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13
|
||||
this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23
|
||||
this.m30 = m30; this.m31 = m31; this.m32 = m32; this.m33 = m33
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun set(row: Int, column: Int, value: Float) {
|
||||
when (column) {
|
||||
0 -> when (row) {
|
||||
0 -> m00 = value
|
||||
1 -> m10 = value
|
||||
2 -> m20 = value
|
||||
3 -> m30 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
1 -> when (row) {
|
||||
0 -> m01 = value
|
||||
1 -> m11 = value
|
||||
2 -> m21 = value
|
||||
3 -> m31 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
2 -> when (row) {
|
||||
0 -> m02 = value
|
||||
1 -> m12 = value
|
||||
2 -> m22 = value
|
||||
3 -> m32 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
3 -> when (row) {
|
||||
0 -> m03 = value
|
||||
1 -> m13 = value
|
||||
2 -> m23 = value
|
||||
3 -> m33 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
else -> throw IndexOutOfBoundsException("Column: $column")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractMatrix3f<T : AbstractMatrix3f<T>> : FloatMatrix<T> {
|
||||
abstract val m00: Float; abstract val m01: Float; abstract val m02: Float
|
||||
abstract val m10: Float; abstract val m11: Float; abstract val m12: Float
|
||||
abstract val m20: Float; abstract val m21: Float; abstract val m22: Float
|
||||
|
||||
override val columns: Int
|
||||
get() = 3
|
||||
|
||||
override val rows: Int
|
||||
get() = 3
|
||||
|
||||
override fun get(row: Int, column: Int): Float {
|
||||
return when (column) {
|
||||
0 -> when (row) {
|
||||
0 -> m00
|
||||
1 -> m10
|
||||
2 -> m20
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
1 -> when (row) {
|
||||
0 -> m01
|
||||
1 -> m11
|
||||
2 -> m21
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
2 -> when (row) {
|
||||
0 -> m02
|
||||
1 -> m12
|
||||
2 -> m22
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
else -> throw IndexOutOfBoundsException("Column: $column")
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun createOrModify(
|
||||
m00: Float, m01: Float, m02: Float,
|
||||
m10: Float, m11: Float, m12: Float,
|
||||
m20: Float, m21: Float, m22: Float,
|
||||
): T
|
||||
|
||||
override fun plus(other: IMatrix): T {
|
||||
if (other !is FloatMatrix<*>) {
|
||||
throw IllegalArgumentException("Can not use $other for addition")
|
||||
}
|
||||
|
||||
if (other.columns != 3 || other.rows != 3) {
|
||||
throw IllegalArgumentException("Concrete Matrix3f can only use 3x3 matrices")
|
||||
}
|
||||
|
||||
val m00: Float; val m01: Float; val m02: Float;
|
||||
val m10: Float; val m11: Float; val m12: Float;
|
||||
val m20: Float; val m21: Float; val m22: Float;
|
||||
|
||||
if (other is Matrix3f) {
|
||||
m00 = other.m00; m01 = other.m01; m02 = other.m02;
|
||||
m10 = other.m10; m11 = other.m11; m12 = other.m12;
|
||||
m20 = other.m20; m21 = other.m21; m22 = other.m22;
|
||||
} else {
|
||||
m00 = other[0, 0]; m01 = other[0, 1]; m02 = other[0, 2];
|
||||
m10 = other[1, 0]; m11 = other[1, 1]; m12 = other[1, 2];
|
||||
m20 = other[2, 0]; m21 = other[2, 1]; m22 = other[2, 2];
|
||||
}
|
||||
|
||||
return createOrModify(
|
||||
this.m00 + m00, this.m01 + m01, this.m02 + m02,
|
||||
this.m10 + m10, this.m11 + m11, this.m12 + m12,
|
||||
this.m20 + m20, this.m21 + m21, this.m22 + m22,
|
||||
)
|
||||
}
|
||||
|
||||
override fun minus(other: IMatrix): T {
|
||||
if (other !is FloatMatrix<*>) {
|
||||
throw IllegalArgumentException("Can not use $other for subtraction")
|
||||
}
|
||||
|
||||
if (other.columns != 3 || other.rows != 3) {
|
||||
throw IllegalArgumentException("Concrete Matrix3f can only use 3x3 matrices")
|
||||
}
|
||||
|
||||
val m00: Float; val m01: Float; val m02: Float;
|
||||
val m10: Float; val m11: Float; val m12: Float;
|
||||
val m20: Float; val m21: Float; val m22: Float;
|
||||
|
||||
if (other is Matrix3f) {
|
||||
m00 = other.m00; m01 = other.m01; m02 = other.m02;
|
||||
m10 = other.m10; m11 = other.m11; m12 = other.m12;
|
||||
m20 = other.m20; m21 = other.m21; m22 = other.m22;
|
||||
} else {
|
||||
m00 = other[0, 0]; m01 = other[0, 1]; m02 = other[0, 2];
|
||||
m10 = other[1, 0]; m11 = other[1, 1]; m12 = other[1, 2];
|
||||
m20 = other[2, 0]; m21 = other[2, 1]; m22 = other[2, 2];
|
||||
}
|
||||
|
||||
return createOrModify(
|
||||
this.m00 - m00, this.m01 - m01, this.m02 - m02,
|
||||
this.m10 - m10, this.m11 - m11, this.m12 - m12,
|
||||
this.m20 - m20, this.m21 - m21, this.m22 - m22,
|
||||
)
|
||||
}
|
||||
|
||||
override fun plus(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 + other, this.m01 + other, this.m02 + other,
|
||||
this.m10 + other, this.m11 + other, this.m12 + other,
|
||||
this.m20 + other, this.m21 + other, this.m22 + other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun minus(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 - other, this.m01 - other, this.m02 - other,
|
||||
this.m10 - other, this.m11 - other, this.m12 - other,
|
||||
this.m20 - other, this.m21 - other, this.m22 - other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun times(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 * other, this.m01 * other, this.m02 * other,
|
||||
this.m10 * other, this.m11 * other, this.m12 * other,
|
||||
this.m20 * other, this.m21 * other, this.m22 * other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun div(other: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 / other, this.m01 / other, this.m02 / other,
|
||||
this.m10 / other, this.m11 / other, this.m12 / other,
|
||||
this.m20 / other, this.m21 / other, this.m22 / other,
|
||||
)
|
||||
}
|
||||
|
||||
override fun scale(x: Float, y: Float, z: Float, w: Float): T {
|
||||
return createOrModify(
|
||||
this.m00 * x, this.m01, this.m02,
|
||||
this.m10, this.m11 * y, this.m12,
|
||||
this.m20, this.m21, this.m22 * z,
|
||||
)
|
||||
}
|
||||
|
||||
override fun translate(x: Float, y: Float, z: Float): T {
|
||||
return createOrModify(
|
||||
this.m00, this.m01, this.m02 + x,
|
||||
this.m10, this.m11, this.m12 + y,
|
||||
this.m20, this.m21, this.m22,
|
||||
)
|
||||
}
|
||||
|
||||
override fun times(other: IMatrix): T {
|
||||
if (other !is FloatMatrix<*>) {
|
||||
throw IllegalArgumentException("Can not use $other for multiplication")
|
||||
}
|
||||
|
||||
if (other.columns != 3 || other.rows != 3) {
|
||||
throw IllegalArgumentException("Concrete Matrix3f can only use 3x3 matrixes")
|
||||
}
|
||||
|
||||
val m00: Float; val m01: Float; val m02: Float;
|
||||
val m10: Float; val m11: Float; val m12: Float;
|
||||
val m20: Float; val m21: Float; val m22: Float;
|
||||
|
||||
if (other is Matrix3f) {
|
||||
m00 = other.m00; m01 = other.m01; m02 = other.m02;
|
||||
m10 = other.m10; m11 = other.m11; m12 = other.m12;
|
||||
m20 = other.m20; m21 = other.m21; m22 = other.m22;
|
||||
} else {
|
||||
m00 = other[0, 0]; m01 = other[0, 1]; m02 = other[0, 2];
|
||||
m10 = other[1, 0]; m11 = other[1, 1]; m12 = other[1, 2];
|
||||
m20 = other[2, 0]; m21 = other[2, 1]; m22 = other[2, 2];
|
||||
}
|
||||
|
||||
// первый столбец
|
||||
val newm00 =
|
||||
this.m00 * m00 +
|
||||
this.m01 * m10 +
|
||||
this.m02 * m20
|
||||
|
||||
val newm10 =
|
||||
this.m10 * m00 +
|
||||
this.m11 * m10 +
|
||||
this.m12 * m20
|
||||
|
||||
val newm20 =
|
||||
this.m20 * m00 +
|
||||
this.m21 * m10 +
|
||||
this.m22 * m20
|
||||
|
||||
// второй столбец
|
||||
val newm01 =
|
||||
this.m00 * m01 +
|
||||
this.m01 * m11 +
|
||||
this.m02 * m21
|
||||
|
||||
val newm11 =
|
||||
this.m10 * m01 +
|
||||
this.m11 * m11 +
|
||||
this.m12 * m21
|
||||
|
||||
val newm21 =
|
||||
this.m20 * m01 +
|
||||
this.m21 * m11 +
|
||||
this.m22 * m21
|
||||
|
||||
// третий столбец
|
||||
val newm02 =
|
||||
this.m00 * m02 +
|
||||
this.m01 * m12 +
|
||||
this.m02 * m22
|
||||
|
||||
val newm12 =
|
||||
this.m10 * m02 +
|
||||
this.m11 * m12 +
|
||||
this.m12 * m22
|
||||
|
||||
val newm22 =
|
||||
this.m20 * m02 +
|
||||
this.m21 * m12 +
|
||||
this.m22 * m22
|
||||
|
||||
return createOrModify(
|
||||
newm00, newm01, newm02,
|
||||
newm10, newm11, newm12,
|
||||
newm20, newm21, newm22,
|
||||
)
|
||||
}
|
||||
|
||||
override fun translateWithScale(x: Float, y: Float, z: Float): T {
|
||||
return createOrModify(
|
||||
m00, m01, m02 + x * m00 + y * m01,
|
||||
m10, m11, m12 + x * m10 + y * m11,
|
||||
m20, m21, m22,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class Matrix3f(
|
||||
override val m00: Float = 1f, override val m01: Float = 0f, override val m02: Float = 0f,
|
||||
override val m10: Float = 0f, override val m11: Float = 1f, override val m12: Float = 0f,
|
||||
override val m20: Float = 0f, override val m21: Float = 0f, override val m22: Float = 1f,
|
||||
) : AbstractMatrix3f<Matrix3f>() {
|
||||
override fun createOrModify(
|
||||
m00: Float, m01: Float, m02: Float,
|
||||
m10: Float, m11: Float, m12: Float,
|
||||
m20: Float, m21: Float, m22: Float,
|
||||
): Matrix3f {
|
||||
return Matrix3f(
|
||||
m00 = m00, m01 = m01, m02 = m02,
|
||||
m10 = m10, m11 = m11, m12 = m12,
|
||||
m20 = m20, m21 = m21, m22 = m22,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val IDENTITY = Matrix3f()
|
||||
}
|
||||
}
|
||||
|
||||
data class MutableMatrix3f(
|
||||
override var m00: Float = 1f, override var m01: Float = 0f, override var m02: Float = 0f,
|
||||
override var m10: Float = 0f, override var m11: Float = 1f, override var m12: Float = 0f,
|
||||
override var m20: Float = 0f, override var m21: Float = 0f, override var m22: Float = 1f,
|
||||
) : AbstractMatrix3f<MutableMatrix3f>() {
|
||||
override fun createOrModify(
|
||||
m00: Float, m01: Float, m02: Float,
|
||||
m10: Float, m11: Float, m12: Float,
|
||||
m20: Float, m21: Float, m22: Float,
|
||||
): MutableMatrix3f {
|
||||
this.m00 = m00; this.m01 = m01; this.m02 = m02
|
||||
this.m10 = m10; this.m11 = m11; this.m12 = m12
|
||||
this.m20 = m20; this.m21 = m21; this.m22 = m22
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun set(row: Int, column: Int, value: Float) {
|
||||
when (column) {
|
||||
0 -> when (row) {
|
||||
0 -> m00 = value
|
||||
1 -> m10 = value
|
||||
2 -> m20 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
1 -> when (row) {
|
||||
0 -> m01 = value
|
||||
1 -> m11 = value
|
||||
2 -> m21 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
2 -> when (row) {
|
||||
0 -> m02 = value
|
||||
1 -> m12 = value
|
||||
2 -> m22 = value
|
||||
else -> throw IndexOutOfBoundsException("Row: $row")
|
||||
}
|
||||
|
||||
else -> throw IndexOutOfBoundsException("Column: $column")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package ru.dbotthepony.kstarbound.math
|
||||
|
||||
class Matrix4fStack {
|
||||
private val stack = ArrayDeque<MutableMatrix4f>()
|
||||
|
||||
init {
|
||||
stack.add(MutableMatrix4f())
|
||||
}
|
||||
|
||||
val last get() = stack.last()
|
||||
|
||||
fun push(matrix4f: MutableMatrix4f = last.copy()): Matrix4fStack {
|
||||
stack.add(matrix4f)
|
||||
return this
|
||||
}
|
||||
|
||||
fun pop(): Matrix4fStack {
|
||||
stack.removeLast()
|
||||
return this
|
||||
}
|
||||
|
||||
fun clear(matrix: MutableMatrix4f = MutableMatrix4f()): Matrix4fStack {
|
||||
stack.clear()
|
||||
stack.add(matrix)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun plus(other: FloatMatrix<*>): Matrix4fStack {
|
||||
last.plus(other)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun minus(other: FloatMatrix<*>): Matrix4fStack {
|
||||
last.minus(other)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun times(other: FloatMatrix<*>): Matrix4fStack {
|
||||
last.times(other)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun plus(other: Float): Matrix4fStack {
|
||||
last.plus(other)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun minus(other: Float): Matrix4fStack {
|
||||
last.minus(other)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun times(other: Float): Matrix4fStack {
|
||||
last.times(other)
|
||||
return this
|
||||
}
|
||||
|
||||
fun scale(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f): Matrix4fStack {
|
||||
last.scale(x, y, z, w)
|
||||
return this
|
||||
}
|
||||
|
||||
fun translate(x: Float = 0f, y: Float = 0f, z: Float = 0f): Matrix4fStack {
|
||||
last.translate(x, y, z)
|
||||
return this
|
||||
}
|
||||
|
||||
fun translateWithScale(x: Float = 0f, y: Float = 0f, z: Float = 0f): Matrix4fStack {
|
||||
last.translateWithScale(x, y, z)
|
||||
return this
|
||||
}
|
||||
|
||||
fun translate(vec: Vector3f): Matrix4fStack {
|
||||
last.translate(vec)
|
||||
return this
|
||||
}
|
||||
|
||||
fun translateWithScale(vec: Vector3f): Matrix4fStack {
|
||||
last.translateWithScale(vec)
|
||||
return this
|
||||
}
|
||||
|
||||
fun replace(matrix4f: MutableMatrix4f): Matrix4fStack {
|
||||
stack.removeLast()
|
||||
stack.add(matrix4f)
|
||||
return this
|
||||
}
|
||||
}
|
162
src/main/kotlin/ru/dbotthepony/kstarbound/math/Vector.kt
Normal file
162
src/main/kotlin/ru/dbotthepony/kstarbound/math/Vector.kt
Normal file
@ -0,0 +1,162 @@
|
||||
package ru.dbotthepony.kstarbound.math
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
data class Vector2i(val x: Int, val y: Int) : IMatrixLike, IMatrixLikeGetterI {
|
||||
operator fun plus(other: Vector2i) = Vector2i(x + other.x, y + other.y)
|
||||
operator fun plus(other: Int) = Vector2i(x + other, y + other)
|
||||
operator fun minus(other: Vector2i) = Vector2i(x - other.x, y - other.y)
|
||||
operator fun minus(other: Int) = Vector2i(x - other, y - other)
|
||||
operator fun times(other: Vector2i) = Vector2i(x * other.x, y * other.y)
|
||||
operator fun times(other: Int) = Vector2i(x * other, y * other)
|
||||
operator fun div(other: Vector2i) = Vector2i(x / other.x, y / other.y)
|
||||
operator fun div(other: Int) = Vector2i(x / other, y / other)
|
||||
|
||||
override val columns = 1
|
||||
override val rows = 2
|
||||
|
||||
override fun get(row: Int, column: Int): Int {
|
||||
if (column != 0) {
|
||||
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||
}
|
||||
|
||||
return when (row) {
|
||||
0 -> x
|
||||
1 -> y
|
||||
else -> throw IndexOutOfBoundsException("Row out of bounds: $row")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromJson(input: JsonArray): Vector2i {
|
||||
return Vector2i(input[0].asInt, input[1].asInt)
|
||||
}
|
||||
|
||||
val ZERO = Vector2i(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
data class Vector3f(val x: Float = 0f, val y: Float = 0f, val z: Float = 0f) : IMatrixLikeFloat {
|
||||
override val columns = 1
|
||||
override val rows = 3
|
||||
|
||||
override fun get(row: Int, column: Int): Float {
|
||||
if (column != 0) {
|
||||
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||
}
|
||||
|
||||
return when (row) {
|
||||
0 -> x
|
||||
1 -> y
|
||||
2 -> z
|
||||
else -> throw IndexOutOfBoundsException("Row out of bounds: $row")
|
||||
}
|
||||
}
|
||||
|
||||
fun rotateAroundThis(rotation: Double): Matrix4f {
|
||||
val c = cos(rotation).toFloat()
|
||||
val s = sin(rotation).toFloat()
|
||||
val cInv = 1f - c
|
||||
|
||||
return Matrix4f(
|
||||
m00 = c + x * x * cInv, m01 = x * y * cInv - z * s, m02 = x * z * cInv + y * s,
|
||||
m10 = y * x * cInv + z * s, m11 = c + y * y * cInv, m12 = y * z * cInv - x * s,
|
||||
m20 = z * x * cInv - y * s, m21 = z * y * cInv + x * s, m22 = c + z * z * cInv,
|
||||
)
|
||||
}
|
||||
|
||||
operator fun times(other: IMatrixLikeFloat): Vector3f {
|
||||
if (other.rows >= 4 && other.columns >= 4) {
|
||||
val x = this.x * other[0, 0] +
|
||||
this.y * other[0, 1] +
|
||||
this.z * other[0, 2] +
|
||||
other[0, 3]
|
||||
|
||||
val y = this.x * other[1, 0] +
|
||||
this.y * other[1, 1] +
|
||||
this.z * other[1, 2] +
|
||||
other[1, 3]
|
||||
|
||||
val z = this.x * other[2, 0] +
|
||||
this.y * other[2, 1] +
|
||||
this.z * other[2, 2] +
|
||||
other[2, 3]
|
||||
|
||||
return Vector3f(x, y, z)
|
||||
} else if (other.rows >= 3 && other.columns >= 3) {
|
||||
val x = this.x * other[0, 0] +
|
||||
this.y * other[0, 1] +
|
||||
this.z * other[0, 2]
|
||||
|
||||
val y = this.x * other[1, 0] +
|
||||
this.y * other[1, 1] +
|
||||
this.z * other[1, 2]
|
||||
|
||||
val z = this.x * other[2, 0] +
|
||||
this.y * other[2, 1] +
|
||||
this.z * other[2, 2]
|
||||
|
||||
return Vector3f(x, y, z)
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Incompatible matrix provided: ${other.rows} x ${other.columns}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UP = Vector3f(0f, 1f, 0f)
|
||||
val DOWN = Vector3f(0f, -1f, 0f)
|
||||
val LEFT = Vector3f(-1f, 0f, 0f)
|
||||
val RIGHT = Vector3f(1f, 0f, 0f)
|
||||
val FORWARD = Vector3f(0f, 0f, 1f)
|
||||
val BACKWARD = Vector3f(0f, 0f, -1f)
|
||||
}
|
||||
}
|
||||
|
||||
data class Vector4f(val x: Float, val y: Float, val z: Float, val w: Float) : IMatrixLikeFloat {
|
||||
override val columns = 1
|
||||
override val rows = 4
|
||||
|
||||
override fun get(row: Int, column: Int): Float {
|
||||
if (column != 0) {
|
||||
throw IndexOutOfBoundsException("Column must be 0 ($column given)")
|
||||
}
|
||||
|
||||
return when (row) {
|
||||
0 -> x
|
||||
1 -> y
|
||||
2 -> z
|
||||
3 -> w
|
||||
else -> throw IndexOutOfBoundsException("Row out of bounds: $row")
|
||||
}
|
||||
}
|
||||
|
||||
operator fun times(other: IMatrixLikeFloat): Vector4f {
|
||||
if (other.rows >= 4 && other.columns >= 4) {
|
||||
val x = this.x * other[0, 0] +
|
||||
this.y * other[0, 1] +
|
||||
this.z * other[0, 2] +
|
||||
this.w * other[0, 3]
|
||||
|
||||
val y = this.x * other[1, 0] +
|
||||
this.y * other[1, 1] +
|
||||
this.z * other[1, 2] +
|
||||
this.w * other[1, 3]
|
||||
|
||||
val z = this.x * other[2, 0] +
|
||||
this.y * other[2, 1] +
|
||||
this.z * other[2, 2] +
|
||||
this.w * other[2, 3]
|
||||
|
||||
val w = this.x * other[3, 0] +
|
||||
this.y * other[3, 1] +
|
||||
this.z * other[3, 2] +
|
||||
this.w * other[3, 3]
|
||||
|
||||
return Vector4f(x, y, z, w)
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Incompatible matrix provided: ${other.rows} x ${other.columns}")
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package ru.dbotthepony.kstarbound.render
|
||||
|
||||
import org.lwjgl.opengl.GL11
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.gl.*
|
||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||
|
||||
/**
|
||||
* Служит для быстрой настройки состояния для будущей отрисовки
|
||||
*
|
||||
* Установка текстурных блоков, программы, самих текстур и загрузка юниформ должна осуществляться тут
|
||||
*
|
||||
* Класс обязан быть наследован для осмысленных результатов
|
||||
*
|
||||
* Ожидается, что состояние будет выставлено ПОЛНОСТЬЮ, т.е. НИКАКОЙ предыдущий код НЕ МОЖЕТ повлиять на результат выполнения
|
||||
* шейдерной программы, которая связанна с этим объектом (за исключением не вызова [setTransform] внешним кодом)
|
||||
*/
|
||||
open class BakedProgramState(
|
||||
val program: GLShaderProgram,
|
||||
) {
|
||||
private val transformLocation = program["_transform"]
|
||||
|
||||
open fun setTransform(value: FloatMatrix<*>) {
|
||||
transformLocation?.set(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается перед началом отрисовки
|
||||
*/
|
||||
open fun setup() {
|
||||
program.use()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запечённый статичный меш, позволяет быстро отрисовать меш со всеми параметрами
|
||||
* с заданной матрицей трансформации
|
||||
*/
|
||||
class BakedStaticMesh(
|
||||
val programState: BakedProgramState,
|
||||
val indexCount: Int,
|
||||
val vao: GLVertexArrayObject,
|
||||
) : AutoCloseable {
|
||||
private var onClose = {}
|
||||
constructor(programState: BakedProgramState, builder: VertexBuilder) : this(
|
||||
programState,
|
||||
builder.indexCount,
|
||||
programState.program.state.newVAO(),
|
||||
) {
|
||||
val vbo = programState.program.state.newVBO()
|
||||
val ebo = programState.program.state.newEBO()
|
||||
|
||||
onClose = {
|
||||
vbo.close()
|
||||
ebo.close()
|
||||
}
|
||||
|
||||
vao.bind()
|
||||
vbo.bind()
|
||||
ebo.bind()
|
||||
|
||||
builder.upload(vbo, ebo, GL_STATIC_DRAW)
|
||||
builder.attributes.apply(vao, true)
|
||||
|
||||
vao.unbind()
|
||||
vbo.unbind()
|
||||
ebo.unbind()
|
||||
}
|
||||
|
||||
fun render(transform: FloatMatrix<*>? = null) {
|
||||
check(isValid) { "$this is no longer valid" }
|
||||
programState.setup()
|
||||
|
||||
if (transform != null) {
|
||||
programState.setTransform(transform)
|
||||
}
|
||||
|
||||
vao.bind()
|
||||
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0L)
|
||||
checkForGLError()
|
||||
}
|
||||
|
||||
var isValid = true
|
||||
private set
|
||||
|
||||
override fun close() {
|
||||
vao.close()
|
||||
onClose.invoke()
|
||||
isValid = false
|
||||
}
|
||||
}
|
19
src/main/kotlin/ru/dbotthepony/kstarbound/render/Camera.kt
Normal file
19
src/main/kotlin/ru/dbotthepony/kstarbound/render/Camera.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package ru.dbotthepony.kstarbound.render
|
||||
|
||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||
import ru.dbotthepony.kstarbound.math.Vector3f
|
||||
|
||||
class Camera {
|
||||
var pos = Vector3f(z = 400f)
|
||||
var zoom = 1f
|
||||
|
||||
fun matrix(): Matrix4f {
|
||||
return Matrix4f.IDENTITY.scale(zoom, zoom, zoom).translate(pos)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_ZOOM = 4f
|
||||
const val MIN_ZOOM = 0.1f
|
||||
const val ZOOM_STEP = 0.1f
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package ru.dbotthepony.kstarbound.render
|
||||
|
||||
import ru.dbotthepony.kstarbound.gl.*
|
||||
import ru.dbotthepony.kstarbound.math.FloatMatrix
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class ChunkRenderer(val state: GLStateTracker, val chunk: Chunk) : AutoCloseable {
|
||||
private val builders = HashMap<BakedProgramState, VertexBuilder>()
|
||||
private val bakedMeshes = ArrayList<BakedStaticMesh>()
|
||||
private val unloadableBakedMeshes = ArrayList<BakedStaticMesh>()
|
||||
|
||||
/**
|
||||
* Тесселирует "статичную" геометрию в builders (к примеру тайлы).
|
||||
*
|
||||
* Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке)
|
||||
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
|
||||
*/
|
||||
fun tesselateStatic() {
|
||||
if (state.isSameThread()) {
|
||||
for (mesh in bakedMeshes) {
|
||||
mesh.close()
|
||||
}
|
||||
|
||||
bakedMeshes.clear()
|
||||
} else {
|
||||
unloadableBakedMeshes.addAll(bakedMeshes)
|
||||
bakedMeshes.clear()
|
||||
}
|
||||
|
||||
builders.clear()
|
||||
|
||||
// TODO: Синхронизация (ибо обновления игровой логики будут в потоке вне рендер потока)
|
||||
for ((pos, tile) in chunk.posToTile) {
|
||||
if (tile != null) {
|
||||
val renderer = state.tileRenderers.get(tile.def.materialName)
|
||||
renderer.tesselate(chunk, builders, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее)
|
||||
*
|
||||
* Вызывается перед tesselateStatic()
|
||||
*/
|
||||
fun loadRenderers() {
|
||||
unloadUnused()
|
||||
|
||||
// TODO: Синхронизация (ибо обновления игровой логики будут в потоке вне рендер потока)
|
||||
for ((pos, tile) in chunk.posToTile) {
|
||||
if (tile != null) {
|
||||
state.tileRenderers.get(tile.def.materialName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unloadUnused() {
|
||||
if (unloadableBakedMeshes.size != 0) {
|
||||
for (baked in unloadableBakedMeshes) {
|
||||
baked.close()
|
||||
}
|
||||
|
||||
unloadableBakedMeshes.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadStatic() {
|
||||
unloadUnused()
|
||||
|
||||
for ((baked, builder) in builders) {
|
||||
bakedMeshes.add(BakedStaticMesh(baked, builder))
|
||||
}
|
||||
}
|
||||
|
||||
fun render(transform: FloatMatrix<*> = state.matrixStack.last) {
|
||||
unloadUnused()
|
||||
|
||||
for (mesh in bakedMeshes) {
|
||||
mesh.render(transform)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
for (mesh in bakedMeshes) {
|
||||
mesh.close()
|
||||
}
|
||||
|
||||
for (mesh in unloadableBakedMeshes) {
|
||||
mesh.close()
|
||||
}
|
||||
}
|
||||
}
|
244
src/main/kotlin/ru/dbotthepony/kstarbound/render/TileRenderer.kt
Normal file
244
src/main/kotlin/ru/dbotthepony/kstarbound/render/TileRenderer.kt
Normal file
@ -0,0 +1,244 @@
|
||||
package ru.dbotthepony.kstarbound.render
|
||||
|
||||
import org.lwjgl.glfw.GLFW.glfwGetTime
|
||||
import org.lwjgl.opengl.GL46.*
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.TileRenderMatchPiece
|
||||
import ru.dbotthepony.kstarbound.defs.TileRenderMatchedPiece
|
||||
import ru.dbotthepony.kstarbound.defs.TileRenderPiece
|
||||
import ru.dbotthepony.kstarbound.gl.*
|
||||
import ru.dbotthepony.kstarbound.math.Matrix4f
|
||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.IChunk
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class TileRenderers(val state: GLStateTracker) {
|
||||
private val simpleBakedPrograms = HashMap<GLTexture2D, SimpleBakedProgram>()
|
||||
private val tileRenderers = HashMap<String, TileRenderer>()
|
||||
|
||||
operator fun get(tile: String): TileRenderer {
|
||||
return tileRenderers.computeIfAbsent(tile) {
|
||||
return@computeIfAbsent TileRenderer(state, Starbound.loadTileDefinition(tile))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class SimpleBakedProgram(private val texture: GLTexture2D) : BakedProgramState(state.shaderVertexTexture) {
|
||||
override fun setup() {
|
||||
super.setup()
|
||||
state.activeTexture = 0
|
||||
program["_texture"] = 0
|
||||
texture.bind()
|
||||
texture.textureMagFilter = GL_NEAREST
|
||||
texture.textureMinFilter = GL_NEAREST
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (other is SimpleBakedProgram) {
|
||||
return texture == other.texture
|
||||
}
|
||||
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return texture.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает запечённое состояние shaderVertexTexture с данной текстурой
|
||||
*/
|
||||
fun simpleProgram(texture: GLTexture2D): BakedProgramState {
|
||||
return simpleBakedPrograms.computeIfAbsent(texture, ::SimpleBakedProgram)
|
||||
}
|
||||
}
|
||||
|
||||
class TileRenderer(val state: GLStateTracker, val tile: TileDefinition) {
|
||||
val texture = state.loadNamedTexture(tile.render.texture).also {
|
||||
it.textureMagFilter = GL_NEAREST
|
||||
}
|
||||
|
||||
val bakedProgramState = state.tileRenderers.simpleProgram(texture)
|
||||
|
||||
private fun tesselateAt(piece: TileRenderPiece, getter: IChunk, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO) {
|
||||
val fx = pos.x.toFloat()
|
||||
val fy = pos.y.toFloat()
|
||||
|
||||
var a = fx
|
||||
var b = fy
|
||||
|
||||
var c = fx + piece.textureSize.x / BASELINE_TEXTURE_SIZE
|
||||
var d = fy + piece.textureSize.y / BASELINE_TEXTURE_SIZE
|
||||
|
||||
if (offset != Vector2i.ZERO) {
|
||||
a += offset.x / BASELINE_TEXTURE_SIZE
|
||||
|
||||
// в json файлах y указан как положительный вверх
|
||||
b += offset.y / BASELINE_TEXTURE_SIZE
|
||||
|
||||
c += offset.x / BASELINE_TEXTURE_SIZE
|
||||
d += offset.y / BASELINE_TEXTURE_SIZE
|
||||
}
|
||||
|
||||
if (tile.render.variants == 0 || piece.texture != null || piece.variantStride == null) {
|
||||
val (u0, v0) = texture.pixelToUV(piece.texturePosition)
|
||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
||||
|
||||
builder.quadZ(a, b, c, d, 1f, VertexTransformers.uv(u0, v1, u1, v0))
|
||||
} else {
|
||||
val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt()
|
||||
|
||||
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride * variant)
|
||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
|
||||
|
||||
builder.quadZ(a, b, c, d, 1f, VertexTransformers.uv(u0, v1, u1, v0))
|
||||
}
|
||||
}
|
||||
|
||||
private fun tesselatePiece(matchPiece: TileRenderMatchPiece, getter: IChunk, builders: MutableMap<BakedProgramState, VertexBuilder>, pos: Vector2i, thisBuilder: VertexBuilder) {
|
||||
if (matchPiece.test(getter, tile, pos)) {
|
||||
for (renderPiece in matchPiece.pieces) {
|
||||
if (renderPiece.piece.texture != null) {
|
||||
tesselateAt(renderPiece.piece, getter, builders.computeIfAbsent(state.tileRenderers.simpleProgram(state.loadNamedTexture(renderPiece.piece.texture))) {
|
||||
return@computeIfAbsent VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
|
||||
}, pos, renderPiece.offset)
|
||||
} else {
|
||||
tesselateAt(renderPiece.piece, getter, thisBuilder, pos, renderPiece.offset)
|
||||
}
|
||||
}
|
||||
|
||||
for (subPiece in matchPiece.subMatches) {
|
||||
tesselatePiece(subPiece, getter, builders, pos, thisBuilder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Тесселирует тайлы в заданной позиции в нужном билдере
|
||||
*
|
||||
* [getter] Нужен для получения информации о ближайших блоках
|
||||
*
|
||||
* [builders] содержит текущие программы и их билдеры
|
||||
*
|
||||
* Тесселирует тайлы в границы -1f .. CHUNK_SIZEf + 1f на основе [pos]
|
||||
*/
|
||||
fun tesselate(getter: IChunk, builders: MutableMap<BakedProgramState, VertexBuilder>, pos: Vector2i) {
|
||||
// если у нас нет renderTemplate
|
||||
// то мы просто не можем его отрисовать
|
||||
tile.render.renderTemplate ?: return
|
||||
|
||||
val builder = builders.computeIfAbsent(bakedProgramState) {
|
||||
return@computeIfAbsent VertexBuilder(GLFlatAttributeList.VERTEX_TEXTURE, VertexType.QUADS)
|
||||
}
|
||||
|
||||
for ((_, matcher) in tile.render.renderTemplate.matches) {
|
||||
for (matchPiece in matcher.pieces) {
|
||||
tesselatePiece(matchPiece, getter, builders, pos, builder)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
val fx = pos.x.toFloat()
|
||||
val fy = pos.y.toFloat()
|
||||
|
||||
val a = fx
|
||||
val b = fy
|
||||
|
||||
val c = fx + 1f
|
||||
val d = fy + 1f
|
||||
|
||||
val piece = tile.render.renderTemplate.pieces[tile.render.renderTemplate.representativePiece]!!
|
||||
|
||||
if (tile.render.variants == 0) {
|
||||
val (u0, v0) = texture.pixelToUV(piece.texturePosition)
|
||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize)
|
||||
|
||||
builder.quadZ(b, a, d, c, 1f, VertexTransformers.uv(u0, v0, u1, v1))
|
||||
} else {
|
||||
val variant = (getter.randomDoubleFor(pos) * tile.render.variants).toInt()
|
||||
|
||||
val (u0, v0) = texture.pixelToUV(piece.texturePosition + piece.variantStride!! * variant)
|
||||
val (u1, v1) = texture.pixelToUV(piece.texturePosition + piece.textureSize + piece.variantStride * variant)
|
||||
|
||||
builder.quadZ(b, a, d, c, 1f, VertexTransformers.uv(u0, v0, u1, v1))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fun renderPiece() {
|
||||
val vao = state.newVAO()
|
||||
val vbo = state.newVBO()
|
||||
val ebo = state.newEBO()
|
||||
|
||||
vao.bind()
|
||||
vbo.bind()
|
||||
|
||||
val base = tile.render.renderTemplate!!.pieces["base"]!!
|
||||
|
||||
val builder = GLFlatAttributeListBuilder.VERTEX_TEXTURE.vertexBuilder(VertexType.QUADS)
|
||||
|
||||
run {
|
||||
val pos1 = base.texturePosition + base.variantStride!! * (glfwGetTime() % 15).toInt()
|
||||
val pos2 = base.texturePosition + base.textureSize + base.variantStride * (glfwGetTime() % 15).toInt()
|
||||
|
||||
val (u0, v0) = texture.pixelToUV(pos1)
|
||||
val (u1, v1) = texture.pixelToUV(pos2)
|
||||
|
||||
builder.quadZ(-1f, -1f, 1f, 1f, 0f, VertexTransformers.uv(u0, v0, u1, v1))
|
||||
}
|
||||
|
||||
run {
|
||||
val pos1 = base.texturePosition + base.variantStride!! * ((glfwGetTime() + 1) % 15).toInt()
|
||||
val pos2 = base.texturePosition + base.textureSize + base.variantStride * ((glfwGetTime() + 1) % 15).toInt()
|
||||
|
||||
val (u0, v0) = texture.pixelToUV(pos1)
|
||||
val (u1, v1) = texture.pixelToUV(pos2)
|
||||
|
||||
builder.quadZ(-3f, -1f, -1f, 1f, 0f, VertexTransformers.uv(u0, v0, u1, v1))
|
||||
}
|
||||
|
||||
run {
|
||||
val pos1 = base.texturePosition + base.variantStride!! * ((glfwGetTime() + 2) % 15).toInt()
|
||||
val pos2 = base.texturePosition + base.textureSize + base.variantStride * ((glfwGetTime() + 2) % 15).toInt()
|
||||
|
||||
val (u0, v0) = texture.pixelToUV(pos1)
|
||||
val (u1, v1) = texture.pixelToUV(pos2)
|
||||
|
||||
builder.quadZ(3f, -1f, 1f, 1f, 0f, VertexTransformers.uv(u0, v0, u1, v1))
|
||||
}
|
||||
|
||||
builder.upload(vbo, ebo, GL_STREAM_DRAW)
|
||||
GLFlatAttributeListBuilder.VERTEX_TEXTURE.apply(vao, enable = true)
|
||||
|
||||
state.shaderVertexTexture.use()
|
||||
|
||||
state.activeTexture = 0
|
||||
texture.bind()
|
||||
|
||||
state.shaderVertexTexture["_texture"] = 0
|
||||
state.shaderVertexTexture["_transform"] = Matrix4f.IDENTITY.scale(0.5f, 0.5f)
|
||||
|
||||
texture.textureMagFilter = GL_NEAREST
|
||||
texture.textureMinFilter = GL_NEAREST_MIPMAP_NEAREST
|
||||
|
||||
vbo.bind()
|
||||
ebo.bind()
|
||||
glDrawElements(GL_TRIANGLES, builder.indexCount, GL_UNSIGNED_INT, 0L)
|
||||
checkForGLError()
|
||||
|
||||
vao.close()
|
||||
vbo.close()
|
||||
ebo.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BASELINE_TEXTURE_SIZE = 8f
|
||||
}
|
||||
}
|
11
src/main/kotlin/ru/dbotthepony/kstarbound/util/Color.kt
Normal file
11
src/main/kotlin/ru/dbotthepony/kstarbound/util/Color.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
|
||||
data class Color(val red: Float, val green: Float, val blue: Float, val alpha: Float = 1f) {
|
||||
constructor(input: JsonArray) : this(input[0].asFloat / 255f, input[1].asFloat / 255f, input[2].asFloat / 255f, if (input.size() >= 4) input[3].asFloat / 255f else 1f)
|
||||
|
||||
companion object {
|
||||
val WHITE = Color(1f, 1f, 1f)
|
||||
}
|
||||
}
|
161
src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt
Normal file
161
src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt
Normal file
@ -0,0 +1,161 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||
|
||||
data class ChunkTile(val def: TileDefinition) {
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
interface IChunk {
|
||||
val pos: ChunkPos
|
||||
|
||||
/**
|
||||
* Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
|
||||
*/
|
||||
operator fun get(x: Int, y: Int): ChunkTile?
|
||||
operator fun get(pos: Vector2i) = get(pos.x, pos.y)
|
||||
|
||||
/**
|
||||
* Относительная проверка находится ли координата вне границ чагка
|
||||
*/
|
||||
fun isOutside(x: Int, y: Int): Boolean {
|
||||
return x !in 0 until CHUNK_SIZE || y !in 0 until CHUNK_SIZE
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает псевдослучайное Long для заданной позиции
|
||||
*
|
||||
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
||||
*/
|
||||
fun randomLongFor(x: Int, y: Int): Long {
|
||||
var long = x * 738548L + y * 2191293543L
|
||||
long = long xor 8339437585692L
|
||||
long = (long ushr 4) or (long shl 52)
|
||||
long *= 7848344324L
|
||||
long = (long ushr 12) or (long shl 44)
|
||||
return long
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Возвращает псевдослучайное нормализированное Double для заданной позиции
|
||||
*
|
||||
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
||||
*/
|
||||
fun randomDoubleFor(x: Int, y: Int): Double {
|
||||
return (randomLongFor(x, y) / 9.223372036854776E18) / 2.0 + 0.5
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Возвращает псевдослучайное Long для заданной позиции
|
||||
*
|
||||
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
||||
*/
|
||||
fun randomLongFor(pos: Vector2i) = randomLongFor(pos.x, pos.y)
|
||||
|
||||
/**
|
||||
* Возвращает псевдослучайное нормализированное Double для заданной позиции
|
||||
*
|
||||
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
||||
*/
|
||||
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
|
||||
|
||||
/**
|
||||
* Возвращает итератор пар <Vector2i, Тайл?>
|
||||
*
|
||||
* Вектор имеет ОТНОСИТЕЛЬНЫЕ значения внутри самого чанка
|
||||
*/
|
||||
val posToTile: Iterator<Pair<Vector2i, ChunkTile?>> get() {
|
||||
return object : Iterator<Pair<Vector2i, ChunkTile?>> {
|
||||
private var x = 0
|
||||
private var y = 0
|
||||
|
||||
private fun idx() = x + CHUNK_SIZE * y
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return idx() < CHUNK_SIZE * CHUNK_SIZE
|
||||
}
|
||||
|
||||
override fun next(): Pair<Vector2i, ChunkTile?> {
|
||||
if (!hasNext()) {
|
||||
throw IllegalStateException("Already iterated everything!")
|
||||
}
|
||||
|
||||
val tile = this@IChunk[x, y]
|
||||
val pos = Vector2i(x, y)
|
||||
|
||||
x++
|
||||
|
||||
if (x >= CHUNK_SIZE) {
|
||||
y++
|
||||
x = 0
|
||||
}
|
||||
|
||||
return pos to tile
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IChunkSetter {
|
||||
/**
|
||||
* Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
|
||||
*/
|
||||
operator fun set(x: Int, y: Int, tile: ChunkTile?)
|
||||
}
|
||||
|
||||
interface IMutableChunk : IChunk, IChunkSetter
|
||||
|
||||
const val CHUNK_SHIFT = 6
|
||||
const val CHUNK_SIZE = 1 shl CHUNK_SHIFT // 64
|
||||
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
||||
|
||||
data class ChunkPos(val x: Int, val y: Int) {
|
||||
constructor(pos: Vector2i) : this(pos.x shr CHUNK_SHIFT, pos.y shr CHUNK_SHIFT)
|
||||
|
||||
val firstBlock get() = Vector2i(x shl CHUNK_SHIFT, y shl CHUNK_SHIFT)
|
||||
val lastBlock get() = Vector2i(((x + 1) shl CHUNK_SHIFT) - 1, ((y + 1) shl CHUNK_SHIFT) - 1)
|
||||
}
|
||||
|
||||
class Chunk(val world: World, override val pos: ChunkPos) : IMutableChunk {
|
||||
/**
|
||||
* Хранит тайлы как x + y * CHUNK_SIZE
|
||||
*/
|
||||
val tiles = arrayOfNulls<ChunkTile>(CHUNK_SIZE * CHUNK_SIZE)
|
||||
|
||||
override operator fun get(x: Int, y: Int): ChunkTile? {
|
||||
if (isOutside(x, y))
|
||||
return null
|
||||
|
||||
return tiles[x or (y shl CHUNK_SHIFT)]
|
||||
}
|
||||
|
||||
override operator fun set(x: Int, y: Int, tile: ChunkTile?) {
|
||||
if (isOutside(x, y))
|
||||
throw IndexOutOfBoundsException("Trying to set tile ${tile?.def?.materialName} at $x $y, but that is outside of chunk's range")
|
||||
|
||||
tiles[x or (y shl CHUNK_SHIFT)] = tile
|
||||
}
|
||||
|
||||
override fun randomLongFor(x: Int, y: Int): Long {
|
||||
return super.randomLongFor(x, y) xor world.seed
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = object : IMutableChunk {
|
||||
override val pos = ChunkPos(0, 0)
|
||||
|
||||
override fun get(x: Int, y: Int): ChunkTile? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun set(x: Int, y: Int, tile: ChunkTile?) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt
Normal file
43
src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.math.Vector2i
|
||||
|
||||
class World(val seed: Long = 0L) {
|
||||
val chunkMap = ArrayList<Pair<ChunkPos, Chunk>>()
|
||||
|
||||
fun getChunk(pos: ChunkPos): Chunk? {
|
||||
for ((k, v) in chunkMap) {
|
||||
if (k == pos) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getChunk(pos: Vector2i) = getChunk(ChunkPos(pos))
|
||||
|
||||
fun getOrMakeChunk(pos: ChunkPos): Chunk {
|
||||
for ((k, v) in chunkMap) {
|
||||
if (k == pos) {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
val chunk = Chunk(this, pos)
|
||||
chunkMap.add(pos to chunk)
|
||||
return chunk
|
||||
}
|
||||
|
||||
fun getOrMakeChunk(pos: Vector2i) = getOrMakeChunk(ChunkPos(pos))
|
||||
|
||||
fun getTile(pos: Vector2i): ChunkTile? {
|
||||
return getChunk(pos)?.get(pos.x, pos.y)
|
||||
}
|
||||
|
||||
fun setTile(pos: Vector2i, tile: ChunkTile?): Chunk {
|
||||
val chunk = getOrMakeChunk(pos)
|
||||
chunk[pos.x and CHUNK_SIZE_FF, pos.y and CHUNK_SIZE_FF] = tile
|
||||
return chunk
|
||||
}
|
||||
}
|
13
src/main/resources/log4j2.xml
Normal file
13
src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level [%t] - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
13
src/main/resources/shaders/f_texture.glsl
Normal file
13
src/main/resources/shaders/f_texture.glsl
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
#version 460
|
||||
|
||||
uniform sampler2D _texture;
|
||||
|
||||
in vec2 _uv_out;
|
||||
|
||||
out vec4 _color_out;
|
||||
|
||||
void main() {
|
||||
_color_out = texture(_texture, _uv_out);
|
||||
//_color_out = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
}
|
13
src/main/resources/shaders/tile_fragment.glsl
Normal file
13
src/main/resources/shaders/tile_fragment.glsl
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
#version 460
|
||||
|
||||
out vec4 FragColor;
|
||||
in vec3 ourColor;
|
||||
in vec2 TexCoord;
|
||||
|
||||
uniform vec3 globalColor;
|
||||
uniform sampler2D ourTexture;
|
||||
|
||||
void main() {
|
||||
FragColor = texture(ourTexture, TexCoord);
|
||||
}
|
14
src/main/resources/shaders/tile_vertex.glsl
Normal file
14
src/main/resources/shaders/tile_vertex.glsl
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aColor;
|
||||
layout (location = 2) in vec2 aTexCoord;
|
||||
out vec3 ourColor;
|
||||
out vec2 TexCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 1.0);
|
||||
ourColor = aColor;
|
||||
TexCoord = aTexCoord;
|
||||
}
|
14
src/main/resources/shaders/v_vertex_texture.glsl
Normal file
14
src/main/resources/shaders/v_vertex_texture.glsl
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
#version 460
|
||||
|
||||
layout (location = 0) in vec3 _pos;
|
||||
layout (location = 1) in vec2 _uv_in;
|
||||
|
||||
out vec2 _uv_out;
|
||||
|
||||
uniform mat4 _transform;
|
||||
|
||||
void main() {
|
||||
_uv_out = _uv_in;
|
||||
gl_Position = _transform * vec4(_pos, 1.0);
|
||||
}
|
31
src/test/kotlin/ru/dbotthepony/kstarbound/test/MatrixTest.kt
Normal file
31
src/test/kotlin/ru/dbotthepony/kstarbound/test/MatrixTest.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package ru.dbotthepony.kstarbound.test
|
||||
|
||||
import jdk.jfr.Description
|
||||
import org.junit.jupiter.api.Test
|
||||
import ru.dbotthepony.kstarbound.math.Matrix3f
|
||||
|
||||
object MatrixTest {
|
||||
@Test
|
||||
@Description("Matrix test")
|
||||
fun test() {
|
||||
val a = Matrix3f(
|
||||
4f, 2f, 0f,
|
||||
0f, 8f, 1f,
|
||||
0f, 1f, 0f
|
||||
)
|
||||
|
||||
val b = Matrix3f(
|
||||
4f, 2f, 1f,
|
||||
2f, 0f, 4f,
|
||||
9f, 4f, 2f
|
||||
)
|
||||
|
||||
val c = Matrix3f(
|
||||
20f, 8f, 12f,
|
||||
25f, 4f, 34f,
|
||||
2f, 0f, 4f
|
||||
)
|
||||
|
||||
require(a * b == c) { "${a * b} != $c" }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user