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/.idea/kotlinc.xml b/.idea/kotlinc.xml index 131e44d7..2e4da65a 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,7 @@ - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index ba9deec3..6e08a227 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -5,5 +5,6 @@ + \ No newline at end of file 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/AsdIntData.kt b/src/internal/client/kotlin/net/terramodulus/engine/AsdIntData.kt new file mode 100644 index 00000000..401d7344 --- /dev/null +++ b/src/internal/client/kotlin/net/terramodulus/engine/AsdIntData.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.engine + +/** + * ASD Intermediate Data + */ +class AsdIntData { +} diff --git a/src/internal/client/kotlin/net/terramodulus/engine/BaseAsdProcessor.kt b/src/internal/client/kotlin/net/terramodulus/engine/BaseAsdProcessor.kt new file mode 100644 index 00000000..a63aabda --- /dev/null +++ b/src/internal/client/kotlin/net/terramodulus/engine/BaseAsdProcessor.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.engine + +import java.io.InputStream + +/** + * Since for most parts, there is no definite metadata of lengths for all data, + * it is necessary to provide at least processing fragmentation from implementation. + */ +interface BaseAsdProcessor { + /** + * @param data ByteArrayInputStream backed by direct byte buffer + */ + fun process(data: InputStream) +} 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..f4b8ffe5 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/ModelTransform.kt @@ -5,8 +5,11 @@ package net.terramodulus.engine -import net.terramodulus.engine.ferricia.Mui.modelFullScaling -import net.terramodulus.engine.ferricia.Mui.modelSmartScaling +import com.cout970.math.vec2.MutVec2d +import com.cout970.math.vec2.Vec2d +import net.terramodulus.engine.ferricia.Mui.modelGeneralTransform +import net.terramodulus.engine.ferricia.Mui.updateGeneralTransform +import kotlin.properties.Delegates @OptIn(ExperimentalUnsignedTypes::class) sealed class ModelTransform(handles: ULongArray) { @@ -15,19 +18,15 @@ sealed class ModelTransform(handles: ULongArray) { } @OptIn(ExperimentalUnsignedTypes::class) -class SmartScaling private constructor(vararg args: Int) : - ModelTransform(modelSmartScaling(args)) { - - companion object { - fun none(w: Int, h: Int) = SmartScaling(w, h, 0) - - fun x(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 1, ww, hh) - - fun y(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 2, ww, hh) - - fun both(w: Int, h: Int, ww: Int, hh: Int) = SmartScaling(w, h, 3, ww, hh) +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 FullScaling(w: Int, h: Int) : ModelTransform(modelFullScaling(intArrayOf(w, h))) diff --git a/src/internal/client/kotlin/net/terramodulus/engine/Window.kt b/src/internal/client/kotlin/net/terramodulus/engine/Window.kt index ef3c5e54..f21f4dc0 100644 --- a/src/internal/client/kotlin/net/terramodulus/engine/Window.kt +++ b/src/internal/client/kotlin/net/terramodulus/engine/Window.kt @@ -18,11 +18,30 @@ 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) + private val listeners = HashSet<(UInt, UInt) -> Unit>() + + fun addListener(listener: (UInt, UInt) -> Unit) = listeners.add(listener) + fun removeListener(listener: (UInt, UInt) -> Unit) = listeners.remove(listener) + + fun sizeChanged(width: UInt, height: UInt) { + this.width = width + this.height = height + canvas.resizeGLViewport() + listeners.forEach { it(width, height) } + } + 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..f42a5dc0 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 @@ -142,25 +143,18 @@ internal object Mui { external fun setGeomPos(handle: ULong, data: FloatArray) /** - * @param data `[w, h, param, w, h]` - * @return SmartScaling handle pointers + * @param data `[sx, sy, angle, px, py]`; scaling, rotation, position + * @return GeneralTransform handle pointers */ - @JvmName("modelSmartScaling") - external fun modelSmartScaling(data: IntArray): ULongArray + @JvmName("modelGeneralTransform") + external fun modelGeneralTransform(data: DoubleArray): ULongArray /** - * @param data `[w, h]` - * @return FullScaling handle pointers + * @param handle GeneralTransform thin pointer + * @param data `[sx, sy, angle, px, py]`; scaling, rotation, position */ - @JvmName("modelFullScaling") - external fun modelFullScaling(data: IntArray): ULongArray - - /** - * @param data `[x, y]` - * @return SimpleTranslation handle pointers - */ - @JvmName("modelSimpleTranslation") - external fun modelSimpleTranslation(data: FloatArray): ULongArray + @JvmName("updateGeneralTransform") + external fun updateGeneralTransform(handle: ULong, data: DoubleArray) /** * @param data alpha 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..d6ca96da --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/GuiManager.kt @@ -0,0 +1,57 @@ +/* + * 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.agim.LayoutManager +import net.terramodulus.mui.gui.gfx.RenderSystem +import net.terramodulus.mui.gui.agim.ScreenManager +import net.terramodulus.mui.gui.asd.AsdManager +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 asdManager = AsdManager() + val screenManager = ScreenManager(window, renderSystem.handle, asdManager.AgimHandle()) + val layoutManager = LayoutManager() + + private var proceeded = false + + /** + * 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) + if (!proceeded) { + asdManager.process() + proceeded = true + } + } + + /** + * 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..c6f23675 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AbstractPane.kt @@ -0,0 +1,23 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.asd.AsdProcessor +import net.terramodulus.mui.gui.gfx.RectangleF + +abstract class AbstractPane(asdHandle: AsdHandle) : Component(asdHandle), Container { + protected inner class ComponentAsdHandleImpl : AsdHandle.Container() { + override lateinit var rect: RectangleF + override fun registerAsdProcessor(processor: AsdProcessor<*>) = asdHandle.registerAsdProcessor(processor) + } + + final override fun update(muiIoI: ScreenManager.MuiIoI) { + super.update(muiIoI) + layout.update() + layout.components.forEach { it.update(muiIoI) } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AgimoProperty.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AgimoProperty.kt new file mode 100644 index 00000000..107b6360 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AgimoProperty.kt @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import net.terramodulus.util.TypedMap + +/** + * Instances of this class must be immutable, as simple values; + * may be data classes, data objects, enums. + */ +abstract class AgimoProperty + +fun T.getPropertyKey() = AgimoPropertyMap.Key(javaClass) + +class AgimoPropertyMap { + private val properties = TypedMap>() + private val observers = HashMap, + LinkedHashSet<(AgimoProperty?, AgimoProperty?) -> Unit>>() + + class Key(c: Class) : TypedMap.Key(c) + + @Suppress("UNCHECKED_CAST") + fun getProperty(key: Key) = properties[key] as T? + + fun putProperty(key: Key, value: T) { + @Suppress("UNCHECKED_CAST") + val old = properties.put(key, value) as T? + triggerPropertyObservers(key, old, value) + } + + @Suppress("UNCHECKED_CAST") + fun removeProperty(key: Key): T? { + val old = properties.remove(key) as T? + triggerPropertyObservers(key, old, null) + return old + } + + fun containsProperty(key: Key) = properties.containsKey(key) + + /** + * @see java.util.Map.compute + */ + inline fun computeProperty(key: Key, remappingFunction: (Key, T?) -> T?) { + val v = remappingFunction(key, getProperty(key)) + if (v === null) removeProperty(key) else putProperty(key, v) + } + + /** + * @see java.util.Map.computeIfAbsent + */ + inline fun computePropertyIfAbsent(key: Key, mappingFunction: (Key) -> T?) { + if (!containsProperty(key)) { + val v = mappingFunction(key) + if (v !== null) putProperty(key, v) + } + } + + /** + * @see java.util.Map.computeIfPresent + */ + inline fun computePropertyIfPresent(key: Key, remappingFunction: (Key, T) -> T?) { + val v = getProperty(key) + if (v !== null) { + val v = remappingFunction(key, v) + if (v !== null) putProperty(key, v) + } + } + + /** + * @see java.util.Map.merge + */ + inline fun mergeProperty(key: Key, value: T, remappingFunction: (T, T) -> T?) { + val v = getProperty(key) + if (v !== null) { + val v = remappingFunction(v, value) + if (v !== null) putProperty(key, v) + } else putProperty(key, value) + } + + fun observeProperty(key: Key, l: (T?, T?) -> Unit) { + @Suppress("UNCHECKED_CAST") + observers.computeIfAbsent(key) { LinkedHashSet() }.add(l as (AgimoProperty?, AgimoProperty?) -> Unit) + } + + fun unobserveProperty(key: Key, l: (T?, T?) -> Unit) { + observers[key]?.remove(l) + } + + private fun triggerPropertyObservers(key: Key, old: T?, new: T?) { + observers[key]?.forEach { it(old, new) } + } + + @Suppress("UNCHECKED_CAST") + fun asMap(): Map, AgimoProperty> = properties as Map, AgimoProperty> +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AgimoTreeVisitor.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AgimoTreeVisitor.kt new file mode 100644 index 00000000..d6232bdc --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/AgimoTreeVisitor.kt @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import java.util.LinkedList + +internal abstract class AgimoTreeVisitor { + internal fun interface ScreenTreeVisitor { + fun visit(): Sequence + } + + internal fun interface MenuTreeVisitor { + fun visit(): Sequence + } + + class RootNode(screens: Sequence, menus: Sequence) { + val screens = ScreenTree(screens) + val menus = MenuTree(menus) + } + + sealed class ContainerNode(val layout: Layout) { + val elements = LinkedList(layout.components.map { + if (it is AbstractPane) PaneNode(it) else SimpleComponentNode(it) + }.toList()) + } + + sealed interface ComponentNode { + val component: Component + } + + class SimpleComponentNode(override val component: Component) : ComponentNode + + class PaneNode(override val component: AbstractPane) : ContainerNode(component.layout), ComponentNode + + class ScreenTree(screens: Sequence) { + val list = LinkedList(screens.map { ScreenNode(it) }.toList()) + } + + class ScreenNode(val screen: Screen) : ContainerNode(screen.layout) { + val menus = MenuTree(screen.visit().visit()) + } + + class MenuTree(menus: Sequence) { + val list = LinkedList(menus.map { MenuNode(it) }.toList()) + } + + class MenuNode(val menu: Menu) : ContainerNode(menu.layout) +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Alignment.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Alignment.kt new file mode 100644 index 00000000..04e024cd --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Alignment.kt @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim + +import com.cout970.math.vec2.Vec2d +import com.cout970.math.vec2.minus +import com.cout970.math.vec2.plus +import net.terramodulus.mui.gui.gfx.Anchor5 +import net.terramodulus.mui.gui.gfx.Dimension2D +import net.terramodulus.mui.gui.gfx.RectangleD + +const val ALIGN_START = 0.0 +const val ALIGN_CENTER = 0.5 +const val ALIGN_END = 1.0 + +/** + * `anchor` is relative position of positioning anchor from the anchor of the rectangle/dimension.; + * should be within the bounds of the rectangle/dimension. + */ +class AnchorAlignmentHelper { + data class Subject(val rect: RectangleD, val anchor: Vec2d) { + fun alignTarget(target: Target): RectangleD { + // This should be the anchor of the rectangle of target + val anchor = rect.anchor(Anchor5.BottomLeft) + anchor - target.anchor + return RectangleD(anchor.x, anchor.y, target.dim.width, target.dim.height) + } + } + + data class Target(val dim: Dimension2D, val anchor: Vec2d) +} 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 50% 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..f4058b7c 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,29 +1,28 @@ /* - * 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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.RenderSystem /** - * [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. */ -abstract class Component { +abstract class Component(open val asdHandle: AsdHandle) { private val listeners = HashMap, LinkedHashSet<(ComponentEvent) -> Unit>>() - /** - * This should only be modified by [Layout] managers. - */ - open lateinit var rect: ManagedRect - internal set +// /** +// * Caveat: This should only be modified by [Layout][net.terramodulus.mui.gui.agim.Layout] managers. +// */ +// open lateinit var layoutHandle: LayoutHandle +// internal set abstract fun render(renderSystem: RenderSystem) @@ -40,7 +39,7 @@ abstract class Component { listeners[event.javaClass]?.forEach { it(event) } } - fun update(inputSystem: InputSystem) { - TODO() + internal open fun update(muiIoI: ScreenManager.MuiIoI) { + dispatchEvent(ComponentEvent.Update(muiIoI)) } } 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..a80e16d8 --- /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.asd.AsdHandle + +/** + * **AGIM Container**, direct subclasses are explicitly defined. + */ +sealed interface Container { + val asdHandle: AsdHandle//.Container + + 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 71% 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..bfe498cc 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,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 +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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.io.Closeable +import java.util.ArrayDeque /** * [Layout] is always mutable. @@ -15,40 +16,73 @@ 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 containerObserver = { layOut(container.asdHandle) }.apply(container.asdHandle::observeRect) + + 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 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) updated = true + } + + /** + * Whether this [Layout] has been updated (as in [update]) at this moment. + * This should only be updated by [update] and [LayoutManager]. */ - fun update() = layout(container.rect.rect) + internal var updated = false /** * Lays out the managed [components] by this [Layout] manager. * - * Only the `rect`s of the managed `components` should be (re)assigned; - * no other state-changing operations should be done beside this. - * @param rect the rectangle of the container at this moment + * In most cases, only the `layoutHandle`s of the managed `components` should be (re)assigned. + * Otherwise, no other state-changing operations should be done beside this. */ - protected abstract fun layout(rect: RectangleF) + protected abstract fun layOut(handle: LayoutHandle): LayoutComputationGroup + + /** + * Renders this [Layout] with underlying managed [components]. + */ + internal fun render(renderSystem: RenderSystem) = components.forEach { it.render(renderSystem) } /** * Must be invoked when this [Layout] is no longer in use. */ - fun clear() { - container.rect.unobserve(containerObserver) + fun clear() { // Not sure whether there is the necessity to separate this from [close]. + container.asdHandle.unobserveRect(containerObserver) + } + + override fun close() { + clear() } /** @@ -92,10 +126,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 +178,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 - } - - override fun hasNext(): Boolean = untilNotNull() + protected fun componentsNullableSequence(vararg components: () -> Component?) = + sequenceOf(*components).mapNotNull { it() } - 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/LayoutComputation.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutComputation.kt new file mode 100644 index 00000000..09b2f3f6 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutComputation.kt @@ -0,0 +1,45 @@ +/* + * 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.asd.AsdHandle + +class LayoutComputationUnit @PublishedApi internal constructor( + val computation: LayoutHandle.() -> Map, + val dependencies: Map>>, + val results: Map>>, +) { + companion object { + inline operator fun invoke( + dependencies: Map>>.() -> Unit, + results: Map>>.() -> Unit, + noinline computation: LayoutHandle.() -> Map, + ) = LayoutComputationUnit(computation, + mutableMapOf>>().apply(dependencies), + mutableMapOf>>().apply(results), + ) + } +} + +class LayoutComputationGroup @PublishedApi internal constructor( + val conditions: LayoutHandle.() -> Set, + val dependencies: Map>>, +) { + companion object { + inline operator fun invoke( + dependencies: Map>>.() -> Unit, + noinline conditions: LayoutHandle.() -> Set, + ) = LayoutComputationGroup(conditions, + mutableMapOf>>().apply(dependencies), + ) + } +} + +// class LayoutComputationDependency(val key: AgimoPropertyMap.Key<*>) { // Dependencies saved in a Set (immutable) +// } + +// class LayoutComputationResult() { // Results saved in a Map (immutable) +// } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutHandle.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutHandle.kt new file mode 100644 index 00000000..61f4f26c --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutHandle.kt @@ -0,0 +1,16 @@ +/* + * 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.asd.AsdHandle + +sealed interface LayoutHandle { + fun getUnit(handle: AsdHandle): Unit + + sealed interface Unit { + val properties: Map, AgimoProperty> + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutManager.kt new file mode 100644 index 00000000..eb001d00 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/LayoutManager.kt @@ -0,0 +1,153 @@ +/* + * 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.asd.AsdHandle +import kotlin.collections.mutableSetOf + +internal class LayoutManager(screenManager: ScreenManager) { + private val agimoTree = AgimoTree(screenManager) + + // TODO maybe later think of a way to dynamically update nodes partially + private class AgimoTree(screenManager: ScreenManager) : AgimoTreeVisitor() { + private val screenVisitor = screenManager.visitScreens() + private val menuVisitor = screenManager.visitMenus() + var root = RootNode(sequenceOf(), sequenceOf()) + + private fun update() { + root = RootNode(screenVisitor.visit(), menuVisitor.visit()) + } + + private fun listContainers(): Map { + val containers = mutableMapOf() + fun pushElements(node: ContainerNode) { + node.elements.forEach { + if (it is PaneNode) { + containers[it] = it.component.asdHandle + pushElements(it) + } + } + } + fun pushMenu(node: MenuNode) { + containers[node] = node.menu.asdHandle + pushElements(node) + } + fun pushScreen(node: ScreenNode) { + containers[node] = node.screen.asdHandle + node.menus.list.forEach(::pushMenu) + pushElements(node) + } + root.screens.list.forEach(::pushScreen) + root.menus.list.forEach(::pushMenu) + return containers + } + + fun discoverLayoutChanges(): LayoutChanges { + val old = listContainers() + update() + val new = listContainers() + return LayoutChanges( + old.filter { !new.containsKey(it.key) }.entries.associate { it.value to it.key.layout }, + new.filter { !old.containsKey(it.key) }.entries.associate { it.value to it.key.layout }, + new.filter { it.key.layout.updated }.entries.associate { it.value to it.key.layout }, + ) + } + + class LayoutChanges( + val removed: Map, + val added: Map, + val updated: Map, + ) + } + + private data class LayoutNode( + val layout: Layout, + val containerHandle: AsdHandle, + var group: LayoutComputationGroup, + var units: Set, + ) + + private typealias InstancedPropertyMap = MutableMap, V>> + + private operator fun InstancedPropertyMap.get(handle: AsdHandle, key: AgimoPropertyMap.Key<*>) = + this[handle]?.get(key) + + private inline fun , E> InstancedPropertyMap + .push(handle: AsdHandle, key: AgimoPropertyMap.Key<*>, value: E, crossinline ifAbsent: () -> V) = + computeIfAbsent(handle) { mutableMapOf() }.computeIfAbsent(key) { ifAbsent() }.add(value) + +// private fun , E> InstancedPropertyMap +// .push(handle: AsdHandle, key: AgimoPropertyMap.Key<*>, value: E) = +// push(handle, key, value) { mutableSetOf() as MutableCollection } + + private inner class LayoutStates { + private val layouts = mutableMapOf() + private val groupDeps: InstancedPropertyMap> = mutableMapOf() + private val unitDeps: InstancedPropertyMap> = mutableMapOf() + private val groups = mutableMapOf>() + private val results = mutableMapOf>() + private val affectedProps = mutableMapOf>>() + private val changedUnits = mutableSetOf() + + fun getLayout(asdHandle: AsdHandle) = layouts[asdHandle] + + fun addLayout(asdHandle: AsdHandle, layout: LayoutNode) { + if (layouts.containsKey(asdHandle)) throw IllegalStateException() + layouts[asdHandle] = layout + } + + private fun addAffectedProp(handle: AsdHandle, key: AgimoPropertyMap.Key<*>) { + affectedProps.computeIfAbsent(handle) { mutableSetOf() }.add(key) + } + + fun validateCyclicGraphs() { + run { // Groups + data class GroupNode( + val deps: Set>>, + val children: Set>>, + ) + data class PropNode( + val deps: Set, + val children: Set, + ) + val visited = mutableSetOf() + val visiting = mutableSetOf() + layouts.values.map { + GroupNode( + it.group.dependencies.flatMap { (handle, keys) -> keys.map { it -> handle to it } }.toSet(), + it.units.flatMap { it -> + it.results.flatMap { (handle, keys) -> keys.map { it -> handle to it } } + }.toSet(), + ) + } + + } + run { // Units + + } + } + + fun removeLayout(asdHandle: AsdHandle) { + val layout = layouts.remove(asdHandle) + if (layout === null) throw IllegalStateException() + groups.remove(layout.group) + layout.group.dependencies.forEach { (handle, keys) -> + keys.forEach { groupDeps[handle, it]?.remove(layout.group) } + } + layout.units.forEach { unit -> + unit.dependencies.forEach { (handle, keys) -> + keys.forEach { unitDeps[handle, it]?.remove(unit) } + } + changedUnits.add(unit) + } + } + } + + internal fun tick() { + val changes = agimoTree.discoverLayoutChanges() + + } +} 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..009d36bf --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Menu.kt @@ -0,0 +1,68 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.asd.AsdProcessor +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.io.Closeable + +abstract class Menu( + managerHandle: MenuManager.Handle, + final override val asdHandle: AsdHandle.Container, +) : Container, Closeable { + private val listeners = HashMap, LinkedHashSet<(MenuEvent) -> Unit>>() + val handle: Handle = HandleImpl(managerHandle) + + 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 addMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) + + fun removeMenu(menu: Menu) + } + + private inner class HandleImpl(private val managerHandle: MenuManager.Handle) : Handle { + override fun addMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) = managerHandle.addMenu(menu) + + override fun removeMenu(menu: Menu) = managerHandle.removeMenu(menu) + } + + protected inner class ComponentAsdHandleImpl : AsdHandle.Container() { + override lateinit var rect: RectangleF + override fun registerAsdProcessor(processor: AsdProcessor<*>) = asdHandle.registerAsdProcessor(processor) + } + + internal fun render(renderSystem: RenderSystem) { + layout.render(renderSystem) + } + + internal fun update(muiIoI: ScreenManager.MuiIoI) { + dispatchEvent(MenuEvent.Update(muiIoI)) + layout.update() + layout.components.forEach { it.update(muiIoI) } + } + + /** + * 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..70870f85 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/MenuManager.kt @@ -0,0 +1,68 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.asd.AsdProcessor +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.util.ArrayDeque + +class MenuManager internal constructor(private val asdHandle: (AsdProcessor<*>) -> Unit) { + 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: (Handle, AsdHandle) -> Menu) + + fun removeMenu(menu: Menu) + } + + private inner class HandleImpl : Handle { + override fun addMenu(menu: (Handle, AsdHandle) -> Menu) { + menuQueue.add(MenuOperation.Add { menu(handle, MenuAsdHandleImpl()) }) + } + + override fun removeMenu(menu: Menu) { + menuQueue.add(MenuOperation.Remove(menu)) + } + } + + private inner class MenuAsdHandleImpl : AsdHandle.Menu() { + override lateinit var rect: RectangleF + override fun registerAsdProcessor(processor: AsdProcessor<*>) = asdHandle(processor) + } + + internal fun update(muiIoI: ScreenManager.MuiIoI) { + menuQueue.forEach { it.apply(menus) } + menuQueue.clear() + } + + internal fun render(renderSystem: RenderSystem, screenManager: ScreenManager) { + menus.forEach { it.render(renderSystem) } + } + + internal fun visit() = AgimoTreeVisitor.MenuTreeVisitor { + menus.asSequence() + } +} 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..441b618f --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/Screen.kt @@ -0,0 +1,85 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.asd.AsdProcessor +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.mui.gui.gfx.RenderSystem +import java.io.Closeable + +abstract class Screen( + managerHandle: ScreenManager.Handle, + final override val asdHandle: AsdHandle.Container +) : Container, Closeable { + private val listeners = HashMap, LinkedHashSet<(ScreenEvent) -> Unit>>() + private val menuManager = MenuManager(asdHandle::registerAsdProcessor) + val handle: Handle = HandleImpl(managerHandle) + +// init { +// // TODO there should be an entry for background, maybe it sets background for entire render background? +// asdHandle.registerAsdProcessor() +// } + + 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: (MenuManager.Handle, AsdHandle) -> Menu) + + fun removeMenu(menu: Menu) + + fun addTopMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) + + fun removeTopMenu(menu: Menu) + } + + private inner class HandleImpl(private val managerHandle: ScreenManager.Handle) : Handle { + override fun addMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) = menuManager.handle.addMenu(menu) + + override fun removeMenu(menu: Menu) = menuManager.handle.removeMenu(menu) + + override fun addTopMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) = managerHandle.addMenu(menu) + + override fun removeTopMenu(menu: Menu) = managerHandle.removeMenu(menu) + } + + protected inner class ComponentAsdHandleImpl : AsdHandle.Container() { + override lateinit var rect: RectangleF + override fun registerAsdProcessor(processor: AsdProcessor<*>) = asdHandle.registerAsdProcessor(processor) + } + + internal fun update(muiIoI: ScreenManager.MuiIoI) { + dispatchEvent(ScreenEvent.Update(muiIoI)) + layout.update() + layout.components.forEach { it.update(muiIoI) } + } + + internal fun render(renderSystem: RenderSystem, screenManager: ScreenManager) { + layout.render(renderSystem) + menuManager.render(renderSystem, screenManager) + } + + /** + * Cleans up and closes any used resources in this session. + */ + final override fun close() { + dispatchEvent(ScreenEvent.Close) + } + + internal fun visit() = menuManager.visit() +} 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..829703cd --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/ScreenManager.kt @@ -0,0 +1,249 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.asd.AsdManager +import net.terramodulus.mui.gui.asd.AsdProcessor +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.mui.kui.InputSystem + +class ScreenManager internal constructor( + window: Window, + private val renderSystemHandle: RenderSystem.Handle, + private val asdManagerHandle: AsdManager.AgimHandle, +) { + /** + * FILO screen stack; the top-most screen instance is in the last. + */ + private val screens = ArrayDeque() + private val screenOpQueue = ArrayDeque() + private val menuManager = MenuManager(asdManagerHandle::registerProcessor) + + private val asdHandles = HashSet() + private var viewportRect = RectangleF(0F, 0F, window.width.toFloat(), window.height.toFloat()) + + init { + window.addListener { w, h -> + viewportRect = RectangleF(0F, 0F, w.toFloat(), h.toFloat()) + asdHandles.forEach { it.triggerRectObservers() } // already referring viewportRect + } + } + + // TODO Basically need to hide those rectangles in LayoutManager instead +// inner class LayoutHandle.Screen : ManagedRect(), Closeable { +// override var value: RectangleF by Delegates.observable(viewportRect.value) { _, _, newValue -> +// observers.forEach { it(newValue) } +// } +// private set +// +// } + + val handle: Handle = HandleImpl() + + init { + screens.add(LaunchingScreen(handle, ScreenAsdHandleImpl(), renderSystemHandle)) + } + + private sealed interface ScreenOperation { + fun apply(handle: RenderSystem.Handle, screens: ArrayDeque, asdHandles: HashSet) + + /** + * 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, + asdHandles: HashSet + ) { + if (n >= screens.size) { + throw IllegalStateException("`n` >= screens.size") + } + + for (i in 1..n) { + screens.removeLast().apply { + close() + asdHandles.remove(asdHandle) + } + } + } + } + + /** + * Opens the `screen` + */ + class Open(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply( + handle: RenderSystem.Handle, + screens: ArrayDeque, + asdHandles: HashSet + ) { + 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, + asdHandles: HashSet + ) { + 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, + asdHandles: HashSet + ) { + val it = screens.asReversed().listIterator() + while (it.hasNext()) { + val e = it.next() + if (e == screen) { + break + } else { + it.remove() + e.close() + asdHandles.remove(e.asdHandle) + } + } + } + } + + /** + * Clears [screens] then opens the `screen` + */ + class Reset(val screen: (RenderSystem.Handle) -> Screen) : ScreenOperation { + override fun apply( + handle: RenderSystem.Handle, + screens: ArrayDeque, + asdHandles: HashSet + ) { + screens.asReversed().forEach { it.close() } + screens.clear() + asdHandles.clear() + screens.add(screen(handle)) + } + } + } + + sealed interface Handle { + /** + * @see ScreenOperation.Exit + */ + fun exit(n: Int) + + /** + * @see ScreenOperation.Open + */ + fun open(screen: (Handle, AsdHandle.Screen, RenderSystem.Handle) -> Screen) + + /** + * It is not recommended to use this in general scenarios. + * @see ScreenOperation.OpenBefore + */ + fun openBefore(target: Screen, screen: (Handle, AsdHandle.Screen, RenderSystem.Handle) -> Screen) + + /** + * @see ScreenOperation.ExitTo + */ + fun exitTo(screen: Screen) + + /** + * @see ScreenOperation.Reset + */ + fun reset(screen: (Handle, AsdHandle.Screen, RenderSystem.Handle) -> Screen) + + fun addMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) + + fun removeMenu(menu: Menu) + } + + private inner class HandleImpl : Handle { + override fun exit(n: Int) { + screenOpQueue.add(ScreenOperation.Exit(n)) + } + + override fun open(screen: (Handle, AsdHandle.Screen, RenderSystem.Handle) -> Screen) { + screenOpQueue.add(ScreenOperation.Open { screen(handle, ScreenAsdHandleImpl(), it) }) + } + + override fun openBefore(target: Screen, screen: (Handle, AsdHandle.Screen, RenderSystem.Handle) -> Screen) { + screenOpQueue.add(ScreenOperation.OpenBefore(target) { screen(handle, ScreenAsdHandleImpl(), it) }) + } + + override fun exitTo(screen: Screen) { + screenOpQueue.add(ScreenOperation.ExitTo(screen)) + } + + override fun reset(screen: (Handle, AsdHandle.Screen, RenderSystem.Handle) -> Screen) { + screenOpQueue.add(ScreenOperation.Reset { screen(handle, ScreenAsdHandleImpl(), it) }) + } + + override fun addMenu(menu: (MenuManager.Handle, AsdHandle) -> Menu) = menuManager.handle.addMenu(menu) + + override fun removeMenu(menu: Menu) = menuManager.handle.removeMenu(menu) + } + + private inner class ScreenAsdHandleImpl : AsdHandle.Screen() { + init { + asdHandles.add(this) + } + + override var rect get() = viewportRect + set(value) = throw UnsupportedOperationException() // should never be invoked + + override fun registerAsdProcessor(processor: AsdProcessor<*>) = asdManagerHandle.registerProcessor(processor) + } + + /** + * MUI Interoperability Interface + */ + class MuiIoI internal constructor( + val renderSystem: RenderSystem, + val screenManager: ScreenManager, + val inputSystem: InputSystem, + ) + + internal fun update(muiManager: MuiManager) { + screenOpQueue.forEach { it.apply(renderSystemHandle, screens, asdHandles) } + screenOpQueue.clear() + val ioi = MuiIoI(muiManager.guiManager.renderSystem, this, muiManager.kuiManager.inputSystem) + menuManager.update(ioi) + screens.forEach { it.update(ioi) } + } + + internal fun render(renderSystem: RenderSystem) { + menuManager.render(renderSystem, this) + screens.forEach { it.render(renderSystem, this) } + } + + internal fun visitScreens() = AgimoTreeVisitor.ScreenTreeVisitor { + screens.asSequence() + } + + internal fun visitMenus() = menuManager.visit() +} 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..bd5ddd73 --- /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 muiIoI: ScreenManager.MuiIoI) : 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..0e22e339 --- /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 muiIoI: ScreenManager.MuiIoI) : 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..bf79e7cd --- /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 muiIoI: ScreenManager.MuiIoI) : 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..b80d95ba --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/AbsoluteLayout.kt @@ -0,0 +1,48 @@ +/* + * 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.asd.AsdHandle +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(handle: AsdHandle) { + component.asdHandle.rect = config.layOut(handle.rect) + component.asdHandle.triggerRectObservers() + } +} 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..88a6d0f3 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/BlankComponent.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.gfx.RenderSystem +import net.terramodulus.mui.gui.agim.Component +import net.terramodulus.mui.gui.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.Dimension2F + +/** + * This can act as a placeholder [Component] in a [Layout][net.terramodulus.mui.gui.agim.Layout]. + */ +class BlankComponent(asdHandle: AsdHandle, dim: Dimension2F) : Component(asdHandle) { + 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..ff26b95b --- /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.asd.AsdHandle + +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(handle: AsdHandle) { + 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..e981ca09 --- /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.asd.AsdHandle + +class FlexibleBoxLayout(container: Container) : Layout(container) { + override val components = TODO("Not yet implemented") + + override fun layOut(handle: AsdHandle) { + 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 76% 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..2a12dbfe 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,49 @@ * 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.agim.event.ScreenEvent +import net.terramodulus.mui.gui.asd.AsdHandle +import net.terramodulus.mui.gui.asd.AsdProcessor +import net.terramodulus.mui.gui.gfx.Direction6C +import net.terramodulus.mui.gui.gfx.RectangleF +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,18 +59,28 @@ 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, + asdHandle: AsdHandle.Container, +) : Screen(managerHandle, asdHandle) { private val geoShaders = camera.loadGeoShaders( getResourceAsString("/gwr_geo.vsh"), getResourceAsString("/gwr_geo.fsh"), ) private lateinit var player: PlayerVoidGeom + override val layout: AbsoluteLayout init { renderSystemHandle.setBackgroundColor(0F, 0F, 0F, 0F) core.world = World(Ymir()) - addComponent(GameplayRenderer()) + layout = AbsoluteLayout(this, GameplayRenderer(), AbsoluteLayout.Config.Full) + addListener(ScreenEvent.Update::class.java) { + update0(it.muiIoI) + } // val progressBarEdge = GeomComponent(GuiLine(0, 100, 100, 100, 255, 255, 255, 255)) // addComponent(progressBarEdge) // val progressBarCtnVal = GuiLine(0, 101, 0, 101, 255, 255, 0, 255) @@ -86,11 +106,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 +122,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 +134,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 +162,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 - } - - private fun Vec3D.normalize(): Vec3D { - val mag = mag() - return Vec3D(x / mag, y / mag, z / mag) + override var pos: Vec3d by phyBody::pos } - 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,17 +181,18 @@ 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) { + private fun update0(muiIoI: ScreenManager.MuiIoI) { + val inputSystem = muiIoI.inputSystem // Those keys are not related to GUI, so they are fine to be here. if (inputSystem.condition { Q.justDown() }) { // Query position of sphere @@ -292,7 +297,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,12 +325,12 @@ 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() { + + private inner class GameplayRenderer : Component(ComponentAsdHandleImpl()) { override fun render(renderSystem: RenderSystem) { if (core.world != null) core.world!!.objects.values.sortedWith( compareBy { it.pos.y }.thenBy { it.pos.z } @@ -334,6 +339,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..865c06a2 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GeomComponent.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.agim.Component +import net.terramodulus.mui.gui.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.GuiGeometry +import net.terramodulus.mui.gui.gfx.RenderSystem + +class GeomComponent(val geom: GuiGeometry, asdHandle: AsdHandle) : Component(asdHandle) { + 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..d2d8077f --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/GraphicsComponent.kt @@ -0,0 +1,28 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.GuiSprite +import net.terramodulus.mui.gui.gfx.RenderSystem + +sealed interface GraphicsComponent + +class SpriteComponent(val sprite: GuiSprite, asdHandle: AsdHandle) : Component(asdHandle), GraphicsComponent { + override fun render(renderSystem: RenderSystem) { + sprite.render(renderSystem) + } +} + +@Suppress("CanSealedSubClassBeObject") +class CanvasComponent(override val layout: Layout, asdHandle: AsdHandle) : AbstractPane(asdHandle), GraphicsComponent { + override fun render(renderSystem: RenderSystem) { + TODO("Not yet implemented") + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/IntrinsicProperties.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/IntrinsicProperties.kt new file mode 100644 index 00000000..abd0e86d --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/IntrinsicProperties.kt @@ -0,0 +1,30 @@ +/* + * 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.AgimoProperty +import net.terramodulus.mui.gui.asd.AsdHandle +import net.terramodulus.util.gcd + +/** + * @constructor Simple constructor without any preprocessing + */ +data class IntrinsicRatioProperty(val width: UInt, val height: UInt) : AgimoProperty() { + companion object { + val KEY = AsdHandle.PropertyKey(IntrinsicRatioProperty::class.java) + fun compute(width: UInt, height: UInt): IntrinsicRatioProperty { + val gcd = gcd(width, height) + return IntrinsicRatioProperty(width / gcd, height / gcd) + } + } +} + +class IntrinsicDimensionsProperty(val width: UInt, val height: UInt) : AgimoProperty() { + companion object { + val KEY = AsdHandle.PropertyKey(IntrinsicDimensionsProperty::class.java) + } + fun computeRatio() = IntrinsicRatioProperty.compute(width, height) +} 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..dfe83d18 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/LaunchingScreen.kt @@ -0,0 +1,85 @@ +/* + * 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.AlphaFilter +import net.terramodulus.mui.gui.gfx.Dimension2I +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 +import net.terramodulus.mui.gui.asd.AsdHandle + +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, + asdHandle: AsdHandle.Container, + renderSystemHandle: RenderSystem.Handle, +) : Screen(managerHandle, asdHandle) { + 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 + ), ComponentAsdHandleImpl()).apply { + geom.add(alphaFilter) + }, AbsoluteLayout.Config.Full)) + } + layout.update { + add(AbsoluteLayout(this@LaunchingScreen, SpriteComponent(GuiSprite( + RectangleI(0, 0, 512, 128), + renderSystemHandle.loadTexture("/studio_logo.png"), + ), ComponentAsdHandleImpl()).apply { + sprite.add(alphaFilter) + sprite.add(SmartScaling.both(REF_SIZE.width, REF_SIZE.height, 512, 128)) + }, AbsoluteLayout.Config.Full)) + } + + 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.muiIoI.screenManager.handle.reset(::ResourceLoadingScreen) + } + } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ObjectFitLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ObjectFitLayout.kt new file mode 100644 index 00000000..ef64f4ba --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ObjectFitLayout.kt @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import com.cout970.math.vec2.ImmVec2d +import net.terramodulus.mui.gui.agim.AnchorAlignmentHelper +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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.Dimension2D +import net.terramodulus.mui.gui.gfx.RectangleF +import kotlin.math.max +import kotlin.math.min + +/** + * Only applicable to AGIMO elements with intrinsic ratios. + */ +class ObjectFitLayout( + container: Container, + component: Component, + private var mode: ModeConfig, + private var alignment: AlignmentConfig, +) : Layout(container) { + override val components = componentsSequence(::component) + var component = component + private set + + // TODO should Fill be implemented? + enum class ModeConfig { + Contain { + override fun compute(container: RectangleF, component: IntrinsicRatioProperty): Dimension2D { + val w = container.width.toDouble() / component.width.toDouble() + val h = container.height.toDouble() / component.height.toDouble() + val scale = min(w, h) + return Dimension2D( + component.width.toDouble() * scale, + component.height.toDouble() * scale, + ) + } + }, + Cover { + override fun compute(container: RectangleF, component: IntrinsicRatioProperty): Dimension2D { + val w = container.width.toDouble() / component.width.toDouble() + val h = container.height.toDouble() / component.height.toDouble() + val scale = max(w, h) + return Dimension2D( + component.width.toDouble() * scale, + component.height.toDouble() * scale, + ) + } + }, + ; + + abstract fun compute(container: RectangleF, component: IntrinsicRatioProperty): Dimension2D + } + + /** + * Range within `[0,1]` + */ + data class AlignmentConfig(val x: Double, val y: Double) { + companion object { + val DEFAULT = AlignmentConfig(0.5, 0.5) + fun withX(x: Double) = AlignmentConfig(x, 0.5) + fun withY(y: Double) = AlignmentConfig(y, 0.5) + } + } + + fun update(component: Component) { + operate { + this@ObjectFitLayout.component = component + } + } + + interface Config { + var mode: ModeConfig + var alignment: AlignmentConfig + } + + fun update(operation: Config.() -> Unit) { + operate { + operation(object : Config { + override var mode: ModeConfig by this@ObjectFitLayout::mode + override var alignment: AlignmentConfig by this@ObjectFitLayout::alignment + }) + } + } + + private operator fun RectangleF.times(other: AlignmentConfig) = ImmVec2d(width * other.x, height * other.y) + private operator fun Dimension2D.times(other: AlignmentConfig) = ImmVec2d(width * other.x, height * other.y) + + override fun layOut(handle: AsdHandle) { + val ratio = component.asdHandle.getProperty(IntrinsicRatioProperty.KEY) + if (ratio === null) + throw IllegalStateException("component has no intrinsic ratio") + val target = mode.compute(handle.rect, ratio) + component.asdHandle.rect = AnchorAlignmentHelper.Subject(handle.rect.toDouble(), handle.rect * alignment) + .alignTarget(AnchorAlignmentHelper.Target(target, target * alignment)).toFloat() + component.asdHandle.triggerRectObservers() + } +} 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/ScaledLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ScaledLayout.kt new file mode 100644 index 00000000..bf01443e --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ScaledLayout.kt @@ -0,0 +1,108 @@ +/* + * 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.agim.Container +import net.terramodulus.mui.gui.agim.Layout +import net.terramodulus.mui.gui.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.Dimension2D + +/** + * Scaling intrinsic dimensions of AGIMO element for intrinsic dimensions for this object. + */ +class ScaledLayout( + container: Container, + component: Component, + private var config: Config, +) : Layout(container) { + override val components = componentsSequence(::component) + var component = component + private set + + sealed class Config { + abstract fun compute(dim: IntrinsicDimensionsProperty): Dimension2D + + /** + * Scale both dimensions by the same scaling + * @param scale `> 0` + */ + data class Scale(val scale: Double) : Config() { + override fun compute(dim: IntrinsicDimensionsProperty) = + Dimension2D(dim.width.toDouble() * scale, dim.height.toDouble() * scale) + } + + class Compute private constructor(private val x: Value, private val y: Value) : Config() { + private object MathEnvImpl : MathEnv + + constructor(x: MathEnv.() -> Value, y: MathEnv.() -> Value) : this(x(MathEnvImpl), y(MathEnvImpl)) + + sealed interface Value { + fun compute(dim: IntrinsicDimensionsProperty): Double + + operator fun plus(that: Value) = Operator.Plus(this, that) + operator fun minus(that: Value) = Operator.Minus(this, that) + operator fun times(that: Value) = Operator.Times(this, that) + operator fun div(that: Value) = Operator.Div(this, that) + } + + sealed class Operator private constructor() : Value { + data class Plus(val a: Value, val b: Value) : Operator() { + override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) + b.compute(dim) + } + data class Minus(val a: Value, val b: Value) : Operator() { + override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) - b.compute(dim) + } + data class Times(val a: Value, val b: Value) : Operator() { + override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) * b.compute(dim) + } + data class Div(val a: Value, val b: Value) : Operator() { + override fun compute(dim: IntrinsicDimensionsProperty) = a.compute(dim) / b.compute(dim) + } + } + + sealed class Param private constructor() : Value { + data class Num(val value: Double) : Param() { + override fun compute(dim: IntrinsicDimensionsProperty) = value + } + data object DimX : Param() { + override fun compute(dim: IntrinsicDimensionsProperty) = dim.width.toDouble() + } + data object DimY : Param() { + override fun compute(dim: IntrinsicDimensionsProperty) = dim.height.toDouble() + } + } + + sealed interface MathEnv { + val dimX: Param get() = Param.DimX + val dimY: Param get() = Param.DimY + fun num(value: Double) = Param.Num(value) + } + + override fun compute(dim: IntrinsicDimensionsProperty) = Dimension2D(x.compute(dim), y.compute(dim)) + } + } + + fun update(component: Component) { + operate { + this@ScaledLayout.component = component + } + } + + fun update(operation: (Config) -> Config) { + operate { + config = operation(config) + } + } + + override fun layOut(handle: AsdHandle) { + val dim = component.asdHandle.getProperty(IntrinsicDimensionsProperty.KEY) + if (dim === null) + throw IllegalStateException("component has no intrinsic dimensions") + config.compute(dim) + TODO("Then?") + } +} 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..73587106 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/ScrollPane.kt @@ -0,0 +1,20 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.RenderSystem + +class ScrollPane(asdHandle: AsdHandle) : AbstractPane(asdHandle) { + 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 86% 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..1c22a726 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,15 @@ /* - * 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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.RectangleF /** * Common implementation that is either [ColumnLayout] or [RowLayout]. @@ -59,7 +60,7 @@ class ColumnLayout private constructor(container: Container, elements: ElementLi } } - override fun layout(rect: RectangleF) { + override fun layOut(handle: AsdHandle) { elements.forEach { TODO() } } } @@ -87,7 +88,7 @@ class RowLayout private constructor(container: Container, elements: ElementList< } } - override fun layout(rect: RectangleF) { + override fun layOut(handle: AsdHandle) { elements.forEach { TODO() } } } diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SimplePane.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SimplePane.kt new file mode 100644 index 00000000..4137b804 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SimplePane.kt @@ -0,0 +1,17 @@ +/* + * 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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.RenderSystem + +class SimplePane(asdHandle: AsdHandle, override var layout: Layout) : AbstractPane(asdHandle) { + override fun render(renderSystem: RenderSystem) { + layout.render(renderSystem) + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SingletonLayout.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SingletonLayout.kt new file mode 100644 index 00000000..4b7e5bfd --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/agim/impl/SingletonLayout.kt @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.agim.impl + +import com.cout970.math.vec2.ImmVec2d +import net.terramodulus.mui.gui.agim.AnchorAlignmentHelper +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.asd.AsdHandle +import net.terramodulus.mui.gui.gfx.Dimension2D +import net.terramodulus.mui.gui.gfx.InsetsF +import net.terramodulus.mui.gui.gfx.RectangleF +import kotlin.math.max +import kotlin.math.min + +class SingletonLayout(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 + + sealed class Absolute private constructor() : Config() { + data object Full : Config() { + override fun layOut(container: RectangleF) = container + } + + data class Insets(var insets: InsetsF) : Config() { + override fun layOut(container: RectangleF) = container - insets + } + } + + enum class ObjectFit { + Contain { + override fun compute(container: RectangleF, component: IntrinsicRatioProperty): Dimension2D { + val w = container.width.toDouble() / component.width.toDouble() + val h = container.height.toDouble() / component.height.toDouble() + val scale = min(w, h) + return Dimension2D( + component.width.toDouble() * scale, + component.height.toDouble() * scale, + ) + } + }, + Cover { + override fun compute(container: RectangleF, component: IntrinsicRatioProperty): Dimension2D { + val w = container.width.toDouble() / component.width.toDouble() + val h = container.height.toDouble() / component.height.toDouble() + val scale = max(w, h) + return Dimension2D( + component.width.toDouble() * scale, + component.height.toDouble() * scale, + ) + } + }, + ; + + abstract fun compute(container: RectangleF, component: IntrinsicRatioProperty): Dimension2D + + private operator fun RectangleF.times(other: ObjectFitLayout.AlignmentConfig) = ImmVec2d(width * other.x, height * other.y) + private operator fun Dimension2D.times(other: ObjectFitLayout.AlignmentConfig) = ImmVec2d(width * other.x, height * other.y) + + override fun layOut(handle: AsdHandle) { + val ratio = component.asdHandle.getProperty(IntrinsicRatioProperty.KEY) + if (ratio === null) + throw IllegalStateException("component has no intrinsic ratio") + val target = mode.compute(handle.rect, ratio) + component.asdHandle.rect = AnchorAlignmentHelper.Subject(handle.rect.toDouble(), handle.rect * alignment) + .alignTarget(AnchorAlignmentHelper.Target(target, target * alignment)).toFloat() + component.asdHandle.triggerRectObservers() + } + } + + /** + * Range within `[0,1]` + */ + data class AlignmentConfig(val x: Double, val y: Double) { + companion object { + val DEFAULT = AlignmentConfig(0.5, 0.5) + fun withX(x: Double) = AlignmentConfig(x, 0.5) + fun withY(y: Double) = AlignmentConfig(y, 0.5) + } + } + + } + + fun update(component: Component) { + operate { + this@SingletonLayout.component = component + } + } + + fun update(operation: (Config) -> Config) { + operate { + config = operation(config) + } + } + + override fun layOut(handle: AsdHandle) { + component.asdHandle.rect = config.layOut(handle.rect) + component.asdHandle.triggerRectObservers() + } +} 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/gui/asd/AsdHandle.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdHandle.kt new file mode 100644 index 00000000..cadc2284 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdHandle.kt @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.asd + +import net.terramodulus.mui.gui.agim.AgimoProperty +import net.terramodulus.mui.gui.agim.AgimoPropertyMap +import net.terramodulus.mui.gui.gfx.RectangleF +import net.terramodulus.util.TypedAnchorMap +import java.util.function.BiFunction + +/** + * Since rectangles are modified only during layout processing, + * further follow-ups must not be deferred to next tick. + */ +abstract class AsdHandle internal constructor() { + /** + * Caveat: Must only be modified by [Layout][net.terramodulus.mui.gui.agim.Layout]. + * When modified, [triggerRectObservers] must be invoked. + */ + abstract var rect: RectangleF + internal set + + protected val rectObservers = LinkedHashSet<() -> Unit>() + + internal fun observeRect(observer: () -> Unit) { + rectObservers.add(observer) + } + + internal fun unobserveRect(observer: () -> Unit) { + rectObservers.remove(observer) + } + + internal fun triggerRectObservers() { + rectObservers.forEach { it() } + } + + /** + * Permanent AGIMO Properties + */ + val properties = AgimoPropertyMap() + + /** + * Registers ASD Processors from AGIMOs to [AsdManager]. + */ + abstract fun registerAsdProcessor(processor: AsdProcessor<*>) + + // TODO likely those below are useless + abstract class Container : AsdHandle() {} + + abstract class Menu : Container() {} + + abstract class Screen : Container() {} + +// interface Component : LayoutHandle {} // Do we need this? +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdIr.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdIr.kt new file mode 100644 index 00000000..355c613c --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdIr.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.asd + +/** + * ASD Intermediate Representation (IR) + */ +class AsdIr { +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdManager.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdManager.kt new file mode 100644 index 00000000..6d6e75af --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdManager.kt @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.asd + +import net.terramodulus.mui.gui.agim.AgimoTreeVisitor + +// TODO Should ASD affect choices of Layout? +// There are two proposals: +// - Each Container manages a Layout, then the Layout is put into LayoutManager/LayoutViewport +// - Each Container is totally managed by AsdManager except for Facets, but this rather complicated for ASD +internal class AsdManager internal constructor() { + companion object { + // TODO temporary demonstrative testing default + internal fun default(): AsdManager = AsdManager() + } + + private val processors = HashSet>() + + internal fun registerProcessor(processor: AsdProcessor<*>) { + processors.add(processor) + } + + internal inner class AgimHandle { + internal fun registerProcessor(processor: AsdProcessor<*>) = this@AsdManager.registerProcessor(processor) + } + + internal fun process() { + fun process(processor: AsdProcessor) = processor.processDefined(processor.produceDefined()) + processors.forEach { process(it) } + } + + // TODO What should be the use of this + private inner class TreeVisitor(visitorScreens: ScreenTreeVisitor, visitorMenus: MenuTreeVisitor) : AgimoTreeVisitor() { + init { + val root = RootNode(visitorScreens.visit(), visitorMenus.visit()) + } + } +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdProcessor.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdProcessor.kt new file mode 100644 index 00000000..4a50dc05 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/AsdProcessor.kt @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.mui.gui.asd + +import net.terramodulus.engine.BaseAsdProcessor + +// TODO in reality, only binary data are consumed, but +// in testing environment, objects are created directly +// - most likely, each AGIMO and Layout register necessary Processor +// to consume input data and configure its managed instance of AGIMO/Layout +abstract class AsdProcessor { + companion object { + fun get(): BaseAsdProcessor = TODO("This processor's only use is to provide InputStream") + } + + // TODO Temporary implementation to configure ASD without binary data but produced configs + abstract fun processDefined(definitions: T) + + // TODO Temporary implementation to produce defined configurations for testing and demo + abstract fun produceDefined(): T +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/MenuStyles.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/MenuStyles.kt new file mode 100644 index 00000000..0022ce5d --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/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.asd + +class MenuStyles { +} diff --git a/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/ScreenStyles.kt b/src/kernel/client/kotlin/net/terramodulus/mui/gui/asd/ScreenStyles.kt new file mode 100644 index 00000000..76571c6e --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/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.asd + +class ScreenStyles { +} 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 65% 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..ede9a02c 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,14 +1,16 @@ /* - * 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) data class Dimension2F(val width: Float, val height: Float) +data class Dimension2D(val width: Double, val height: Double) + data class Dimension3I(val width: Int, val height: Int, val length: Int) data class Dimension3F(val width: Float, val height: Float, val length: Float) 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..f661b451 --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/ManagedRect.kt @@ -0,0 +1,37 @@ +/* + * 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>() + + // What is this? + 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..6ba2783c --- /dev/null +++ b/src/kernel/client/kotlin/net/terramodulus/mui/gui/gfx/Rectangle.kt @@ -0,0 +1,218 @@ +/* + * 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.ImmVec2d +import com.cout970.math.vec2.ImmVec2f +import com.cout970.math.vec2.ImmVec2i +import com.cout970.math.vec2.Vec2 +import com.cout970.math.vec2.Vec2d +import com.cout970.math.vec2.Vec2f +import com.cout970.math.vec2.Vec2i + +/** + * Rectangle in a coordinate system with (0, 0) on the bottom left. + * The anchor of the rectangle is the bottom-left corner. + */ +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 toInt(): RectangleI + abstract fun toFloat(): RectangleF + abstract fun toDouble(): RectangleD +} + +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.minus(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 toInt() = this + override fun toFloat() = RectangleF(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) + override fun toDouble() = RectangleD(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble()) +} + +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.minus(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 toInt() = RectangleI(x.toInt(), y.toInt(), width.toInt(), height.toInt()) + override fun toFloat() = this + override fun toDouble() = RectangleD(x.toDouble(), y.toDouble(), width.toDouble(), height.toDouble()) +} + +data class RectangleD( + override val x: Double, + override val y: Double, + override val width: Double, + override val height: Double +) : Rectangle(x, y, width, height) { + override fun constructor( + x: Double, + y: Double, + width: Double, + height: Double + ) = RectangleD(x, y, width, height) + + override fun vec2(x: Double, y: Double) = ImmVec2d(x, y) + + override fun Double.plus(other: Double) = this + other + override fun Double.minus(other: Double) = this - other + override fun Double.div(other: Int) = this / other + + override val Vec2d.x: Double by ::x + override val Vec2d.y: Double by ::y + override val size = Dimension2D(width, height) + + override fun toInt() = RectangleI(x.toInt(), y.toInt(), width.toInt(), height.toInt()) + override fun toFloat() = RectangleF(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) + override fun toDouble() = 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/util/Math.kt b/src/kernel/common/kotlin/net/terramodulus/util/Math.kt new file mode 100644 index 00000000..fb7265b8 --- /dev/null +++ b/src/kernel/common/kotlin/net/terramodulus/util/Math.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.util + +tailrec fun gcd(a: Int, b: Int): Int { + return if (b == 0) a else gcd(b, a % b) +} + +tailrec fun gcd(a: UInt, b: UInt): UInt { + return if (b == 0u) a else gcd(b, a % b) +} + +tailrec fun gcd(a: Long, b: Long): Long { + return if (b == 0L) a else gcd(b, a % b) +} + +tailrec fun gcd(a: ULong, b: ULong): ULong { + return if (b == 0uL) a else gcd(b, a % b) +} diff --git a/src/kernel/common/kotlin/net/terramodulus/util/TypedMap.kt b/src/kernel/common/kotlin/net/terramodulus/util/TypedMap.kt new file mode 100644 index 00000000..28a3e58a --- /dev/null +++ b/src/kernel/common/kotlin/net/terramodulus/util/TypedMap.kt @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 TerraModulus Team and Contributors + * SPDX-License-Identifier: LGPL-3.0-only + */ + +package net.terramodulus.util + +import java.util.function.BiFunction +import java.util.function.Function + +/** + * A map with access by references to unique keys with type of values specified, + * matching the pattern of **Typed Anchor Dynamic Mapping**. + * Localized constrains may be applied by copying this universal structure.\ + * All bulk functions are all unsupported for type safety. + * @param T base class for map values + */ +open class TypedMap> + private constructor(private val map: MutableMap) : MutableMap by map { + constructor() : this(HashMap()) + + open class Simple : TypedMap>() + + /** + * A unique, immutable key that defines and enforces the type for values of a [TypedMap]. + * By hiding public access to the map instance, constrains may be applied by subclassing this. + */ + open class Key(val type: Class) { + companion object { + inline operator fun invoke() = Key(T::class.java) + } + override fun hashCode() = type.hashCode() + override fun equals(other: Any?): Boolean { + if (other === null) return false + if (this === other) return true + if (other !is Key<*>) return false + if (javaClass != other.javaClass) return false + return type == other.type + } + } +} 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