diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 739de1f0..5dfbba5e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,6 +39,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.gitmodules b/.gitmodules index 37e5cd06..a8ec31b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "ferricia"] path = ferricia url = https://github.com/bitsusei/TerraModulus-Ferricia-Engine +[submodule "vector-math"] + path = vector-math + url = https://github.com/bitsusei/kotlin-vector-math diff --git a/build.gradle.kts b/build.gradle.kts index 8541b9db..56fcc34f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,21 +5,56 @@ plugins { kotlin("jvm") version "2.3.21" kotlin("plugin.serialization") version "2.1.20" id("org.jetbrains.kotlinx.atomicfu") version "0.27.0" + id("io.github.arc-blroth.cargo-wrapper") version "1.0.0" apply false +// id("fr.stardustenterprises.rust.wrapper") version "3.2.4" apply false application } -allprojects { - apply(plugin = "org.jetbrains.kotlin.jvm") +version = "0.0.1" - version = "0.0.1" +repositories { + mavenCentral() +} + +project(":ferricia") { + // Candidates: fr.stardustenterprises.rust.wrapper + apply(plugin = "io.github.arc-blroth.cargo-wrapper") - repositories { - mavenCentral() + if (providers.gradleProperty("release").isPresent) configure { + profile = "release" // use `-Prelease=true` + } + // somehow, .cargo extension is unusable + configure { + outputs = mapOf("" to System.mapLibraryName("ferricia")) + } + configurations { + create("client") { + configure { + arguments = listOf("-F", "client") + } + } + create("server") { + configure { + arguments = listOf("-F", "server") + } + } + } + artifacts { + add("client", tasks.named("build")) + add("server", tasks.named("build")) } } configure(listOf(project(":kernel"), project(":internal"))) { configure(listOf(project("common"), project("client"), project("server"))) { + apply(plugin = "org.jetbrains.kotlin.jvm") + + version = rootProject.version + + repositories { + mavenCentral() + } + sourceSets.main { kotlin.srcDir("kotlin") resources.srcDir("resources") @@ -61,6 +96,23 @@ project(":kernel") { } } +project(":kernel:client") { + dependencies { + implementation(project(":ferricia", "client")) + } +} +project(":kernel:server") { + dependencies { + implementation(project(":ferricia", "server")) + } +} + +configure(listOf(project(":internal:common"), project(":kernel:common"))) { + dependencies { + api("com.cout970:kotlin-vector-math:0.1.0") + } +} + project(":kernel:common") { dependencies { api("org.jetbrains:annotations:26.1.0") @@ -112,28 +164,6 @@ configure(listOf(project(":kernel:server"), project(":kernel:client"))) { } } -/** Build Ferricia Engine with Cargo */ -tasks.register("cargoBuildClient") { - onlyIf { - !gradle.taskGraph.hasTask(":kernel:server:jar") - } - workingDir = rootProject.file("ferricia") - commandLine("cargo", "build") - if (project.hasProperty("release")) args("--release") // use `-Prelease=true` - args("-F", "client") -} -tasks.register("cargoBuildServer") { - onlyIf { - !gradle.taskGraph.hasTask(":kernel:client:jar") - } - workingDir = rootProject.file("ferricia") - commandLine("cargo", "build") - if (project.hasProperty("release")) args("--release") // use `-Prelease=true` - args("-F", "server") -} -project(":kernel:client").tasks.named("jar") { dependsOn(tasks.named("cargoBuildClient")) } -project(":kernel:server").tasks.named("jar") { dependsOn(tasks.named("cargoBuildServer")) } - tasks.register("buildClient") { group = "build" description = "Build client" @@ -151,17 +181,13 @@ tasks.named("run") { tasks.register("runClient") { group = "application" description = "Run client" - dependsOn("cargoBuildClient") dependsOn(":kernel:client:run") } -project(":kernel:client").tasks.named("run").get().mustRunAfter(tasks.named("cargoBuildClient")) tasks.register("runServer") { group = "application" description = "Run server" - dependsOn("cargoBuildServer") dependsOn(":kernel:server:run") } -project(":kernel:server").tasks.named("run").get().mustRunAfter(tasks.named("cargoBuildServer")) configure(listOf(project(":kernel:server"), project(":kernel:client"))) { distributions { @@ -170,15 +196,12 @@ configure(listOf(project(":kernel:server"), project(":kernel:client"))) { duplicatesStrategy = DuplicatesStrategy.EXCLUDE into("lib") { val dir = if (project.hasProperty("release")) "release" else "debug" + from("$rootDir/ferricia/target/$dir/${System.mapLibraryName("ferricia")}") if (OperatingSystem.current().isWindows) from( - "$rootDir/ferricia/target/$dir/ferricia.dll", "$rootDir/ferricia/target/$dir/oded.dll", "$rootDir/ferricia/target/$dir/OpenAL32.dll", "$rootDir/ferricia/target/$dir/SDL3.dll", - ) else { // suppose UNIX - // other libs should be installed on user's end directly - from("$rootDir/ferricia/target/$dir/libferricia.so") - } + ) // for UNIX, other libs should have been installed on user's end directly } } } diff --git a/ferricia b/ferricia index 66bbf397..45d00517 160000 --- a/ferricia +++ b/ferricia @@ -1 +1 @@ -Subproject commit 66bbf397fc38663b797a63f31493149602223434 +Subproject commit 45d005177d84f14300c25a015148b6480336a31d diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b95..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a793..aaaabb3c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3b..23d15a93 100644 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -205,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a218..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle.kts b/settings.gradle.kts index a487543e..e2221f71 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,3 +16,11 @@ rootProject.children.forEach { it.projectDir = File(settingsDir, "src/${it.name}") include("${it.name}:common", "${it.name}:client", "${it.name}:server") } + +include("ferricia") // this is Rust + +includeBuild("vector-math") { + dependencySubstitution { + substitute(module("com.cout970:kotlin-vector-math")).using(project(":")) + } +} diff --git a/src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt b/src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt index a838815b..4bdbcf25 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Canvas.kt @@ -32,7 +32,7 @@ class Canvas internal constructor(private val windowHandle: ULong) : Closeable { fun setClearColor(r: Float, g: Float, b: Float, a: Float) = setCanvasClearColor(r, g, b, a) - fun resizeGLViewport() = if (camera3D == null) { + internal fun resizeGLViewport() = if (camera3D == null) { Mui.resizeGLViewport(windowHandle, handle) } else { Mui.resizeGLViewportCamera(windowHandle, handle, camera3D!!.handle) diff --git a/src/internal/client/kotlin/net/terramodulus/engine/Containers.kt b/src/internal/client/kotlin/net/terramodulus/engine/Containers.kt index 69913a49..03d6cacf 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/Containers.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Containers.kt @@ -5,10 +5,6 @@ package net.terramodulus.engine -data class Rgba(val r: Int, val g: Int, val b: Int, val a: Int) { - fun toArray() = intArrayOf(r, g, b, a) -} +import com.cout970.math.vec4.Vec4i -data class Vec3F(val x: Float, val y: Float, val z: Float) { - fun toArray() = floatArrayOf(x, y, z) -} +fun Vec4i.toArray() = intArrayOf(x, y, z, w) diff --git a/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt b/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt index 556db221..68a9d2b1 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt @@ -5,8 +5,13 @@ package net.terramodulus.engine +import com.cout970.math.vec2.MutVec2d +import com.cout970.math.vec2.Vec2d import net.terramodulus.engine.ferricia.Mui.modelFullScaling +import net.terramodulus.engine.ferricia.Mui.modelGeneralTransform import net.terramodulus.engine.ferricia.Mui.modelSmartScaling +import net.terramodulus.engine.ferricia.Mui.updateGeneralTransform +import kotlin.properties.Delegates @OptIn(ExperimentalUnsignedTypes::class) sealed class ModelTransform(handles: ULongArray) { @@ -14,6 +19,20 @@ sealed class ModelTransform(handles: ULongArray) { internal val wideHandle: ULong = handles[1] } +@OptIn(ExperimentalUnsignedTypes::class) +class GeneralTransform(sx: Double, sy: Double, angle: Double, px: Double, py: Double) : + ModelTransform(modelGeneralTransform(doubleArrayOf(sx, sy, angle, px, py))) { + var scale: Vec2d by Delegates.observable(MutVec2d(sx, sy)) { _, _, new -> + updateGeneralTransform(handle, doubleArrayOf(new.x, new.y, angle, px, py)) + } + var angle: Double by Delegates.observable(angle) { _, _, new -> + updateGeneralTransform(handle, doubleArrayOf(sx, sy, new, px, py)) + } + var pos: Vec2d by Delegates.observable(MutVec2d(px, py)) { _, _, new -> + updateGeneralTransform(handle, doubleArrayOf(sx, sy, angle, new.x, new.y)) + } +} + @OptIn(ExperimentalUnsignedTypes::class) class SmartScaling private constructor(vararg args: Int) : ModelTransform(modelSmartScaling(args)) { diff --git a/src/internal/client/kotlin/net/terramodulus/engine/Window.kt b/src/internal/client/kotlin/net/terramodulus/engine/Window.kt index ef3c5e54..677819ad 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/Window.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Window.kt @@ -18,11 +18,24 @@ import java.io.Closeable /** * Manages the SDL window instance and the underlying GL context. */ -class Window : Closeable { +class Window( + width: UInt, + height: UInt, +) : Closeable { + var width = width + private set + var height = height + private set private val sdlHandle = initSdlHandle() - private val windowHandle = initWindowHandle(sdlHandle) + private val windowHandle = initWindowHandle(sdlHandle) // TODO pass dimensions val canvas = Canvas(windowHandle) + fun sizeChanged(width: UInt, height: UInt) { + this.width = width + this.height = height + canvas.resizeGLViewport() + } + fun show() = showWindow(windowHandle) fun swap() = swapWindow(windowHandle) diff --git a/src/internal/client/kotlin/net/terramodulus/engine/WorldObjDrawable.kt b/src/internal/client/kotlin/net/terramodulus/engine/WorldObjDrawable.kt index 55fe2423..cf854519 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/WorldObjDrawable.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/WorldObjDrawable.kt @@ -5,38 +5,41 @@ package net.terramodulus.engine +import com.cout970.math.quaternion.Quatd +import com.cout970.math.vec3.Vec3d +import com.cout970.math.vec4.Vec4i import net.terramodulus.engine.ferricia.Gwr.newMeshGeomCube import net.terramodulus.engine.ferricia.Gwr.newMeshGeomSphere import net.terramodulus.engine.ferricia.Gwr.updateWorldObjModel -sealed class WorldObjDrawable(internal val handle: ULong, private var pos: Vec3D, private var scale: Vec3D, private var rot: Quat) { +sealed class WorldObjDrawable(internal val handle: ULong, private var pos: Vec3d, private var scale: Vec3d, private var rot: Quatd) { fun updateModel(px: Double, py: Double, pz: Double, sx: Double, sy: Double, sz: Double, w: Double, i: Double, j: Double, k: Double) = updateWorldObjModel(handle, doubleArrayOf(px, py, pz, w, i, j, k, sx, sy, sz)) - fun updateModel(pos: Vec3D, scale: Vec3D, rot: Quat) = - updateModel(pos.x, pos.y, pos.z, scale.x, scale.y, scale.z, rot.w, rot.i, rot.j, rot.k) + fun updateModel(pos: Vec3d, scale: Vec3d, rot: Quatd) = + updateModel(pos.x, pos.y, pos.z, scale.x, scale.y, scale.z, rot.w, rot.x, rot.y, rot.z) init { updateModel(pos, scale, rot) } - fun setPos(value: Vec3D) { + fun setPos(value: Vec3d) { pos = value updateModel(pos, scale, rot) } - fun setScale(value: Vec3D) { + fun setScale(value: Vec3d) { scale = value updateModel(pos, scale, rot) } - fun setRot(value: Quat) { + fun setRot(value: Quatd) { rot = value updateModel(pos, scale, rot) } } -class SimpleMesh3dGeomCube(width: Float, rgba: Rgba, pos: Vec3D, scale: Vec3D, rot: Quat) : +class SimpleMesh3dGeomCube(width: Float, rgba: Vec4i, pos: Vec3d, scale: Vec3d, rot: Quatd) : WorldObjDrawable(newMeshGeomCube(width, rgba.toArray()), pos, scale, rot) -class SimpleMesh3dGeomSphere(radius: Float, rgba: Rgba, pos: Vec3D, scale: Vec3D, rot: Quat) : +class SimpleMesh3dGeomSphere(radius: Float, rgba: Vec4i, pos: Vec3d, scale: Vec3d, rot: Quatd) : WorldObjDrawable(newMeshGeomSphere(radius, rgba.toArray()), pos, scale, rot) diff --git a/src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt b/src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt index 6ffa4fa0..2a3100b6 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/ferricia/Mui.kt @@ -7,6 +7,7 @@ package net.terramodulus.engine.ferricia import net.terramodulus.engine.MuiEvent +@OptIn(ExperimentalUnsignedTypes::class) internal object Mui { /** * @return SDL handle pointer @@ -141,6 +142,20 @@ internal object Mui { @JvmName("setGeomPos") external fun setGeomPos(handle: ULong, data: FloatArray) + /** + * @param data `[sx, sy, angle, px, py]`; scaling, rotation, position + * @return GeneralTransform handle pointers + */ + @JvmName("modelGeneralTransform") + external fun modelGeneralTransform(data: DoubleArray): ULongArray + + /** + * @param handle GeneralTransform thin pointer + * @param data `[sx, sy, angle, px, py]`; scaling, rotation, position + */ + @JvmName("updateGeneralTransform") + external fun updateGeneralTransform(handle: ULong, data: DoubleArray) + /** * @param data `[w, h, param, w, h]` * @return SmartScaling handle pointers diff --git a/src/internal/common/kotlin/net/terramodulus/engine/Containers.kt b/src/internal/common/kotlin/net/terramodulus/engine/Containers.kt deleted file mode 100644 index d15aea07..00000000 --- a/src/internal/common/kotlin/net/terramodulus/engine/Containers.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.engine - -data class Quat(val w: Double, val i: Double, val j: Double, val k: Double) { - fun toArray() = doubleArrayOf(w, i, j, k) -} - -data class Vec3D(val x: Double, val y: Double, val z: Double) { - companion object { - val ZERO = Vec3D(0.0, 0.0, 0.0) - - /** - * @param array array containing 3 double values - */ - fun fromArray(array: DoubleArray) = Vec3D(array[0], array[1], array[2]) - } - - fun toArray() = doubleArrayOf(x, y, z) -} diff --git a/src/internal/common/kotlin/net/terramodulus/engine/PhyBody.kt b/src/internal/common/kotlin/net/terramodulus/engine/PhyBody.kt index b2cca8ff..6ce9bed6 100644 --- a/src/internal/common/kotlin/net/terramodulus/engine/PhyBody.kt +++ b/src/internal/common/kotlin/net/terramodulus/engine/PhyBody.kt @@ -5,6 +5,9 @@ package net.terramodulus.engine +import com.cout970.math.vec3.Vec3d +import net.terramodulus.engine.common.ImmVec3dFromArray +import net.terramodulus.engine.common.toArray import net.terramodulus.engine.ferricia.Physics.addPhyBodyForce import net.terramodulus.engine.ferricia.Physics.addPhyBodyGeom import net.terramodulus.engine.ferricia.Physics.getPhyBodyLinearVel @@ -22,12 +25,12 @@ class PhyBody internal constructor(worldHandle: ULong, mass: Mass) { class SphereTotal(mass: Double, radius: Double) : Mass(newMassSphereTotal(mass, radius)) } - var pos - get() = Vec3D.fromArray(getPhyBodyPos(handle)) + var pos: Vec3d + get() = ImmVec3dFromArray(getPhyBodyPos(handle)) set(value) = setPhyBodyPos(handle, value.toArray()) - var linearVel - get() = Vec3D.fromArray(getPhyBodyLinearVel(handle)) + var linearVel: Vec3d + get() = ImmVec3dFromArray(getPhyBodyLinearVel(handle)) set(value) = setPhyBodyLinearVel(handle, value.toArray()) var gravityMode: Boolean by Delegates.observable(true) { _, _, newValue -> @@ -36,5 +39,5 @@ class PhyBody internal constructor(worldHandle: ULong, mass: Mass) { fun addGeom(geom: PhyGeom) = addPhyBodyGeom(handle, geom.handle) - fun addForce(force: Vec3D) = addPhyBodyForce(handle, force.toArray()) + fun addForce(force: Vec3d) = addPhyBodyForce(handle, force.toArray()) } diff --git a/src/internal/common/kotlin/net/terramodulus/engine/PhyWorld.kt b/src/internal/common/kotlin/net/terramodulus/engine/PhyWorld.kt index b985ab0a..ef74ddbe 100644 --- a/src/internal/common/kotlin/net/terramodulus/engine/PhyWorld.kt +++ b/src/internal/common/kotlin/net/terramodulus/engine/PhyWorld.kt @@ -5,6 +5,9 @@ package net.terramodulus.engine +import com.cout970.math.vec3.Vec3d +import net.terramodulus.engine.common.ZeroImmVec3d +import net.terramodulus.engine.common.toArray import net.terramodulus.engine.ferricia.Physics.newPhyCollisionManager import net.terramodulus.engine.ferricia.Physics.newPhyWorld import net.terramodulus.engine.ferricia.Physics.omitPhyCollisionManagerSpace @@ -18,7 +21,7 @@ class PhyWorld internal constructor(envHandle: ULong) { private val handle = newPhyWorld(envHandle) private val cmHandle = newPhyCollisionManager() - var gravity: Vec3D by Delegates.observable(Vec3D.ZERO) { _, _, newValue -> + var gravity: Vec3d by Delegates.observable(ZeroImmVec3d) { _, _, newValue -> setPhyWorldGravity(handle, newValue.toArray()) } diff --git a/src/internal/common/kotlin/net/terramodulus/engine/common/Containers.kt b/src/internal/common/kotlin/net/terramodulus/engine/common/Containers.kt new file mode 100644 index 00000000..e1a9a08e --- /dev/null +++ b/src/internal/common/kotlin/net/terramodulus/engine/common/Containers.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.engine.common + +import com.cout970.math.vec3.ImmVec3d +import com.cout970.math.vec3.ImmVec3f +import com.cout970.math.vec3.Vec3d + +/** + * @throws ArrayIndexOutOfBoundsException if [array]'s size < 3 + */ +fun ImmVec3dFromArray(array: DoubleArray) = ImmVec3d(array[0], array[1], array[2]) + +fun Vec3d.toArray() = doubleArrayOf(x, y, z) + +val ZeroImmVec3d = ImmVec3d(0.0) +val ZeroImmVec3f = ImmVec3f(0F) diff --git a/src/kernel/client/kotlin/net/terramodulus/core/Main.kt b/src/kernel/client/kotlin/net/terramodulus/core/Main.kt index f27c43ad..44747f35 100644 --- a/src/kernel/client/kotlin/net/terramodulus/core/Main.kt +++ b/src/kernel/client/kotlin/net/terramodulus/core/Main.kt @@ -15,7 +15,6 @@ import net.terramodulus.common.core.ApplicationArgumentParsingError import net.terramodulus.common.core.ApplicationInitializationFault import net.terramodulus.common.core.run import net.terramodulus.common.core.setupInit -import net.terramodulus.mui.GuiManager import net.terramodulus.util.exception.CodeLogicFault import net.terramodulus.util.exception.triggerGlobalCrash import java.awt.Dimension diff --git a/src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt b/src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt index 29846b28..cc1efb8c 100644 --- a/src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt +++ b/src/kernel/client/kotlin/net/terramodulus/core/TerraModulus.kt @@ -6,11 +6,12 @@ package net.terramodulus.core import net.terramodulus.common.core.AbstractTerraModulus -import net.terramodulus.mui.GuiManager +import net.terramodulus.mui.MuiManager +import net.terramodulus.mui.gui.GuiManager import net.terramodulus.void.World class TerraModulus internal constructor() : AbstractTerraModulus() { - private val guiManager = GuiManager(this) + private val muiManager = MuiManager(this) internal var world: World? = null override var tps: Int @@ -18,10 +19,9 @@ class TerraModulus internal constructor() : AbstractTerraModulus() { set(value) {} override fun run() { - guiManager.showWindow() + muiManager.showWindow() while (true) { - guiManager.updateCanvas() -// guiManager.updateScreens() + muiManager.update() Thread.sleep(1) } } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/GuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/MuiManager.kt similarity index 87% rename from src/kernel/client/kotlin/net/terramodulus/mui/GuiManager.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/MuiManager.kt index 60ab391b..a7b7ba86 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/GuiManager.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/MuiManager.kt @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ @@ -8,37 +8,32 @@ package net.terramodulus.mui import net.terramodulus.core.TerraModulus import net.terramodulus.engine.MuiEvent import net.terramodulus.engine.Window -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.ScreenManager -import net.terramodulus.mui.input.InputSystem +import net.terramodulus.mui.aui.AuiManager +import net.terramodulus.mui.gui.GuiManager +import net.terramodulus.mui.hui.HuiManager +import net.terramodulus.mui.kui.InputSystem +import net.terramodulus.mui.kui.KuiManager import net.terramodulus.util.logging.logger import java.io.Closeable private val logger = logger {} +private const val WIDTH = 800u +private const val HEIGHT = 480u -/** - * Graphical User Interface (GUI) Manager - */ -internal class GuiManager internal constructor(core: TerraModulus) : Closeable { - private val window = Window() - val renderSystem = RenderSystem(core, window.canvas) - val inputSystem = InputSystem() - val screenManager = ScreenManager(renderSystem.handle) +internal class MuiManager internal constructor(core: TerraModulus) : Closeable { + private val window = Window(WIDTH, HEIGHT) // SDL Window + internal val auiManager = AuiManager() + internal val huiManager = HuiManager() + internal val kuiManager = KuiManager() + internal val guiManager = GuiManager(window, core) internal fun showWindow() = window.show() -// /** -// * Screen updating, targeting as the same as *maximum FPS*, -// * but the numbers of ticks are not supposed to be compensated when missed, -// * so it is up to the callers to compensate missed activities. -// */ -// internal fun updateScreens() {} - /** - * Canvas updating, per frame, maximally the *maximum FPS*. + * SDL events updating, per frame, maximally the *maximum FPS*. * This includes input ticking and canvas rendering. */ - internal fun updateCanvas() { + internal fun update() { val keyEvents = ArrayList() window.pollEvents().forEach { event -> when (event) { @@ -226,7 +221,7 @@ internal class GuiManager internal constructor(core: TerraModulus) : Closeable { } is MuiEvent.WindowPixelSizeChanged -> { logger.debug { "Window pixel size changed to ${event.width}x${event.height}." } - window.canvas.resizeGLViewport() + window.sizeChanged(event.width, event.height) logger.debug { "Window viewport resized." } } is MuiEvent.WindowResized -> { @@ -240,14 +235,12 @@ internal class GuiManager internal constructor(core: TerraModulus) : Closeable { } } } - inputSystem.update(keyEvents) - screenManager.update(renderSystem, inputSystem) - window.canvas.clear() - screenManager.render(renderSystem) - window.swap() + kuiManager.update(keyEvents) + guiManager.updateScreens(this) + guiManager.updateCanvas() } override fun close() { - window.close() + TODO("Not yet implemented") } } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/audio/AudioSystem.kt b/src/kernel/client/kotlin/net/terramodulus/mui/aui/AudioSystem.kt similarity index 82% rename from src/kernel/client/kotlin/net/terramodulus/mui/audio/AudioSystem.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/aui/AudioSystem.kt index 3f8bc02b..26f330fa 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/audio/AudioSystem.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/aui/AudioSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.audio +package net.terramodulus.mui.aui class AudioSystem internal constructor() { } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/AuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/aui/AuiManager.kt similarity index 57% rename from src/kernel/client/kotlin/net/terramodulus/mui/AuiManager.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/aui/AuiManager.kt index 7d58bc1f..32ab8450 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/AuiManager.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/aui/AuiManager.kt @@ -1,11 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui - -import net.terramodulus.mui.audio.AudioSystem +package net.terramodulus.mui.aui /** * Audio User Interface (AUI) Manager diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ColorFilter.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ColorFilter.kt deleted file mode 100644 index d3a3ed42..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ColorFilter.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gfx - -typealias ColorFilter = net.terramodulus.engine.ColorFilter - -typealias AlphaFilter = net.terramodulus.engine.AlphaFilter diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ManagedRect.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ManagedRect.kt deleted file mode 100644 index 2362b7ae..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ManagedRect.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gfx - -import kotlin.properties.Delegates.observable - -class ManagedRect(rect: RectangleF) { - var rect: RectangleF by observable(rect) { _, _, newValue -> observers.forEach { it(newValue) } } - - private val observers = LinkedHashSet<(RectangleF) -> Unit>() - - fun observe(observer: (RectangleF) -> Unit) { - observers.add(observer) - } - - fun unobserve(observer: (RectangleF) -> Unit) { - observers.remove(observer) - } -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ModelTransform.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ModelTransform.kt deleted file mode 100644 index 8dcdb20c..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/ModelTransform.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gfx - -typealias ModelTransform = net.terramodulus.engine.ModelTransform - -typealias SmartScaling = net.terramodulus.engine.SmartScaling - -typealias FullScaling = net.terramodulus.engine.FullScaling - -fun FullScaling(rect: Dimension2I) = FullScaling(rect.width, rect.height) diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Rectangle.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Rectangle.kt deleted file mode 100644 index ecfeca3f..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Rectangle.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gfx - -/** - * Rectangle in a coordinate system with (0, 0) on the bottom left. - * The anchor of the rectangle is the bottom-left corner. - */ -data class RectangleI( - val x: Int, - val y: Int, - val width: Int, - val height: Int -) { - companion object { - fun withPoints(x0: Int, y0: Int, x1: Int, y1: Int): RectangleI { - val minX: Int; - val maxX: Int; - if (x0 < x1) { - minX = x0; - maxX = x1; - } else { - maxX = x0; - minX = x1; - } - val minY: Int; - val maxY: Int; - if (y0 < y1) { - minY = y0; - maxY = y1; - } else { - maxY = y0; - minY = y1; - } - return RectangleI(minX, minY, maxX - minX, maxY - minY) - } - } - - val size get() = Dimension2I(width, height) - - fun anchor(pos: Anchor5) = when (pos) { - Anchor5.TopLeft -> Vector2I(x, y + width) - Anchor5.TopRight -> Vector2I(x + width, y + height) - Anchor5.BottomLeft -> Vector2I(x, y) - Anchor5.BottomRight -> Vector2I(x + width, y) - Anchor5.Center -> Vector2I(x + width / 2, y + height / 2) - } - - fun translateBy(pos: Vector2I) = RectangleI(x + pos.x, y + pos.y, width, height) - - fun translateBy(x: Int, y: Int) = RectangleI(this.x + x, this.y + y, width, height) - - fun translateByY(y: Int) = RectangleI(x, this.y + y, width, height) - - fun translateByX(x: Int) = RectangleI(this.x + x, y, width, height) - - fun translateToY(y: Int) = RectangleI(x, y, width, height) - - fun translateToX(x: Int) = RectangleI(x, y, width, height) - - fun translateTo(pos: Vector2I) = RectangleI(pos.x, pos.y, width, height) - - fun translateTo(x: Int, y: Int) = RectangleI(x, y, width, height) - - fun toFloat() = RectangleF(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) -} - -/** - * Rectangle in a coordinate system with (0, 0) on the bottom left. - * The anchor of the rectangle is the bottom-left corner. - */ -data class RectangleF( - val x: Float, - val y: Float, - val width: Float, - val height: Float -) { - companion object { - fun withPoints(x0: Float, y0: Float, x1: Float, y1: Float): RectangleF { - val minX: Float; - val maxX: Float; - if (x0 < x1) { - minX = x0; - maxX = x1; - } else { - maxX = x0; - minX = x1; - } - val minY: Float; - val maxY: Float; - if (y0 < y1) { - minY = y0; - maxY = y1; - } else { - maxY = y0; - minY = y1; - } - return RectangleF(minX, maxX, minY, maxY) - } - } - - val size get() = Dimension2F(width, height) - - fun anchor(pos: Anchor5) = when (pos) { - Anchor5.TopLeft -> Vector2F(x, y + width) - Anchor5.TopRight -> Vector2F(x + width, y + height) - Anchor5.BottomLeft -> Vector2F(x, y) - Anchor5.BottomRight -> Vector2F(x + width, y) - Anchor5.Center -> Vector2F(x + width / 2, y + height / 2) - } - - fun translateBy(pos: Vector2F) = RectangleF(x + pos.x, y + pos.y, width, height) - - fun translateBy(x: Float, y: Float) = RectangleF(this.x + x, this.y + y, width, height) - - fun translateByY(y: Float) = RectangleF(x, this.y + y, width, height) - - fun translateByX(x: Float) = RectangleF(this.x + x, y, width, height) - - fun translateToY(y: Float) = RectangleF(x, y, width, height) - - fun translateToX(x: Float) = RectangleF(x, y, width, height) - - fun translateTo(pos: Vector2F) = RectangleF(pos.x, pos.y, width, height) - - fun translateTo(x: Float, y: Float) = RectangleF(x, y, width, height) -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Vector.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Vector.kt deleted file mode 100644 index a92d68db..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Vector.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gfx - -data class Vector2I(val x: Int, val y: Int) { - companion object { - val ZERO = Vector2I(0, 0) - } - - operator fun plus(other: Vector2I) = Vector2I(x + other.x, y + other.y) -} - -data class Vector2D(val x: Double, val y: Double) { - companion object { - val ZERO = Vector2D(.0, .0) - } - - operator fun plus(other: Vector2D) = Vector2D(x + other.x, y + other.y) -} - -data class Vector2F(val x: Float, val y: Float) { - companion object { - val ZERO = Vector2F(0F, 0F) - } - - operator fun plus(other: Vector2F) = Vector2F(x + other.x, y + other.y) -} - -data class Vector3I(val x: Int, val y: Int, val z: Int) { - companion object { - val ZERO = Vector3I(0, 0, 0) - } - - operator fun plus(other: Vector3I) = Vector3I(x + other.x, y + other.y, z + other.z) -} - -data class Vector3D(val x: Double, val y: Double, val z: Double) { - companion object { - val ZERO = Vector3D(.0, .0, .0) - } - - operator fun plus(other: Vector3D) = Vector3D(x + other.x, y + other.y, z + other.z) - - operator fun times(factor: Int) = Vector3D(x * factor, y * factor, z * factor) - operator fun times(factor: Float) = Vector3D(x * factor, y * factor, z * factor) - operator fun times(factor: Double) = Vector3D(x * factor, y * factor, z * factor) -} - -data class Vector3F(val x: Float, val y: Float, val z: Float) { - companion object { - val ZERO = Vector3F(0F, 0F, 0F) - } - - operator fun plus(other: Vector3F) = Vector3F(x + other.x, y + other.y, z + other.z) -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/AbstractPanel.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/AbstractPanel.kt deleted file mode 100644 index 810e7259..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/AbstractPanel.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms - -abstract class AbstractPanel : Component(), Container { -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Container.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Container.kt deleted file mode 100644 index de7f2fba..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Container.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms - -import net.terramodulus.mui.gfx.ManagedRect - -sealed interface Container { - val rect: ManagedRect -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Menu.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Menu.kt deleted file mode 100644 index ada1df80..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Menu.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms - -import net.terramodulus.mui.gms.event.MenuEvent -import java.util.ArrayDeque - -abstract class Menu : Container { - private val listeners = HashMap, LinkedHashSet<(MenuEvent) -> Unit>>() - private val components = LinkedHashSet() - private val componentQueue = ArrayDeque() - val handle: Handle = HandleImpl() - - private sealed interface ComponentOperation { - class Add(val component: () -> Component) : ComponentOperation - - class Remove(val component: Component) : ComponentOperation - } - - /** - * It is strongly suggested only using this function during initialization. - */ - protected fun addComponent(component: Component) { - components.add(component) - } - - /** - * It is strongly suggested only using this function during initialization. - */ - protected fun removeComponent(component: Component) { - components.remove(component) - } - - fun addListener(e: Class, l: (T) -> Unit) { - @Suppress("UNCHECKED_CAST") - listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (MenuEvent) -> Unit) - } - - fun removeListener(e: Class, l: (T) -> Unit) { - listeners[e]?.remove(l) - } - - internal fun dispatchEvent(event: MenuEvent) { - listeners[event.javaClass]?.forEach { it(event) } - } - - sealed interface Handle { - fun addComponent(component: () -> Component) - - fun removeComponent(component: Component) - } - - private inner class HandleImpl : Handle { - override fun addComponent(component: () -> Component) { - componentQueue.add(ComponentOperation.Add(component)) - } - - override fun removeComponent(component: Component) { - componentQueue.add(ComponentOperation.Remove(component)) - } - } - - abstract fun render() -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Screen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/Screen.kt deleted file mode 100644 index 60715ce5..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Screen.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms - -import net.terramodulus.mui.gfx.ManagedRect -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.event.ScreenEvent -import net.terramodulus.mui.input.InputSystem -import java.util.ArrayDeque - -abstract class Screen : Container { - private val listeners = HashMap, LinkedHashSet<(ScreenEvent) -> Unit>>() - private val menus = LinkedHashSet() - private val components = ArrayList() - private val componentQueue = ArrayDeque() - private val menuQueue = ArrayDeque() - override val rect: ManagedRect - get() = TODO("Not yet implemented") - val handle: Handle = HandleImpl() - - private sealed interface ComponentOperation { - fun apply(components: ArrayList) - - class Add(val component: () -> Component) : ComponentOperation { - override fun apply(components: ArrayList) { - components.add(component()) - } - } - - class Remove(val component: Component) : ComponentOperation { - override fun apply(components: ArrayList) { - components.remove(component) - } - } - } - - private sealed interface MenuOperation { - fun apply(menus: LinkedHashSet) - - class Add(val menu: () -> Menu) : MenuOperation { - override fun apply(menus: LinkedHashSet) { - menus.add(menu()) - } - } - - class Remove(val menu: Menu) : MenuOperation { - override fun apply(menus: LinkedHashSet) { - menus.remove(menu) - } - } - } - - /** - * It is strongly suggested only using this function during initialization. - */ - protected fun addComponent(component: Component) { - components.add(component) - } - - /** - * It is strongly suggested only using this function during initialization. - */ - protected fun removeComponent(component: Component) { - components.remove(component) - } - - /** - * It is strongly suggested only using this function during initialization. - */ - protected fun addMenu(menu: Menu) { - menus.add(menu) - } - - /** - * It is strongly suggested only using this function during initialization. - */ - protected fun removeMenu(menu: Menu) { - menus.remove(menu) - } - - fun addListener(e: Class, l: (T) -> Unit) { - @Suppress("UNCHECKED_CAST") - listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (ScreenEvent) -> Unit) - } - - fun removeListener(e: Class, l: (T) -> Unit) { - listeners[e]?.remove(l) - } - - internal fun dispatchEvent(event: ScreenEvent) { - listeners[event.javaClass]?.forEach { it(event) } - } - - sealed interface Handle { - fun addComponent(component: () -> Component) - - fun removeComponent(component: Component) - - fun addMenu(menu: () -> Menu) - - fun removeMenu(menu: Menu) - } - - private inner class HandleImpl : Handle { - override fun addComponent(component: () -> Component) { - componentQueue.add(ComponentOperation.Add(component)) - } - - override fun removeComponent(component: Component) { - componentQueue.add(ComponentOperation.Remove(component)) - } - - override fun addMenu(menu: () -> Menu) { - menuQueue.add(MenuOperation.Add(menu)) - } - - override fun removeMenu(menu: Menu) { - menuQueue.add(MenuOperation.Remove(menu)) - } - } - - internal open fun update(renderSystem: RenderSystem, screenManager: ScreenManager, inputSystem: InputSystem) {}; - - internal fun render(renderSystem: RenderSystem, screenManager: ScreenManager) { - componentQueue.forEach { it.apply(components) } - componentQueue.clear() - menuQueue.forEach { it.apply(menus) } - menuQueue.clear() - components.forEach { it.render(renderSystem) } - } - - /** - * Cleans up and closes any used resource here. - */ - abstract fun exit() -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/ScreenManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/ScreenManager.kt deleted file mode 100644 index f618d3ee..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/ScreenManager.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms - -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.impl.LaunchingScreen -import net.terramodulus.mui.input.InputSystem - -class ScreenManager internal constructor(private val renderSystemHandle: RenderSystem.Handle) { - /** - * FILO screen stack; the top-most screen instance is in the last. - */ - private val screens = ArrayDeque() - private val screenQueue = ArrayDeque() - val handle: Handle = HandleImpl() - - init { - screens.add(LaunchingScreen(renderSystemHandle)) - } - - private sealed interface ScreenOperation { - fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) - - /** - * Exits `n` times - * - * @throws IllegalArgumentException when `n` < 1 - * @throws IllegalStateException when `n` >= [screens] size during operation - */ - class Exit(val n: Int) : ScreenOperation { - init { - require(n < 0) { "`n` < 1" } - } - - override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { - if (n >= screens.size) { - throw IllegalStateException("`n` >= screens.size") - } - - for (i in 1..n) { - screens.removeLast().exit() - } - } - } - - /** - * Opens the `screen` - */ - class Open(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { - override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { - screens.addLast(screen(handle)) - } - } - - /** - * Opens the `screen` before the `target` screen - */ - class OpenBefore(val screen: (RenderSystem.Handle) -> Screen, val target: Screen) : ScreenOperation { - override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { - screens.add(screens.lastIndexOf(target), screen(handle)) - } - } - - /** - * Exits until reaching the `screen` then remains on the `screen` - */ - class ExitTo(val screen: Screen) : ScreenOperation { - override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { - val it = screens.asReversed().listIterator() - while (it.hasNext()) { - val e = it.next() - if (e == screen) { - break - } else { - it.remove() - e.exit() - } - } - } - } - - /** - * Clears [screens] then opens the `screen` - */ - class Reset(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { - override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { - screens.asReversed().forEach { it.exit() } - screens.clear() - screens.add(screen(handle)) - } - } - } - - sealed interface Handle { - /** - * @see ScreenOperation.Exit - */ - fun exit(n: Int) - - /** - * @see ScreenOperation.Open - */ - fun open(screen: (RenderSystem.Handle) -> Screen) - - /** - * It is not recommended to use this in general scenarios. - * @see ScreenOperation.OpenBefore - */ - fun openBefore(screen: (RenderSystem.Handle) -> Screen, target: Screen) - - /** - * @see ScreenOperation.ExitTo - */ - fun exitTo(screen: Screen) - - /** - * @see ScreenOperation.Reset - */ - fun reset(screen: (RenderSystem.Handle) -> Screen) - } - - private inner class HandleImpl : Handle { - override fun exit(n: Int) { - screenQueue.add(ScreenOperation.Exit(n)) - } - - override fun open(screen: (RenderSystem.Handle) -> Screen) { - screenQueue.add(ScreenOperation.Open(screen)) - } - - override fun openBefore(screen: (RenderSystem.Handle) -> Screen, target: Screen) { - screenQueue.add(ScreenOperation.OpenBefore(screen, target)) - } - - override fun exitTo(screen: Screen) { - screenQueue.add(ScreenOperation.ExitTo(screen)) - } - - override fun reset(screen: (RenderSystem.Handle) -> Screen) { - screenQueue.add(ScreenOperation.Reset(screen)) - } - } - - internal fun update(renderSystem: RenderSystem, inputSystem: InputSystem) { - screens.forEach { it.update(renderSystem, this, inputSystem) } - } - - internal fun render(renderSystem: RenderSystem) { - screenQueue.forEach { it.apply(renderSystemHandle, screens) } - screenQueue.clear() - screens.forEach { it.render(renderSystem, this) } - } -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ComponentEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ComponentEvent.kt deleted file mode 100644 index 7383b352..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ComponentEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.event - -sealed interface ComponentEvent { -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/MenuEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/MenuEvent.kt deleted file mode 100644 index 2cdbc0b5..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/MenuEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.event - -sealed interface MenuEvent { -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ScreenEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ScreenEvent.kt deleted file mode 100644 index ae203cae..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/event/ScreenEvent.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.event - -sealed interface ScreenEvent { - data object Open : ScreenEvent - data object Close : ScreenEvent -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/BlankComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/BlankComponent.kt deleted file mode 100644 index 129f679b..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/BlankComponent.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.Component - -/** - * This can act as a placeholder [Component] in a [Layout][terramodulus.mui.gms.Layout]. - */ -class BlankComponent : Component() { - override fun render(renderSystem: RenderSystem) {} -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt deleted file mode 100644 index 421b08d2..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/FlexibleBoxLayout.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.RectangleF -import net.terramodulus.mui.gms.Component -import net.terramodulus.mui.gms.Container -import net.terramodulus.mui.gms.Layout - -class FlexibleBoxLayout(container: Container) : Layout(container) { - override val components: Iterable - get() = TODO("Not yet implemented") - - override fun layout(rect: RectangleF) { - TODO("Not yet implemented") - } -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GeomComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GeomComponent.kt deleted file mode 100644 index a1ed2188..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GeomComponent.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.GuiGeometry -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.Component - -class GeomComponent(val geom: GuiGeometry) : Component() { - override fun render(renderSystem: RenderSystem) { - geom.render(renderSystem) - } -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt deleted file mode 100644 index 2d8aa37f..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GraphicsComponent.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.GuiSprite -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.AbstractPanel -import net.terramodulus.mui.gms.Component - -sealed interface GraphicsComponent - -class SpriteComponent(val sprite: GuiSprite) : Component(), GraphicsComponent { - override fun render(renderSystem: RenderSystem) { - sprite.render(renderSystem) - } -} - -@Suppress("CanSealedSubClassBeObject") -class CanvasComponent : AbstractPanel(), GraphicsComponent { - override fun render(renderSystem: RenderSystem) { - TODO("Not yet implemented") - } -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/LaunchingScreen.kt deleted file mode 100644 index 3442eef2..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/LaunchingScreen.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.AlphaFilter -import net.terramodulus.mui.gfx.Dimension2I -import net.terramodulus.mui.gfx.FullScaling -import net.terramodulus.mui.gfx.GuiRect -import net.terramodulus.mui.gfx.GuiSprite -import net.terramodulus.mui.gfx.RectangleI -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gfx.SmartScaling -import net.terramodulus.mui.gms.Screen -import net.terramodulus.mui.gms.ScreenManager -import net.terramodulus.mui.input.InputSystem - -private val REF_SIZE = Dimension2I(800, 480) - -private val BG_COLOR = floatArrayOf(.145F, .776F, .768F) - -private const val ANI_DURATION = .75F // in second - -private const val PAUSE_DURATION = 2 // in second - -internal class LaunchingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { - private var stage = 0 - private var last = System.currentTimeMillis() // timestamp in milliseconds - private var alphaFilter = AlphaFilter(0F) - - init { - GeomComponent(GuiRect(0, 0, 800, 480, 37, 198, 196, 255)).apply { - geom.add(alphaFilter) - geom.add(FullScaling(REF_SIZE)) - addComponent(this) - } - SpriteComponent(GuiSprite( - RectangleI(0, 0, 512, 128), - renderSystemHandle.loadTexture("/studio_logo.png"), - )).apply { - sprite.add(alphaFilter) - sprite.add(SmartScaling.both(REF_SIZE.width, REF_SIZE.height, 512, 128)) - addComponent(this) - } - } - - override fun update(renderSystem: RenderSystem, screenManager: ScreenManager, inputSystem: InputSystem) { - val current = System.currentTimeMillis() - val elapsed = (current - last) / 1000F // elapsed time for this stage - when (stage) { - 0 -> if (elapsed >= ANI_DURATION) { - stage = 1 - last = current - alphaFilter.alpha = 1F - } else { - alphaFilter.alpha = elapsed / ANI_DURATION - } - - 1 -> if (elapsed >= PAUSE_DURATION) { - stage = 2 - last = current - } - - 2 -> if (elapsed >= ANI_DURATION) { - stage = 3 - last = current - alphaFilter.alpha = 0F - } else { - alphaFilter.alpha = 1 - elapsed / ANI_DURATION - } - - 3 -> screenManager.handle.reset(::ResourceLoadingScreen) - } - } - - override fun exit() {} -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/PositioningComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/PositioningComponent.kt deleted file mode 100644 index 729b19be..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/PositioningComponent.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.Component - -class PositioningComponent : Component() { - override fun render(renderSystem: RenderSystem) { - TODO("Not yet implemented") - } -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/TitleScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/TitleScreen.kt deleted file mode 100644 index a13d8e02..00000000 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/TitleScreen.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors - * SPDX-License-Identifier: LGPL-3.0-only - */ - -package net.terramodulus.mui.gms.impl - -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.Screen - -class TitleScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { - override fun exit() {} -} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/GuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/GuiManager.kt new file mode 100644 index 00000000..0ac450ec --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/GuiManager.kt @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui + +import net.terramodulus.core.TerraModulus +import net.terramodulus.engine.Window +import net.terramodulus.mui.MuiManager +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.agim.ScreenManager +import net.terramodulus.util.logging.logger +import java.io.Closeable + +private val logger = logger {} + +/** + * Graphical User Interface (GUI) Manager + */ +internal class GuiManager internal constructor(private val window: Window, core: TerraModulus) : Closeable { + val renderSystem = RenderSystem(core, window.canvas) + val screenManager = ScreenManager(renderSystem.handle) + + /** + * Screen updating, targeting as the same as *maximum FPS*, + * but the numbers of ticks are not supposed to be compensated when missed, + * so it is up to the callers to compensate missed activities. + */ + internal fun updateScreens(muiManager: MuiManager) { + screenManager.update(muiManager) + } + + /** + * Canvas updating, per frame, maximally the *maximum FPS*. + * This includes input ticking and canvas rendering. + */ + internal fun updateCanvas() { + window.canvas.clear() + screenManager.render(renderSystem) + window.swap() + } + + override fun close() { + window.close() + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AbstractPane.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AbstractPane.kt new file mode 100644 index 00000000..c0d4f14e --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AbstractPane.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +abstract class AbstractPane : Component(), Container { + final override fun update(muiIopIf: ScreenManager.MuiIopIf) { + super.update(muiIopIf) + layout.update() + layout.components.forEach { it.update(muiIopIf) } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Component.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Component.kt similarity index 54% rename from src/kernel/client/kotlin/net/terramodulus/mui/gms/Component.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Component.kt index c67121d8..1da300c2 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Component.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Component.kt @@ -1,17 +1,17 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gms +package net.terramodulus.mui.gui.agim -import net.terramodulus.mui.gfx.ManagedRect -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gms.event.ComponentEvent -import net.terramodulus.mui.input.InputSystem +import net.terramodulus.mui.gui.agim.event.ComponentEvent +import net.terramodulus.mui.gui.gfx.ManagedRect +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.kui.InputSystem /** - * [Component] can only be contained by only one [Container]. + * [Component] can only be contained by only one [Container][net.terramodulus.mui.gui.agim.Container] at once. * * It is an undefined behavior when the `Component` is contained repeatedly * or in different containers simultaneously. @@ -20,9 +20,9 @@ abstract class Component { private val listeners = HashMap, LinkedHashSet<(ComponentEvent) -> Unit>>() /** - * This should only be modified by [Layout] managers. + * Caveat: This should only be modified by [Layout][net.terramodulus.mui.gui.agim.Layout] managers. */ - open lateinit var rect: ManagedRect + open lateinit var rect: ManagedRect.Normal internal set abstract fun render(renderSystem: RenderSystem) @@ -40,7 +40,7 @@ abstract class Component { listeners[event.javaClass]?.forEach { it(event) } } - fun update(inputSystem: InputSystem) { - TODO() + internal open fun update(muiIopIf: ScreenManager.MuiIopIf) { + dispatchEvent(ComponentEvent.Update(muiIopIf)) } } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Container.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Container.kt new file mode 100644 index 00000000..56687c20 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Container.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import net.terramodulus.mui.gui.gfx.ManagedRect + +/** + * **AGIM Container**, direct subclasses are explicitly defined. + */ +sealed interface Container { + val rect: ManagedRect + + val layout: Layout +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Layout.kt similarity index 79% rename from src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Layout.kt index 53e52ff9..58c3e16d 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/Layout.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Layout.kt @@ -1,13 +1,13 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gms +package net.terramodulus.mui.gui.agim -import net.terramodulus.mui.gfx.RectangleF -import net.terramodulus.mui.gms.impl.SequenceLayout.Element -import kotlin.reflect.KProperty +import net.terramodulus.mui.gui.gfx.RectangleF +import java.io.Closeable +import java.util.ArrayDeque /** * [Layout] is always mutable. @@ -15,25 +15,44 @@ import kotlin.reflect.KProperty * **Layout** is defined only when all its managed components all belong to the container * associated with this layout manager *exclusively*. */ -abstract class Layout(private val container: Container) { +abstract class Layout(private val container: Container) : Closeable { companion object { - const val ALIGN_START = 0F; - const val ALIGN_CENTER = .5F; - const val ALIGN_END = 1F; + const val ALIGN_START = 0F + const val ALIGN_CENTER = .5F + const val ALIGN_END = 1F } - abstract val components: Iterable + abstract val components: Sequence private val containerObserver = ::layout.apply(container.rect::observe) + private val layoutOperations = ArrayDeque() + + fun interface Operation { + fun Layout.operate() + } + /** - * Updates the layout output using the current layout configurations - * by invoking [layout] internally. + * Query [Operation] on the [Layout] structures, potentially changing any components and configurations. * - * It is recommended to invoke this when this layout is being initialized - * or any layout configuration has been changed. + * It is required to use this when this layout is being initialized + * or any layout configuration is being changed. */ - fun update() = layout(container.rect.rect) + fun operate(operation: Operation) { + layoutOperations.add(operation) + } + + /** + * Updates the layout output using pending operations added via [operate], + * and the resultant layout configurations by invoking [layout] internally if any operation exists. + */ + fun update() { + val nonEmpty = layoutOperations.isNotEmpty() + while (layoutOperations.isNotEmpty()) { + with(layoutOperations.removeFirst()) { this@Layout.operate() } + } + if (nonEmpty) layout(container.rect.value) + } /** * Lays out the managed [components] by this [Layout] manager. @@ -47,10 +66,14 @@ abstract class Layout(private val container: Container) { /** * Must be invoked when this [Layout] is no longer in use. */ - fun clear() { + fun clear() { // Not sure whether there is the necessity to separate this from [close]. container.rect.unobserve(containerObserver) } + override fun close() { + clear() + } + /** * Should not rely on indices in the [Layout] since they are not meaningful. */ @@ -92,10 +115,10 @@ abstract class Layout(private val container: Container) { } abstract class ElementGroup protected constructor( - container: Container, - protected val elements: ElementList, + container: Container, + protected val elements: ElementList, ) : Group(container) { - final override val components = elements.componentsView + final override val components = elements.componentsView.asSequence() override fun contains(component: Component): Boolean = elements.contains(component) @@ -144,39 +167,18 @@ abstract class Layout(private val container: Container) { elements.replace(target, component, element) } - /** - * @param components must not be empty - */ - protected class ComponentIterable(private vararg val components: KProperty) : Iterable { - override fun iterator(): Iterator = object : Iterator { - private var index = 0 - - private fun untilNotNull(): Boolean { - do { - if (components[index].getter.call() != null) - return true - else - index++ - } while (index < components.size) - return false - } + protected fun componentsNullableSequence(vararg components: () -> Component?) = + sequenceOf(*components).mapNotNull { it() } - override fun hasNext(): Boolean = untilNotNull() - - override fun next(): Component = if (untilNotNull()) { - components[index].getter.call()!! - } else { - throw NoSuchElementException() - } - } - } + protected fun componentsSequence(vararg components: () -> Component) = + sequenceOf(*components).map { it() } /** * Internal list of the layout elements. */ protected class ElementList private constructor( - private val components: MutableList, // order is defined here - private val elementMap: MutableMap, // elements are mapped here + private val components: MutableList, // order is defined here + private val elementMap: MutableMap, // elements are mapped here ) : Iterable> { companion object { fun withComponentsDefault(default: () -> E, vararg components: Component): ElementList { diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Menu.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Menu.kt new file mode 100644 index 00000000..5828d4e2 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Menu.kt @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import net.terramodulus.mui.gui.agim.event.MenuEvent +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.io.Closeable + +abstract class Menu : Container, Closeable { + private val listeners = HashMap, LinkedHashSet<(MenuEvent) -> Unit>>() + + fun addListener(e: Class, l: (T) -> Unit) { + @Suppress("UNCHECKED_CAST") + listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (MenuEvent) -> Unit) + } + + fun removeListener(e: Class, l: (T) -> Unit) { + listeners[e]?.remove(l) + } + + internal fun dispatchEvent(event: MenuEvent) { + listeners[event.javaClass]?.forEach { it(event) } + } + + internal fun render(renderSystem: RenderSystem) { + layout.components.forEach { it.render(renderSystem) } + } + + internal fun update(muiIopIf: ScreenManager.MuiIopIf) { + dispatchEvent(MenuEvent.Update(muiIopIf)) + layout.update() + layout.components.forEach { it.update(muiIopIf) } + } + + /** + * Cleans up and closes any used resources in this session. + */ + final override fun close() { + dispatchEvent(MenuEvent.Close) + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/MenuManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/MenuManager.kt new file mode 100644 index 00000000..07c9d0f9 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/MenuManager.kt @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.util.ArrayDeque + +class MenuManager internal constructor() { + private val menus = LinkedHashSet() + private val menuQueue = ArrayDeque() + val handle: Handle = HandleImpl() + + private sealed interface MenuOperation { + fun apply(menus: LinkedHashSet) + + class Add(val menu: () -> Menu) : MenuOperation { + override fun apply(menus: LinkedHashSet) { + menus.add(menu()) + } + } + + class Remove(val menu: Menu) : MenuOperation { + override fun apply(menus: LinkedHashSet) { + menus.remove(menu) + } + } + } + + sealed interface Handle { + fun addMenu(menu: () -> Menu) + + fun removeMenu(menu: Menu) + } + + private inner class HandleImpl : Handle { + override fun addMenu(menu: () -> Menu) { + menuQueue.add(MenuOperation.Add(menu)) + } + + override fun removeMenu(menu: Menu) { + menuQueue.add(MenuOperation.Remove(menu)) + } + } + + internal fun update(muiIopIf: ScreenManager.MuiIopIf) { + menuQueue.forEach { it.apply(menus) } + menuQueue.clear() + } + + internal fun render(renderSystem: RenderSystem, screenManager: ScreenManager) { + menus.forEach { it.render(renderSystem) } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Screen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Screen.kt new file mode 100644 index 00000000..58176308 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Screen.kt @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import net.terramodulus.mui.gui.agim.event.ScreenEvent +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.io.Closeable + +abstract class Screen( + managerHandle: ScreenManager.Handle, + final override val rect: ScreenManager.DelegatedRect +) : Container, Closeable { + private val listeners = HashMap, LinkedHashSet<(ScreenEvent) -> Unit>>() + private val menuManager = MenuManager() + val handle: Handle = HandleImpl(managerHandle) + + fun addListener(e: Class, l: (T) -> Unit) { + @Suppress("UNCHECKED_CAST") + listeners.computeIfAbsent(e) { LinkedHashSet() }.add(l as (ScreenEvent) -> Unit) + } + + fun removeListener(e: Class, l: (T) -> Unit) { + listeners[e]?.remove(l) + } + + internal fun dispatchEvent(event: ScreenEvent) { + listeners[event.javaClass]?.forEach { it(event) } + } + + sealed interface Handle { + fun addMenu(menu: () -> Menu) + + fun removeMenu(menu: Menu) + + fun addTopMenu(menu: () -> Menu) + + fun removeTopMenu(menu: Menu) + } + + private inner class HandleImpl(private val managerHandle: ScreenManager.Handle) : Handle { + override fun addMenu(menu: () -> Menu) = menuManager.handle.addMenu(menu) + + override fun removeMenu(menu: Menu) = menuManager.handle.removeMenu(menu) + + override fun addTopMenu(menu: () -> Menu) = managerHandle.addMenu(menu) + + override fun removeTopMenu(menu: Menu) = managerHandle.removeMenu(menu) + } + + internal fun update(muiIopIf: ScreenManager.MuiIopIf) { + dispatchEvent(ScreenEvent.Update(muiIopIf)) + layout.update() + layout.components.forEach { it.update(muiIopIf) } + } + + internal fun render(renderSystem: RenderSystem, screenManager: ScreenManager) { + menuManager.render(renderSystem, screenManager) + layout.components.forEach { it.render(renderSystem) } + } + + /** + * Cleans up and closes any used resources in this session. + */ + final override fun close() { + dispatchEvent(ScreenEvent.Close) + rect.close() + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/ScreenManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/ScreenManager.kt new file mode 100644 index 00000000..f0d8a50e --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/ScreenManager.kt @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import net.terramodulus.engine.Window +import net.terramodulus.mui.MuiManager +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.agim.impl.LaunchingScreen +import net.terramodulus.mui.gui.gfx.ManagedRect +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.mui.kui.InputSystem +import java.io.Closeable +import kotlin.properties.Delegates + +class ScreenManager internal constructor(window: Window, private val renderSystemHandle: RenderSystem.Handle) { + /** + * FILO screen stack; the top-most screen instance is in the last. + */ + private val screens = ArrayDeque() + private val screenQueue = ArrayDeque() + private val menuManager = MenuManager() + private val viewportRect = ManagedRect.Normal(RectangleF(0F, 0F, window.width.toFloat(), window.height.toFloat())) + + inner class DelegatedRect : ManagedRect(), Closeable { + override var value: RectangleF by Delegates.observable(viewportRect.value) { _, _, newValue -> + observers.forEach { it(newValue) } + } + private set + + private val listener: (RectangleF) -> Unit = { rect -> value = rect } + + init { + viewportRect.observe(listener) + } + + override fun close() { + viewportRect.unobserve(listener) + } + } + + val handle: Handle = HandleImpl() + + init { + screens.add(LaunchingScreen(handle, DelegatedRect(), renderSystemHandle)) + } + + private sealed interface ScreenOperation { + fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) + + /** + * Exits `n` times + * + * @throws IllegalArgumentException when `n` < 1 + * @throws IllegalStateException when `n` >= [screens] size during operation + */ + class Exit(val n: Int) : ScreenOperation { + init { + require(n < 0) { "`n` < 1" } + } + + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + if (n >= screens.size) { + throw IllegalStateException("`n` >= screens.size") + } + + for (i in 1..n) { + screens.removeLast().close() + } + } + } + + /** + * Opens the `screen` + */ + class Open(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + screens.addLast(screen(handle)) + } + } + + /** + * Opens the `screen` before the `target` screen + */ + class OpenBefore(val target: Screen, val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + screens.add(screens.lastIndexOf(target), screen(handle)) + } + } + + /** + * Exits until reaching the `screen` then remains on the `screen` + */ + class ExitTo(val screen: Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + val it = screens.asReversed().listIterator() + while (it.hasNext()) { + val e = it.next() + if (e == screen) { + break + } else { + it.remove() + e.close() + } + } + } + } + + /** + * Clears [screens] then opens the `screen` + */ + class Reset(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply(handle: RenderSystem.Handle, screens: ArrayDeque) { + screens.asReversed().forEach { it.close() } + screens.clear() + screens.add(screen(handle)) + } + } + } + + sealed interface Handle { + /** + * @see ScreenOperation.Exit + */ + fun exit(n: Int) + + /** + * @see ScreenOperation.Open + */ + fun open(screen: (Handle, DelegatedRect, RenderSystem.Handle) -> Screen) + + /** + * It is not recommended to use this in general scenarios. + * @see ScreenOperation.OpenBefore + */ + fun openBefore(target: Screen, screen: (Handle, DelegatedRect, RenderSystem.Handle) -> Screen) + + /** + * @see ScreenOperation.ExitTo + */ + fun exitTo(screen: Screen) + + /** + * @see ScreenOperation.Reset + */ + fun reset(screen: (Handle, DelegatedRect, RenderSystem.Handle) -> Screen) + + fun addMenu(menu: () -> Menu) + + fun removeMenu(menu: Menu) + } + + private inner class HandleImpl : Handle { + override fun exit(n: Int) { + screenQueue.add(ScreenOperation.Exit(n)) + } + + override fun open(screen: (Handle, DelegatedRect, RenderSystem.Handle) -> Screen) { + screenQueue.add(ScreenOperation.Open { screen(handle, DelegatedRect(), it) }) + } + + override fun openBefore(target: Screen, screen: (Handle, DelegatedRect, RenderSystem.Handle) -> Screen) { + screenQueue.add(ScreenOperation.OpenBefore(target) { screen(handle, DelegatedRect(), it) }) + } + + override fun exitTo(screen: Screen) { + screenQueue.add(ScreenOperation.ExitTo(screen)) + } + + override fun reset(screen: (Handle, DelegatedRect, RenderSystem.Handle) -> Screen) { + screenQueue.add(ScreenOperation.Reset { screen(handle, DelegatedRect(), it) }) + } + + override fun addMenu(menu: () -> Menu) = menuManager.handle.addMenu(menu) + + override fun removeMenu(menu: Menu) = menuManager.handle.removeMenu(menu) + } + + /** + * MUI Interoperability Interface + */ + class MuiIopIf internal constructor( + val renderSystem: RenderSystem, + val screenManager: ScreenManager, + val inputSystem: InputSystem, + ) + + internal fun update(muiManager: MuiManager) { + val iopIf = MuiIopIf(muiManager.guiManager.renderSystem, this, muiManager.kuiManager.inputSystem) + menuManager.update(iopIf) + screens.forEach { it.update(iopIf) } + } + + internal fun render(renderSystem: RenderSystem) { + menuManager.render(renderSystem, this) + screenQueue.forEach { it.apply(renderSystemHandle, screens) } + screenQueue.clear() + screens.forEach { it.render(renderSystem, this) } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/AsdManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/AsdManager.kt new file mode 100644 index 00000000..9361f451 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/AsdManager.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.asd + +// TODO Should ASD affect choices of Layout? +class AsdManager internal constructor() { + companion object { + // TODO temporary demonstrative testing default + internal fun default(): AsdManager = AsdManager() + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/MenuStyles.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/MenuStyles.kt new file mode 100644 index 00000000..9ee4e43e --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/MenuStyles.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.asd + +class MenuStyles { +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/ScreenStyles.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/ScreenStyles.kt new file mode 100644 index 00000000..3c6ab931 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/asd/ScreenStyles.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.asd + +class ScreenStyles { +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/ComponentEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/ComponentEvent.kt new file mode 100644 index 00000000..90b80378 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/ComponentEvent.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.event + +import net.terramodulus.mui.gui.agim.ScreenManager + +sealed interface ComponentEvent { + data class Update(val muiIopIf: ScreenManager.MuiIopIf) : ComponentEvent + data class Key(val generic: GenericEvent.Key) : ComponentEvent +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/GenericEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/GenericEvent.kt new file mode 100644 index 00000000..7316c3ee --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/GenericEvent.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.event + +sealed class GenericEvent { + var bubble = true + + data object Key : GenericEvent() +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/MenuEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/MenuEvent.kt new file mode 100644 index 00000000..31316537 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/MenuEvent.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.event + +import net.terramodulus.mui.gui.agim.ScreenManager + +sealed interface MenuEvent { + data class Update(val muiIopIf: ScreenManager.MuiIopIf) : MenuEvent + data object Close : MenuEvent +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/ScreenEvent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/ScreenEvent.kt new file mode 100644 index 00000000..d829b5de --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/event/ScreenEvent.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.event + +import net.terramodulus.mui.gui.agim.ScreenManager + +sealed interface ScreenEvent { + data class Update(val muiIopIf: ScreenManager.MuiIopIf) : ScreenEvent + data object Close : ScreenEvent +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/AbsoluteLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/AbsoluteLayout.kt new file mode 100644 index 00000000..5189436b --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/AbsoluteLayout.kt @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.agim.Container +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.gfx.InsetsD +import net.terramodulus.mui.gui.gfx.InsetsF +import net.terramodulus.mui.gui.gfx.RectangleF + +class AbsoluteLayout(container: Container, component: Component, private var config: Config) : Layout(container) { + override val components = componentsSequence(::component) + var component = component + private set + + sealed class Config private constructor() { + abstract fun layout(container: RectangleF): RectangleF + + data object Full : Config() { + override fun layout(container: RectangleF) = container + } + + data class Insets(var insets: InsetsF) : Config() { + override fun layout(container: RectangleF) = container - insets + } + } + + fun update(component: Component) { + operate { + this@AbsoluteLayout.component = component + } + } + + fun update(operation: (Config) -> Config) { + operate { + config = operation(config) + } + } + + override fun layout(rect: RectangleF) { + component.rect.value = config.layout(rect) + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/BlankComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/BlankComponent.kt new file mode 100644 index 00000000..d8fcc68b --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/BlankComponent.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.agim.Component + +/** + * This can act as a placeholder [Component] in a [Layout][net.terramodulus.mui.gui.agim.Layout]. + */ +class BlankComponent : Component() { + override fun render(renderSystem: RenderSystem) {} +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/CompositeLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/CompositeLayout.kt new file mode 100644 index 00000000..cccd31a7 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/CompositeLayout.kt @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.Container +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.gfx.RectangleF + +class CompositeLayout(container: Container) : Layout(container) { + private val layouts = ArrayDeque() + + override val components = layouts.asSequence().flatMap { it.components } + + fun update(operation: ArrayDeque.() -> Unit) { + operate { operation(layouts) } + } + + override fun layout(rect: RectangleF) { + layouts.forEach { it.update() } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/FlexibleBoxLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/FlexibleBoxLayout.kt new file mode 100644 index 00000000..ac61beaa --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/FlexibleBoxLayout.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.Container +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.gfx.RectangleF + +class FlexibleBoxLayout(container: Container) : Layout(container) { + override val components = TODO("Not yet implemented") + + override fun layout(rect: RectangleF) { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GameplayScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GameplayScreen.kt similarity index 78% rename from src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GameplayScreen.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GameplayScreen.kt index e704c474..7298f5c1 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/GameplayScreen.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GameplayScreen.kt @@ -3,39 +3,45 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gms.impl +package net.terramodulus.mui.gui.agim.impl +import com.cout970.math.quaternion.ImmQuatd +import com.cout970.math.vec3.ImmVec3d +import com.cout970.math.vec3.Vec3d +import com.cout970.math.vec3.Vec3f +import com.cout970.math.vec3.div +import com.cout970.math.vec3.dot +import com.cout970.math.vec3.normalized +import com.cout970.math.vec3.plus +import com.cout970.math.vec3.times +import com.cout970.math.vec3.toImmVec3f +import com.cout970.math.vec4.ImmVec4i import net.terramodulus.core.TerraModulus import net.terramodulus.core.getResourceAsString import net.terramodulus.engine.Camera3D import net.terramodulus.engine.PhyBody import net.terramodulus.engine.PhyGeom -import net.terramodulus.engine.Quat -import net.terramodulus.engine.Rgba import net.terramodulus.engine.SimpleMesh3dGeomCube import net.terramodulus.engine.SimpleMesh3dGeomSphere -import net.terramodulus.engine.Vec3D -import net.terramodulus.engine.Vec3F import net.terramodulus.engine.WorldObjDrawable -import net.terramodulus.mui.gfx.Direction6C -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gfx.Vector3D -import net.terramodulus.mui.gms.Component -import net.terramodulus.mui.gms.Screen -import net.terramodulus.mui.gms.ScreenManager -import net.terramodulus.mui.input.InputSystem +import net.terramodulus.engine.common.ZeroImmVec3d +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.agim.Screen +import net.terramodulus.mui.gui.agim.ScreenManager +import net.terramodulus.mui.gui.gfx.Direction6C +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.kui.InputSystem import net.terramodulus.util.logging.logger import net.terramodulus.void.World import kotlin.math.PI -import kotlin.math.sqrt import kotlin.random.Random -private val WHITE = Rgba(255, 255, 255, 255) -private val RED = Rgba(255, 0, 0, 255) -private val GREEN = Rgba(0, 255, 0, 255) -private val BLUE = Rgba(0, 0, 255, 255) -private val STD_SCALE = Vec3D(.5, .5, .5) -private val IDENT_ROT = Quat(1.0, .0, .0, .0) +private val WHITE = ImmVec4i(255, 255, 255, 255) +private val RED = ImmVec4i(255, 0, 0, 255) +private val GREEN = ImmVec4i(0, 255, 0, 255) +private val BLUE = ImmVec4i(0, 0, 255, 255) +private val STD_SCALE = ImmVec3d(.5, .5, .5) +private val IDENT_ROT = ImmQuatd(1.0, .0, .0, .0) private const val MASS = 1.0 private const val MAX_SPEED = PI * PI // reachable by autonomous movement private const val MAX_ACC = PI * PI // without other forces, reaching MAX_SPEED in one second @@ -49,7 +55,13 @@ private const val MAX_ZOOM = 4 private val logger = logger {} -internal class GameplayScreen(private val core: TerraModulus, private val camera: Camera3D, renderSystemHandle: RenderSystem.Handle) : Screen() { +internal class GameplayScreen( + private val core: TerraModulus, + private val camera: Camera3D, + renderSystemHandle: RenderSystem.Handle, + managerHandle: ScreenManager.Handle, + rect: ScreenManager.DelegatedRect, +) : Screen(managerHandle, rect) { private val geoShaders = camera.loadGeoShaders( getResourceAsString("/gwr_geo.vsh"), getResourceAsString("/gwr_geo.fsh"), @@ -86,11 +98,11 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera SimpleMesh3dGeomCube( 2F, randomColor(), - Vec3D(x, y, z), + ImmVec3d(x, y, z), STD_SCALE, IDENT_ROT, ), - Vec3D(x, y, z) + ImmVec3d(x, y, z) ) private fun randomColor() = when (Random.nextInt(3)) { @@ -102,7 +114,7 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera override fun wrapChar(phyBody: PhyBody): VoidGeom { player = PlayerVoidGeom(phyBody, - SimpleMesh3dGeomSphere(1F, WHITE, Vec3D(0.0, 1.0, 0.0), STD_SCALE, IDENT_ROT) + SimpleMesh3dGeomSphere(1F, WHITE, ImmVec3d(0.0, 1.0, 0.0), STD_SCALE, IDENT_ROT) ) return player } @@ -114,21 +126,21 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera } } - private inner class EnvVoidGeom(override val phyGeom: PhyGeom, drawable: WorldObjDrawable, override val pos: Vec3D) : + private inner class EnvVoidGeom(override val phyGeom: PhyGeom, drawable: WorldObjDrawable, override val pos: Vec3d) : VoidGeom(drawable), World.EnvVoidGeom private inner class PlayerVoidGeom(override val phyBody: PhyBody, drawable: WorldObjDrawable) : VoidGeom(drawable), World.PlayerVoidGeom { - fun move(dir: Vector3D) { - if (dir == Vector3D.ZERO) return // avoid math errors and computations - val dir = Vec3D(dir.x, dir.y, dir.z).normalize() + fun move(dir: Vec3d) { + if (dir == ZeroImmVec3d) return // avoid math errors and computations + val dir = ImmVec3d(dir.x, dir.y, dir.z).normalized() val curVel = phyBody.linearVel // Let d be the unit vector of autonomous movement target direction, // v_c be the current velocity of body, // v_p be the scalar projection of v_c on d. // v_p = v_c * d, may be negative // Autonomous acceleration is made only if v_p < MAX_SPEED. - val projVel = curVel * dir + val projVel = curVel dot dir if (projVel < MAX_SPEED) { // Let v_d be the delta velocity in direction of d, // a_d be the delta acceleration to be made. @@ -142,30 +154,14 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera override fun render() { drawable.setPos(phyBody.pos) - camera.refreshPos(phyBody.pos.toVec3F().toArray()) + camera.refreshPos(phyBody.pos.toImmVec3f().toArray()) super.render() } - override var pos: Vec3D by phyBody::pos + override var pos: Vec3d by phyBody::pos } - private fun Vec3D.normalize(): Vec3D { - val mag = mag() - return Vec3D(x / mag, y / mag, z / mag) - } - - private operator fun Vec3D.times(d: Double) = Vec3D(x * d, y * d, z * d) - private operator fun Vec3D.div(d: Double) = Vec3D(x / d, y / d, z / d) - private operator fun Vec3D.minus(other: Vec3D) = Vec3D(x - other.x, y - other.y, z - other.z) - // dot product - private operator fun Vec3D.times(other: Vec3D) = x * other.x + y * other.y + z * other.z - - // dot product with itself - private fun Vec3D.squared() = x * x + y * y + z * z - // magnitude or length - private fun Vec3D.mag() = sqrt(squared()) - - private fun Vec3D.toVec3F() = Vec3F(x.toFloat(), y.toFloat(), z.toFloat()) + private fun Vec3f.toArray() = floatArrayOf(x, y, z) private fun Direction6C.toKey() = when (this) { Direction6C.North -> InputSystem.Keys.W @@ -177,15 +173,15 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera } private fun Direction6C.toVector() = when (this) { - Direction6C.North -> Vector3D(.0, .0, -1.0) - Direction6C.South -> Vector3D(.0, .0, 1.0) - Direction6C.West -> Vector3D(-1.0, .0, .0) - Direction6C.East -> Vector3D(1.0, .0, .0) - Direction6C.Up -> Vector3D(.0, 1.0, .0) - Direction6C.Down -> Vector3D(.0, -1.0, .0) + Direction6C.North -> ImmVec3d(.0, .0, -1.0) + Direction6C.South -> ImmVec3d(.0, .0, 1.0) + Direction6C.West -> ImmVec3d(-1.0, .0, .0) + Direction6C.East -> ImmVec3d(1.0, .0, .0) + Direction6C.Up -> ImmVec3d(.0, 1.0, .0) + Direction6C.Down -> ImmVec3d(.0, -1.0, .0) } - private fun Vec3D.display() = "[$x, $y, $z]" + private fun Vec3d.display() = "[$x, $y, $z]" override fun update(renderSystem: RenderSystem, screenManager: ScreenManager, inputSystem: InputSystem) { // Those keys are not related to GUI, so they are fine to be here. @@ -292,7 +288,7 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera } if (inputSystem.condition { N.justDown() }) { // Reset velocity of sphere to zero - player.phyBody.linearVel = Vec3D.ZERO + player.phyBody.linearVel = ZeroImmVec3d logger.info { "Reset velocity to zero" } } // This is problematic and difficult to be resolved. @@ -320,9 +316,9 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera } } - val dirs = ArrayList() + val dirs = ArrayList() Direction6C.entries.forEach { if (inputSystem.condition { it.toKey().down() }) dirs.add(it.toVector()) } - player.move(dirs.fold(Vector3D.ZERO, Vector3D::plus)) + player.move(dirs.fold(ZeroImmVec3d, Vec3d::plus)) } private inner class GameplayRenderer : Component() { @@ -334,6 +330,4 @@ internal class GameplayScreen(private val core: TerraModulus, private val camera } internal fun renderGwrGeo(drawable: WorldObjDrawable) = camera.renderGwrGeo(drawable, geoShaders) - - override fun exit() {} } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GeomComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GeomComponent.kt new file mode 100644 index 00000000..5faaa12c --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GeomComponent.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.gfx.GuiGeometry +import net.terramodulus.mui.gui.gfx.RenderSystem + +class GeomComponent(val geom: GuiGeometry) : Component() { + override fun render(renderSystem: RenderSystem) { + geom.render(renderSystem) + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GraphicsComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GraphicsComponent.kt new file mode 100644 index 00000000..f93867ce --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GraphicsComponent.kt @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.AbstractPane +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.gfx.GuiSprite +import net.terramodulus.mui.gui.gfx.RenderSystem + +sealed interface GraphicsComponent + +class SpriteComponent(val sprite: GuiSprite) : Component(), GraphicsComponent { + override fun render(renderSystem: RenderSystem) { + sprite.render(renderSystem) + } +} + +@Suppress("CanSealedSubClassBeObject") +class CanvasComponent(override val layout: Layout) : AbstractPane(), GraphicsComponent { + override fun render(renderSystem: RenderSystem) { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/LaunchingScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/LaunchingScreen.kt new file mode 100644 index 00000000..4ab1b770 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/LaunchingScreen.kt @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.engine.GeneralTransform +import net.terramodulus.mui.gui.gfx.AlphaFilter +import net.terramodulus.mui.gui.gfx.Dimension2I +import net.terramodulus.mui.gui.gfx.FullScaling +import net.terramodulus.mui.gui.gfx.GuiRect +import net.terramodulus.mui.gui.gfx.GuiSprite +import net.terramodulus.mui.gui.gfx.RectangleI +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.gfx.SmartScaling +import net.terramodulus.mui.gui.agim.Screen +import net.terramodulus.mui.gui.agim.ScreenManager +import net.terramodulus.mui.gui.agim.event.ScreenEvent + +private val REF_SIZE = Dimension2I(800, 480) + +private val BG_COLOR = floatArrayOf(.145F, .776F, .768F) + +private const val ANI_DURATION = .75F // in second + +private const val PAUSE_DURATION = 2 // in second + +internal class LaunchingScreen( + managerHandle: ScreenManager.Handle, + rect: ScreenManager.DelegatedRect, + renderSystemHandle: RenderSystem.Handle, +) : Screen(managerHandle, rect) { + private var stage = 0 + private var last = System.currentTimeMillis() // timestamp in milliseconds + private var alphaFilter = AlphaFilter(0F) + override val layout = CompositeLayout(this) + + init { + layout.update { + add(AbsoluteLayout(this@LaunchingScreen, GeomComponent(GuiRect(0, 0, 1, 1, 37, 198, 196, 255)).apply { + geom.add(alphaFilter) + }, AbsoluteLayout.Config.Full)) + } + SpriteComponent(GuiSprite( + RectangleI(0, 0, 512, 128), + renderSystemHandle.loadTexture("/studio_logo.png"), + )).apply { + sprite.add(alphaFilter) + sprite.add(SmartScaling.both(REF_SIZE.width, REF_SIZE.height, 512, 128)) + addComponent(this) + } + + addListener(ScreenEvent.Update::class.java) { + val current = System.currentTimeMillis() + val elapsed = (current - last) / 1000F // elapsed time for this stage + when (stage) { + 0 -> if (elapsed >= ANI_DURATION) { + stage = 1 + last = current + alphaFilter.alpha = 1F + } else { + alphaFilter.alpha = elapsed / ANI_DURATION + } + + 1 -> if (elapsed >= PAUSE_DURATION) { + stage = 2 + last = current + } + + 2 -> if (elapsed >= ANI_DURATION) { + stage = 3 + last = current + alphaFilter.alpha = 0F + } else { + alphaFilter.alpha = 1 - elapsed / ANI_DURATION + } + + 3 -> it.muiIopIf.screenManager.handle.reset(::ResourceLoadingScreen) + } + } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ResourceLoadingScreen.kt similarity index 72% rename from src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ResourceLoadingScreen.kt index e62d091b..9b92ccee 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/ResourceLoadingScreen.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ResourceLoadingScreen.kt @@ -1,23 +1,23 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gms.impl +package net.terramodulus.mui.gui.agim.impl -import net.terramodulus.mui.gfx.AlphaFilter -import net.terramodulus.mui.gfx.Dimension2I -import net.terramodulus.mui.gfx.FullScaling -import net.terramodulus.mui.gfx.GuiRect -import net.terramodulus.mui.gfx.GuiSprite -import net.terramodulus.mui.gfx.RectangleI -import net.terramodulus.mui.gfx.RenderSystem -import net.terramodulus.mui.gfx.SmartScaling -import net.terramodulus.mui.gfx.Vector3F -import net.terramodulus.mui.gms.Screen -import net.terramodulus.mui.gms.ScreenManager -import net.terramodulus.mui.input.InputSystem -import kotlin.math.max +import net.terramodulus.engine.common.ZeroImmVec3f +import net.terramodulus.mui.gui.agim.Screen +import net.terramodulus.mui.gui.agim.ScreenManager +import net.terramodulus.mui.gui.gfx.AlphaFilter +import net.terramodulus.mui.gui.gfx.Dimension2I +import net.terramodulus.mui.gui.gfx.FullScaling +import net.terramodulus.mui.gui.gfx.GuiRect +import net.terramodulus.mui.gui.gfx.GuiSprite +import net.terramodulus.mui.gui.gfx.Rectangle +import net.terramodulus.mui.gui.gfx.RectangleI +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.gfx.SmartScaling +import net.terramodulus.mui.kui.InputSystem import kotlin.math.min import kotlin.properties.Delegates @@ -30,7 +30,11 @@ private val BG_COLOR = floatArrayOf(.145F, .776F, 0.768F) private const val ANI_DURATION = 1F // in second private const val PAUSE_DURATION = 2F // in second -class ResourceLoadingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() { +class ResourceLoadingScreen( + managerHandle: ScreenManager.Handle, + rect: ScreenManager.DelegatedRect, + renderSystemHandle: RenderSystem.Handle, +) : Screen(managerHandle, rect) { private var stage = 0 private var last = System.currentTimeMillis() // timestamp in milliseconds private var alphaFilter = AlphaFilter(0F) @@ -66,7 +70,7 @@ class ResourceLoadingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() } private class ProgressBar { - val rectDim = RectangleI.withPoints(7, 7, 393, 33) + val rectDim = Rectangle.withPoints(7, 7, 393, 33) val length = rectDim.width var progress: Float by Delegates.observable(0f) { _, _, _ -> rect.setPos(7, 7, rectDim.x + (progress * length).toInt(), 33) @@ -104,11 +108,7 @@ class ResourceLoadingScreen(renderSystemHandle: RenderSystem.Handle) : Screen() } // 3 -> screenManager.handle.openBefore(::TitleScreen, this) - 3 -> screenManager.handle.reset(renderSystem.newGameplayScreen(Vector3F.ZERO)) + 3 -> screenManager.handle.reset(renderSystem.newGameplayScreen(ZeroImmVec3f)) } } - - override fun exit() { - - } } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ScrollPane.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ScrollPane.kt new file mode 100644 index 00000000..eb972ac5 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ScrollPane.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.AbstractPane +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.gfx.RenderSystem + +class ScrollPane : AbstractPane() { + override fun render(renderSystem: RenderSystem) { + TODO("Not yet implemented") + } + + override val layout: Layout + get() = TODO("Not yet implemented") +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/SequenceLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SequenceLayout.kt similarity index 90% rename from src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/SequenceLayout.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SequenceLayout.kt index 9d80c12c..0a00cd55 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gms/impl/SequenceLayout.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SequenceLayout.kt @@ -1,14 +1,14 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gms.impl +package net.terramodulus.mui.gui.agim.impl -import net.terramodulus.mui.gfx.RectangleF -import net.terramodulus.mui.gms.Component -import net.terramodulus.mui.gms.Container -import net.terramodulus.mui.gms.Layout +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.agim.Container +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.gfx.RectangleF /** * Common implementation that is either [ColumnLayout] or [RowLayout]. diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SliderComponent.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SliderComponent.kt new file mode 100644 index 00000000..1e06a869 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SliderComponent.kt @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.gfx.Anchor5 +import net.terramodulus.mui.gui.gfx.Direction4A +import net.terramodulus.mui.gui.gfx.Direction4AD +import net.terramodulus.mui.gui.gfx.GuiRect +import net.terramodulus.mui.gui.gfx.Rectangle +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.mui.gui.gfx.RenderSystem +import kotlin.properties.Delegates + +class SliderComponent(val dir: Direction4A) : Component() { + private var bgInit = false + private var fgInit = false + private lateinit var background: GuiRect + private lateinit var bgComponent: GeomComponent + private lateinit var foreground: GuiRect + private lateinit var fgComponent: GeomComponent + var fraction: Float by Delegates.observable(0F) { _, _, value -> + updateForeground(rect.value, value) + } + + init { + rect.observe { + val anchor = it.anchor(Anchor5.TopRight) + if (!bgInit) { + background = GuiRect(it.x.toInt(), it.y.toInt(), anchor.xi, anchor.yi, 255, 255, 255, 255) + bgComponent = GeomComponent(background) + bgInit = true + } else { + background.setPos(it.x.toInt(), it.y.toInt(), anchor.xi, anchor.yi) + } + updateForeground(it, fraction) + } + } + + private fun updateForeground(bounds: RectangleF, fraction: Float) { + // (bounds.x, bounds.y) is the bottom-left anchor + val topRight = bounds.anchor(Anchor5.TopRight) + val rect = when (dir) { + Direction4A.XPos -> // left to right + Rectangle.withDirection(bounds.x, bounds.y, bounds.width * fraction, bounds.height, Direction4AD.QuadOne) + Direction4A.XNeg -> // right to left + Rectangle.withDirection(topRight.x, bounds.y, bounds.width * fraction, bounds.height, Direction4AD.QuadTwo) + Direction4A.YPos -> // bottom to top + Rectangle.withDirection(bounds.x, bounds.y, bounds.width, bounds.height * fraction, Direction4AD.QuadOne) + Direction4A.YNeg -> // top to bottom + Rectangle.withDirection(bounds.x, topRight.y, bounds.width, bounds.height * fraction, Direction4AD.QuadFour) + } + val anchor = rect.anchor(Anchor5.TopRight) + if (!fgInit) { + foreground = GuiRect(rect.x.toInt(), rect.y.toInt(), anchor.xi, anchor.yi, 122, 122, 122, 255) + fgComponent = GeomComponent(foreground) + fgInit = true + } else { + foreground.setPos(rect.x.toInt(), rect.y.toInt(), anchor.xi, anchor.yi) + } + } + + override fun render(renderSystem: RenderSystem) { + rect.value + bgComponent.render(renderSystem) + fgComponent.render(renderSystem) + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/TitleScreen.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/TitleScreen.kt new file mode 100644 index 00000000..ab0881e0 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/TitleScreen.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.agim.Screen +import net.terramodulus.mui.gui.agim.ScreenManager + +class TitleScreen( + renderSystemHandle: RenderSystem.Handle, + managerHandle: ScreenManager.Handle, + rect: ScreenManager.DelegatedRect, +) : Screen(managerHandle, rect) { +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/package-info.java b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/package-info.java new file mode 100644 index 00000000..5723fbd2 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/package-info.java @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +/** + *

Abstract Graphical Interface Model (AGIM) + *

+ * Defined in EFP 9 (or a dedicated EFP for this chapter). + */ +package net.terramodulus.mui.gui.agim; diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Anchor.kt similarity index 83% rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Anchor.kt index b000efe7..b3880d64 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Anchor.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Anchor.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gfx +package net.terramodulus.mui.gui.gfx /* * Every set of anchor position directions has different meanings in different context, diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ColorFilter.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ColorFilter.kt new file mode 100644 index 00000000..fa70486f --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ColorFilter.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.gfx + +import net.terramodulus.engine.AlphaFilter +import net.terramodulus.engine.ColorFilter + +typealias ColorFilter = ColorFilter + +typealias AlphaFilter = AlphaFilter diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Dimension.kt similarity index 74% rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Dimension.kt index 6825bcb0..6d2eb829 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Dimension.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Dimension.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gfx +package net.terramodulus.mui.gui.gfx data class Dimension2I(val width: Int, val height: Int) diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Direction.kt similarity index 83% rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Direction.kt index 621776f8..1e3aed54 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/Direction.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Direction.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gfx +package net.terramodulus.mui.gui.gfx /* * Every set of directions has different meanings in different context, @@ -39,6 +39,13 @@ enum class Direction4A { XPos, XNeg, YPos, YNeg; } +/** + * Set of 4 axial diagonal directions, by Cartesian quadrants. + */ +enum class Direction4AD { + QuadOne, QuadTwo, QuadThree, QuadFour; +} + /** * Set of 8 compass directions. */ diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiGeometry.kt similarity index 90% rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiGeometry.kt index cc6a61b4..950c8236 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiGeometry.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiGeometry.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gfx +package net.terramodulus.mui.gui.gfx import net.terramodulus.engine.GeomDrawable import net.terramodulus.engine.SimpleLineGeom diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiSprite.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiSprite.kt similarity index 80% rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiSprite.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiSprite.kt index d7d7a944..11ba563d 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/GuiSprite.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/GuiSprite.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gfx +package net.terramodulus.mui.gui.gfx import net.terramodulus.engine.SpriteMesh diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Insets.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Insets.kt new file mode 100644 index 00000000..623d7554 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Insets.kt @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.gfx + +abstract class Insets(open val left: N, open val top: N, open val right: N, open val bottom: N) { + +} + +data class InsetsI( + override val left: Int, + override val top: Int, + override val right: Int, + override val bottom: Int, +) : Insets(left, top, right, bottom) + +data class InsetsF( + override val left: Float, + override val top: Float, + override val right: Float, + override val bottom: Float, +) : Insets(left, top, right, bottom) { + operator fun plus(other: InsetsF) = InsetsF(left + other.left, top + other.top, right + other.right, bottom + other.bottom) +} + +data class InsetsD( + override val left: Double, + override val top: Double, + override val right: Double, + override val bottom: Double, +) : Insets(left, top, right, bottom) { + operator fun plus(other: InsetsD) = InsetsD(left + other.left, top + other.top, right + other.right, bottom + other.bottom) +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ManagedRect.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ManagedRect.kt new file mode 100644 index 00000000..f80d3f3b --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ManagedRect.kt @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.gfx + +import kotlin.properties.Delegates.observable + +/** + * AGIM internally Managed Rectangle + */ +abstract class ManagedRect { + abstract val value: RectangleF + + protected val observers = LinkedHashSet<(RectangleF) -> Unit>() + + internal abstract fun setValue(rect: RectangleF) + + class Normal(rect: RectangleF) : ManagedRect() { + override var value: RectangleF by observable(rect) { _, _, newValue -> observers.forEach { it(newValue) } } + internal set + + override fun setValue(rect: RectangleF) { + value = rect + } + } + + internal fun observe(observer: (RectangleF) -> Unit) { + observers.add(observer) + } + + internal fun unobserve(observer: (RectangleF) -> Unit) { + observers.remove(observer) + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ModelTransform.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ModelTransform.kt new file mode 100644 index 00000000..1dd00547 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ModelTransform.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.gfx + +import net.terramodulus.engine.ModelTransform +import net.terramodulus.engine.SmartScaling + +typealias ModelTransform = ModelTransform + +typealias SmartScaling = SmartScaling + +typealias FullScaling = net.terramodulus.engine.FullScaling + +fun FullScaling(rect: Dimension2I) = FullScaling(rect.width, rect.height) diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Rectangle.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Rectangle.kt new file mode 100644 index 00000000..daa4c5fe --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Rectangle.kt @@ -0,0 +1,185 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.gfx + +import com.cout970.math.vec2.ImmVec2f +import com.cout970.math.vec2.ImmVec2i +import com.cout970.math.vec2.Vec2 +import com.cout970.math.vec2.Vec2f +import com.cout970.math.vec2.Vec2i + +sealed class Rectangle, N: Number, V: Vec2, D>( + open val x: N, + open val y: N, + open val width: N, + open val height: N, +) { + companion object { + fun withPoints(x0: Int, y0: Int, x1: Int, y1: Int): RectangleI { + val minX: Int; + val maxX: Int; + if (x0 < x1) { + minX = x0; + maxX = x1; + } else { + maxX = x0; + minX = x1; + } + val minY: Int; + val maxY: Int; + if (y0 < y1) { + minY = y0; + maxY = y1; + } else { + maxY = y0; + minY = y1; + } + return RectangleI(minX, minY, maxX - minX, maxY - minY) + } + + fun withPoints(x0: Float, y0: Float, x1: Float, y1: Float): RectangleF { + val minX: Float; + val maxX: Float; + if (x0 < x1) { + minX = x0; + maxX = x1; + } else { + maxX = x0; + minX = x1; + } + val minY: Float; + val maxY: Float; + if (y0 < y1) { + minY = y0; + maxY = y1; + } else { + maxY = y0; + minY = y1; + } + return RectangleF(minX, maxX, minY, maxY) + } + + fun withDirection(x: Int, y: Int, width: Int, height: Int, dir: Direction4AD) = when (dir) { + Direction4AD.QuadOne -> RectangleI(x, y, width, height) + Direction4AD.QuadTwo -> RectangleI(x - width, y, width, height) + Direction4AD.QuadThree -> RectangleI(x - width, y - height, width, height) + Direction4AD.QuadFour -> RectangleI(x, y - height, width, height) + } + + fun withDirection(x: Float, y: Float, width: Float, height: Float, dir: Direction4AD) = when (dir) { + Direction4AD.QuadOne -> RectangleF(x, y, width, height) + Direction4AD.QuadTwo -> RectangleF(x - width, y, width, height) + Direction4AD.QuadThree -> RectangleF(x - width, y - height, width, height) + Direction4AD.QuadFour -> RectangleF(x, y - height, width, height) + } + } + + protected abstract fun constructor(x: N, y: N, width: N, height: N): T + protected abstract fun vec2(x: N, y: N): V + protected abstract operator fun N.plus(other: N): N + protected abstract operator fun N.minus(other: N): N + protected abstract operator fun N.div(other: Int): N + protected abstract val V.x: N + protected abstract val V.y: N + + abstract val size: D + + fun anchor(pos: Anchor5) = when (pos) { + Anchor5.TopLeft -> vec2(x, y + width) + Anchor5.TopRight -> vec2(x + width, y + height) + Anchor5.BottomLeft -> vec2(x, y) + Anchor5.BottomRight -> vec2(x + width, y) + Anchor5.Center -> vec2(x + width / 2, y + height / 2) + } + + fun translateBy(pos: V) = constructor(x + pos.x, y + pos.y, width, height) + + fun translateBy(x: N, y: N) = constructor(this.x + x, this.y + y, width, height) + + fun translateByY(y: N) = constructor(x, this.y + y, width, height) + + fun translateByX(x: N) = constructor(this.x + x, y, width, height) + + fun translateToY(y: N) = constructor(x, y, width, height) + + fun translateToX(x: N) = constructor(x, y, width, height) + + fun translateTo(pos: V) = constructor(pos.x, pos.y, width, height) + + fun translateTo(x: N, y: N) = constructor(x, y, width, height) + + /** Inflates the [Rectangle] with the [Insets] */ + operator fun plus(other: Insets) = constructor( + x - other.left, + y - other.bottom, + width + other.left + other.right, + height + other.bottom + other.top, + ) + + /** Deflates the [Rectangle] with the [Insets] */ + operator fun minus(other: Insets) = constructor( + x + other.left, + y + other.bottom, + width - other.left - other.right, + height - other.bottom - other.top, + ) + + abstract fun toFloat(): RectangleF +} + +/** + * Rectangle in a coordinate system with (0, 0) on the bottom left. + * The anchor of the rectangle is the bottom-left corner. + */ +data class RectangleI( + override val x: Int, + override val y: Int, + override val width: Int, + override val height: Int +) : Rectangle(x, y, width, height) { + override fun constructor(x: Int, y: Int, width: Int, height: Int) = RectangleI(x, y, width, height) + + override fun vec2(x: Int, y: Int) = ImmVec2i(x, y) + + override fun Int.plus(other: Int) = this + other + + override fun Int.div(other: Int) = this / other + + override val Vec2i.x: Int by ::x + override val Vec2i.y: Int by ::y + override val size = Dimension2I(width, height) + + override fun toFloat() = RectangleF(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) +} + +/** + * Rectangle in a coordinate system with (0, 0) on the bottom left. + * The anchor of the rectangle is the bottom-left corner. + */ +data class RectangleF( + override val x: Float, + override val y: Float, + override val width: Float, + override val height: Float +) : Rectangle(x, y, width, height) { + override fun constructor( + x: Float, + y: Float, + width: Float, + height: Float + ) = RectangleF(x, y, width, height) + + override fun vec2(x: Float, y: Float) = ImmVec2f(x, y) + + override fun Float.plus(other: Float) = this + other + + override fun Float.div(other: Int) = this / other + + override val Vec2f.x: Float by ::x + override val Vec2f.y: Float by ::y + override val size = Dimension2F(width, height) + override fun toFloat() = this +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/RenderSystem.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/RenderSystem.kt similarity index 79% rename from src/kernel/client/kotlin/net/terramodulus/mui/gfx/RenderSystem.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/RenderSystem.kt index 0e86fb93..6091ae4c 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/gfx/RenderSystem.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/RenderSystem.kt @@ -1,17 +1,18 @@ /* - * SPDX-FileCopyrightText: 2025 TerraModulus Team and Contributors + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.gfx +package net.terramodulus.mui.gui.gfx +import com.cout970.math.vec3.Vec3f import net.terramodulus.core.TerraModulus import net.terramodulus.core.getResourceAsBytes import net.terramodulus.core.getResourceAsString import net.terramodulus.engine.Canvas import net.terramodulus.engine.GeomDrawable import net.terramodulus.engine.MeshDrawable -import net.terramodulus.mui.gms.impl.GameplayScreen +import net.terramodulus.mui.gui.agim.impl.GameplayScreen class RenderSystem internal constructor(private val core: TerraModulus, private val canvas: Canvas) { val handle: Handle = HandleImpl() @@ -39,8 +40,13 @@ class RenderSystem internal constructor(private val core: TerraModulus, private } } - internal fun newGameplayScreen(pos: Vector3F) = - { it: Handle -> GameplayScreen(core, canvas.createCamera(floatArrayOf(pos.x, pos.y, pos.z)), it) } + internal fun newGameplayScreen(pos: Vec3f) = { it: Handle -> + GameplayScreen( + core, + canvas.createCamera(floatArrayOf(pos.x, pos.y, pos.z)), + it + ) + } internal fun renderGuiTex(drawable: MeshDrawable, texture: UInt) = canvas.renderGuiTex(drawable, texShaders, texture) diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Vector.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Vector.kt new file mode 100644 index 00000000..47f84c19 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Vector.kt @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.gfx diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/hui/HuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/hui/HuiManager.kt new file mode 100644 index 00000000..f3963b9a --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/hui/HuiManager.kt @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.hui + +class HuiManager { +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/input/InputSystem.kt b/src/kernel/client/kotlin/net/terramodulus/mui/kui/InputSystem.kt similarity index 99% rename from src/kernel/client/kotlin/net/terramodulus/mui/input/InputSystem.kt rename to src/kernel/client/kotlin/net/terramodulus/mui/kui/InputSystem.kt index 7656eed8..d9f8b3e6 100644 --- a/src/kernel/client/kotlin/net/terramodulus/mui/input/InputSystem.kt +++ b/src/kernel/client/kotlin/net/terramodulus/mui/kui/InputSystem.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-only */ -package net.terramodulus.mui.input +package net.terramodulus.mui.kui // TODO Temporary solution, should be rewritten in next update. class InputSystem internal constructor() { diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/kui/KuiManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/kui/KuiManager.kt new file mode 100644 index 00000000..41701cb3 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/kui/KuiManager.kt @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.kui + +import net.terramodulus.mui.kui.InputSystem.KeyEvent + +class KuiManager { + val inputSystem = InputSystem() + + internal fun update(events: List) { + inputSystem.update(events) + } +} diff --git a/src/kernel/common/kotlin/net/terramodulus/void/World.kt b/src/kernel/common/kotlin/net/terramodulus/void/World.kt index f8fbea9a..6c5d7d9f 100644 --- a/src/kernel/common/kotlin/net/terramodulus/void/World.kt +++ b/src/kernel/common/kotlin/net/terramodulus/void/World.kt @@ -5,11 +5,13 @@ package net.terramodulus.void +import com.cout970.math.vec3.ImmVec3d +import com.cout970.math.vec3.Vec3d +import com.cout970.math.vec3.plus import net.terramodulus.engine.PhyBody import net.terramodulus.engine.PhyEnv import net.terramodulus.engine.PhyGeom import net.terramodulus.engine.PhyGeomBox -import net.terramodulus.engine.Vec3D import net.terramodulus.util.logging.logger import java.io.Closeable import kotlin.properties.Delegates @@ -25,7 +27,7 @@ class World(commander: Ymir) : Closeable { private val env = PhyEnv() private val world = env.createWorld() - var gravity: Vec3D by world::gravity + var gravity: Vec3d by world::gravity var frictionMode: FrictionMode by Delegates.observable(FrictionMode.Infinite) { _, _, new -> when (new) { FrictionMode.Zero -> world.setFriction(0.0) @@ -47,7 +49,7 @@ class World(commander: Ymir) : Closeable { val floor = world.createGeomPlane(doubleArrayOf(0.0, 1.0, 0.0, -100.0)) init { - gravity = Vec3D(0.0, -9.81, 0.0) + gravity = ImmVec3d(0.0, -9.81, 0.0) floor.setBits(1u, 1u.inv()) world.omitSpace(mainSpace) // Spawn point @@ -56,7 +58,7 @@ class World(commander: Ymir) : Closeable { objects[ObjId.randomUnique(objects)] = commander.wrapChar( world.newBody(PhyBody.Mass.SphereTotal(1.0, .5)).apply { addGeom(createGeomSphere(.5)) - pos = Vec3D(0.0, 1.0, 0.0) + pos = ImmVec3d(0.0, 1.0, 0.0) } ) // Test Objects @@ -92,7 +94,7 @@ class World(commander: Ymir) : Closeable { interface VoidGeom { fun render() - val pos: Vec3D + val pos: Vec3d } interface EnvVoidGeom : VoidGeom { @@ -111,12 +113,12 @@ class World(commander: Ymir) : Closeable { val interval = 5.0 val max = 5 * 5 * 5 // 125 for each set val directions = arrayOf( - Vec3D(1.0, 0.0, 0.0), - Vec3D(-1.0, 0.0, 0.0), - Vec3D(0.0, 1.0, 0.0), - Vec3D(0.0, -1.0, 0.0), - Vec3D(0.0, 0.0, 1.0), - Vec3D(0.0, 0.0, -1.0), + ImmVec3d(1.0, 0.0, 0.0), + ImmVec3d(-1.0, 0.0, 0.0), + ImmVec3d(0.0, 1.0, 0.0), + ImmVec3d(0.0, -1.0, 0.0), + ImmVec3d(0.0, 0.0, 1.0), + ImmVec3d(0.0, 0.0, -1.0), ) for (x in 1..10) { for (z in 1..10) { @@ -125,10 +127,10 @@ class World(commander: Ymir) : Closeable { logger.info { "Generating: ${++i}/$total" } val xx = (if (xs) x else -x).toDouble() * interval val zz = (if (zs) z else -z).toDouble() * interval - val origin = Vec3D(xx, Random.nextInt(-3..3).toDouble(), zz) - val visited = mutableSetOf(Vec3D(0.0, 0.0, 0.0)) - val heads = ArrayDeque() - heads.addLast(Vec3D(0.0, 0.0, 0.0)) + val origin = ImmVec3d(xx, Random.nextInt(-3..3).toDouble(), zz) + val visited = mutableSetOf(ImmVec3d(0.0, 0.0, 0.0)) + val heads = ArrayDeque() + heads.addLast(ImmVec3d(0.0, 0.0, 0.0)) while (!heads.isEmpty()) { val head = heads.removeFirst() for (d in directions) { @@ -152,8 +154,6 @@ class World(commander: Ymir) : Closeable { return list } - private operator fun Vec3D.plus(other: Vec3D): Vec3D = Vec3D(x + other.x, y + other.y, z + other.z) - private fun createCube(x: Double, y: Double, z: Double): PhyGeomBox { val cube = createGeomBox(1.0, 1.0, 1.0) cube.setPosition(doubleArrayOf(x, y, z)) diff --git a/vector-math b/vector-math new file mode 160000 index 00000000..d25db430 --- /dev/null +++ b/vector-math @@ -0,0 +1 @@ +Subproject commit d25db430d52bb0ce28d485b1d073db1acc31a99a