From c117b6d146321c0a8378de1848ab87f9d41ed7d2 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 30 Apr 2025 15:11:18 +0200 Subject: [PATCH 1/5] feat: basic api structure --- .gitignore | 3 +- .../bbconv/api/entity/RenderedBone.kt | 84 ++++++++ .../bbconv/api/entity/RenderedEntity.kt | 24 +++ .../bbconv/config/OpenModelFormat.kt | 71 ++++--- .../bbconv/config/OpenModelLoader.kt | 15 ++ .../resourcepack/bbconv/util/Interpolation.kt | 67 +++++++ build.gradle.kts | 41 ++++ .../wrapper/gradle-wrapper.jar | Bin .../wrapper/gradle-wrapper.properties | 0 plugin/gradlew => gradlew | 0 plugin/gradlew.bat => gradlew.bat | 188 +++++++++--------- plugin/build.gradle.kts | 26 +-- .../solidresourcepack/bbconv/plugin/Plugin.kt | 49 +---- ...settings.gradle.kts => settings.gradle.kts | 3 +- 14 files changed, 383 insertions(+), 188 deletions(-) create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt rename plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/BaseConfig.kt => api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelFormat.kt (56%) create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelLoader.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt create mode 100644 build.gradle.kts rename {plugin/gradle => gradle}/wrapper/gradle-wrapper.jar (100%) rename {plugin/gradle => gradle}/wrapper/gradle-wrapper.properties (100%) rename plugin/gradlew => gradlew (100%) mode change 100755 => 100644 rename plugin/gradlew.bat => gradlew.bat (96%) rename plugin/settings.gradle.kts => settings.gradle.kts (62%) diff --git a/.gitignore b/.gitignore index 8089a39..3d8a072 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ .DS_Store **.exe **.bbmodel -**.json +gen/**.json +gen/assets plugin.iml /bbconv /assets diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt new file mode 100644 index 0000000..9a79a0d --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt @@ -0,0 +1,84 @@ +package io.github.solid.resourcepack.bbconv.api.entity + +import io.github.solid.resourcepack.bbconv.config.Bone +import org.bukkit.Material +import org.bukkit.NamespacedKey +import org.bukkit.entity.EntityType +import org.bukkit.entity.ItemDisplay +import org.bukkit.inventory.ItemStack +import org.bukkit.persistence.PersistentDataType +import org.bukkit.util.Transformation +import org.joml.Quaternionf +import org.joml.Vector3f +import java.util.* + +class RenderedBone(private val entity: RenderedEntity, private val bone: Bone) { + + private lateinit var display: ItemDisplay + + + fun spawn() { + if (::display.isInitialized) { + return + } + display = entity.location.world.spawnEntity(entity.location, EntityType.ITEM_DISPLAY) as ItemDisplay + display.isInvisible = !bone.visible + display.itemDisplayTransform = ItemDisplay.ItemDisplayTransform.FIXED + val stack = ItemStack(Material.BONE) + stack.editMeta { + it.itemModel = NamespacedKey.fromString(bone.model.replaceFirst("item/", "")) + } + display.setItemStack(stack) + display.transformation = Transformation( + Vector3f(bone.origin[0], bone.origin[1], bone.origin[2]), + bone.leftRotation.toQuaternionf(), + Vector3f(bone.scale), + Quaternionf(), + ) + markDisplay() + return + } + + // Mark our current display to be a certain bone of a certain entity + private fun markDisplay() { + display.persistentDataContainer.set( + NamespacedKey("bbconv", "bone_id"), PersistentDataType.STRING, bone.id + ) + display.persistentDataContainer.set( + NamespacedKey( + "bbconv", "entity_id" + ), PersistentDataType.STRING, entity.id.toString() + ) + } + + fun getDisplay(): ItemDisplay { + return display + } + + companion object { + + /** + * Find the rendered bone of an entity in the world + */ + @JvmStatic + fun find(entity: RenderedEntity, bone: Bone): Optional { + entity.location.world.getNearbyEntitiesByType(ItemDisplay::class.java, entity.location, 20.0) + .forEach { display -> + val boneId = display.persistentDataContainer.get( + NamespacedKey("bbconv", "bone_id"), + PersistentDataType.STRING + ) + val entityId = display.persistentDataContainer.get( + NamespacedKey("bbconv", "entity_id"), + PersistentDataType.STRING + ) + if (entityId == entity.id.toString() && boneId == bone.id) { + val rendered = RenderedBone(entity, bone) + rendered.display = display + return Optional.of(rendered) + } + } + return Optional.empty() + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt new file mode 100644 index 0000000..f606147 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt @@ -0,0 +1,24 @@ +package io.github.solid.resourcepack.bbconv.api.entity + +import io.github.solid.resourcepack.bbconv.config.OpenModelConfig +import org.bukkit.Location +import java.util.* + +data class RenderedEntity( + val location: Location, + val config: OpenModelConfig, + val id: UUID = UUID.randomUUID(), +) { + private val renderedBones = mutableMapOf() + + init { + location.pitch = 0f + config.getBoneMap().filter { it.value.visible }.forEach { (key, bone) -> + renderedBones[key] = RenderedBone(this, bone) + } + } + + fun spawn() { + renderedBones.values.forEach { it.spawn() } + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/BaseConfig.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelFormat.kt similarity index 56% rename from plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/BaseConfig.kt rename to api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelFormat.kt index fab52eb..7708110 100644 --- a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/BaseConfig.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelFormat.kt @@ -1,6 +1,7 @@ -package io.solidresourcepack.bbconv.plugin +package io.github.solid.resourcepack.bbconv.config import org.joml.Quaternionf +import org.joml.Vector3f import org.spongepowered.configurate.objectmapping.ConfigSerializable import org.spongepowered.configurate.objectmapping.meta.Setting @@ -14,10 +15,8 @@ data class Bone( val origin: List = emptyList(), - @Setting("left_rotation") - val leftRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), - @Setting("right_rotation") - val rightRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), + @Setting("left_rotation") val leftRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), + @Setting("right_rotation") val rightRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), val visible: Boolean = false, @@ -30,11 +29,9 @@ data class Animation( val length: Double = 0.0, - @Setting("start_delay") - val startDelay: Float = 0f, + @Setting("start_delay") val startDelay: Float = 0f, - @Setting("loop_delay") - val loopDelay: Float = 0f, + @Setting("loop_delay") val loopDelay: Float = 0f, val name: String = "", @@ -52,7 +49,9 @@ data class Animator( val rotation: List = emptyList(), val scale: List = emptyList(), -) +) { + +} @ConfigSerializable data class PositionKeyframe( @@ -67,11 +66,9 @@ data class PositionKeyframe( data class RotationKeyframe( val time: Float = 0f, - @Setting("left_rotation") - val leftRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), + @Setting("left_rotation") val leftRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), - @Setting("right_rotation") - val rightRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), + @Setting("right_rotation") val rightRotation: Quaternion = Quaternion(0f, 0f, 0f, 0f), val interpolation: String = "", ) @@ -85,15 +82,6 @@ data class ScaleKeyframe( val interpolation: String = "", ) -@ConfigSerializable -data class Point( - val x: Float = 0f, - - val y: Float = 0f, - - val z: Float = 0f, -) - @ConfigSerializable data class Vector( val x: Float = 0f, @@ -101,7 +89,11 @@ data class Vector( val y: Float = 0f, val z: Float = 0f, -) +) { + fun toVectorf(): Vector3f { + return Vector3f(x, y, z) + } +} @ConfigSerializable data class Quaternion( @@ -119,11 +111,34 @@ data class Quaternion( } @ConfigSerializable -data class BaseConfig( +data class OpenModelConfig( val name: String = "", - @Setting("bone_tree") - val boneTree: List = emptyList(), + @Setting("bone_tree") val boneTree: List = emptyList(), val animations: List = emptyList() -) +) { + + private lateinit var boneMap: Map + + fun getBoneMap(): Map { + if (::boneMap.isInitialized) { + return boneMap + } + val result = mutableMapOf() + for (bone in boneTree) { + result.putAll(getPartBoneMap(bone)) + } + boneMap = result + return result + } + + private fun getPartBoneMap(bone: Bone): Map { + val result = mutableMapOf() + result[bone.id] = bone + for (child in bone.children) { + result.putAll(getPartBoneMap(child)) + } + return result + } +} diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelLoader.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelLoader.kt new file mode 100644 index 0000000..dbc5374 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/OpenModelLoader.kt @@ -0,0 +1,15 @@ +package io.github.solid.resourcepack.bbconv.config + +import org.spongepowered.configurate.gson.GsonConfigurationLoader +import java.nio.file.Path + +class OpenModelLoader(private val path: Path) { + fun load(): OpenModelConfig? { + val loader = GsonConfigurationLoader.builder() + .path(path) + .build() + + val node = loader.load() + return node.get(OpenModelConfig::class.java) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt new file mode 100644 index 0000000..8d60bd8 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt @@ -0,0 +1,67 @@ +package io.github.solid.resourcepack.bbconv.util + +import org.joml.Quaternionf +import org.joml.Vector3f + +enum class Interpolation { + LINEAR { + override fun interpolate(t: Float, vararg v: Vector3f): Vector3f { + return InterpolationUtils.lerp(v[0], v[1], t) + } + + override fun interpolate(t: Float, vararg q: Quaternionf): Quaternionf { + return InterpolationUtils.slerp(q[0], q[1], t) + } + }, + CATMULLROM { + override fun interpolate(t: Float, vararg v: Vector3f): Vector3f { + return InterpolationUtils.catmullRom(v[0], v[1], v[2], v[3], t) + } + + override fun interpolate(t: Float, vararg q: Quaternionf): Quaternionf { + return InterpolationUtils.quatCatmullRom(q[0], q[1], q[2], q[3], t) + } + }; + + abstract fun interpolate(t: Float, vararg v: Vector3f): Vector3f + abstract fun interpolate(t: Float, vararg q: Quaternionf): Quaternionf + + companion object { + @JvmStatic + fun fromString(interpolation: String): Interpolation { + return valueOf(interpolation.uppercase()) + } + } +} + + +object InterpolationUtils { + + fun lerp(a: Vector3f, b: Vector3f, t: Float): Vector3f { + return Vector3f().set(a).lerp(b, t) + } + + fun slerp(a: Quaternionf, b: Quaternionf, t: Float): Quaternionf { + return Quaternionf(a).slerp(b, t) + } + + fun catmullRom(p0: Vector3f, p1: Vector3f, p2: Vector3f, p3: Vector3f, t: Float): Vector3f { + val t2 = t * t + val t3 = t2 * t + return Vector3f( + 0.5f * ((2f * p1.x) + (-p0.x + p2.x) * t + (2f * p0.x - 5f * p1.x + 4f * p2.x - p3.x) * t2 + (-p0.x + 3f * p1.x - 3f * p2.x + p3.x) * t3), + + 0.5f * ((2f * p1.y) + (-p0.y + p2.y) * t + (2f * p0.y - 5f * p1.y + 4f * p2.y - p3.y) * t2 + (-p0.y + 3f * p1.y - 3f * p2.y + p3.y) * t3), + + 0.5f * ((2f * p1.z) + (-p0.z + p2.z) * t + (2f * p0.z - 5f * p1.z + 4f * p2.z - p3.z) * t2 + (-p0.z + 3f * p1.z - 3f * p2.z + p3.z) * t3) + ) + } + + // fake quaternion catmullrom (optional smooth approximation) + + fun quatCatmullRom(q0: Quaternionf, q1: Quaternionf, q2: Quaternionf, q3: Quaternionf, t: Float): Quaternionf { + val slerp1 = Quaternionf(q1).slerp(q2, t) + val slerp2 = Quaternionf(q0).slerp(q3, t) + return slerp1.slerp(slerp2, 2f * t * (1f - t)) // not perfect but smoother than pure slerp + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..278fb78 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + kotlin("jvm") version "2.1.10" + id("com.gradleup.shadow") version "8.3.3" +} + +group = "io.solid-resourcepack.bbconv" +version = "0.1.0" + +allprojects { + repositories { + mavenCentral() + maven { + name = "papermc" + url = uri("https://repo.papermc.io/repository/maven-public/") + } + } +} + +subprojects { + + apply { + plugin("org.jetbrains.kotlin.jvm") + plugin("com.gradleup.shadow") + } + + dependencies { + testImplementation(kotlin("test")) + implementation("org.spongepowered:configurate-extra-kotlin:4.1.2") + implementation("org.spongepowered:configurate-gson:4.1.2") + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + } + + tasks.test { + useJUnitPlatform() + } + + kotlin { + jvmToolchain(21) + } +} + diff --git a/plugin/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from plugin/gradle/wrapper/gradle-wrapper.jar rename to gradle/wrapper/gradle-wrapper.jar diff --git a/plugin/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from plugin/gradle/wrapper/gradle-wrapper.properties rename to gradle/wrapper/gradle-wrapper.properties diff --git a/plugin/gradlew b/gradlew old mode 100755 new mode 100644 similarity index 100% rename from plugin/gradlew rename to gradlew diff --git a/plugin/gradlew.bat b/gradlew.bat similarity index 96% rename from plugin/gradlew.bat rename to gradlew.bat index 9b42019..9d21a21 100644 --- a/plugin/gradlew.bat +++ b/gradlew.bat @@ -1,94 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 2037e1a..8746f45 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -1,31 +1,17 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import dev.s7a.gradle.minecraft.server.tasks.LaunchMinecraftServerTask plugins { - kotlin("jvm") version "2.1.10" - id("com.gradleup.shadow") version "8.3.3" id("dev.s7a.gradle.minecraft.server") version "3.2.1" } -group = "io.solid-resourcepack.bbconv" -version = "0.1.0" - -repositories { - mavenCentral() - maven { - name = "papermc" - url = uri("https://repo.papermc.io/repository/maven-public/") - } -} - dependencies { - testImplementation(kotlin("test")) - implementation("org.spongepowered:configurate-extra-kotlin:4.1.2") - implementation("org.spongepowered:configurate-gson:4.1.2") - compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + implementation(project(":api")) } -tasks.test { - useJUnitPlatform() +tasks.named("shadowJar", ShadowJar::class) { + mergeServiceFiles() + archiveFileName = "${rootProject.name}.jar" } task("launchMinecraftServer") { @@ -33,7 +19,7 @@ task("launchMinecraftServer") { doFirst { copy { - from(layout.buildDirectory.file("libs/${project.name}-${project.version}-all.jar")) + from(layout.buildDirectory.file("libs/${rootProject.name}.jar")) into(layout.buildDirectory.file("MinecraftServer/plugins")) } } diff --git a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt b/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt index e7e8572..461a7d0 100644 --- a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt +++ b/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt @@ -1,19 +1,11 @@ package io.solidresourcepack.bbconv.plugin -import org.bukkit.Location -import org.bukkit.Material -import org.bukkit.NamespacedKey +import io.github.solid.resourcepack.bbconv.api.entity.RenderedEntity +import io.github.solid.resourcepack.bbconv.config.OpenModelLoader import org.bukkit.command.Command import org.bukkit.command.CommandSender -import org.bukkit.entity.EntityType -import org.bukkit.entity.ItemDisplay import org.bukkit.entity.Player -import org.bukkit.inventory.ItemStack import org.bukkit.plugin.java.JavaPlugin -import org.bukkit.util.Transformation -import org.joml.Quaternionf -import org.joml.Vector3f -import org.spongepowered.configurate.gson.GsonConfigurationLoader import java.io.File @@ -25,43 +17,12 @@ class Plugin : JavaPlugin() { override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { val path = File(this.dataFolder, "baseconfig.json").toPath() - val loader = GsonConfigurationLoader.builder() - .path(path) - .build() + val config = OpenModelLoader(path).load() ?: return false - val node = loader.load() - val config = node.get(BaseConfig::class.java) ?: return false - - renderTree(config.boneTree, (sender as Player).location) + val entity = RenderedEntity((sender as Player).location, config) + entity.spawn() return true } - fun renderTree(bones: List, loc: Location) { - for (bone in bones) { - if (bone.visible) { - boneToItemDisplay(bone, loc) - } - renderTree(bone.children, loc) - } - } - - fun boneToItemDisplay(bone: Bone, loc: Location) { - val entity = loc.world.spawnEntity(loc, EntityType.ITEM_DISPLAY) as ItemDisplay - val stack = ItemStack(Material.BONE) - stack.editMeta { - it.itemModel = NamespacedKey.fromString(bone.model.replaceFirst("item/", "")) - } - - entity.isInvisible = false - entity.setItemStack(stack) - entity.itemDisplayTransform = ItemDisplay.ItemDisplayTransform.FIXED - entity.transformation = Transformation( - Vector3f(bone.origin[0], bone.origin[1], bone.origin[2]), - bone.leftRotation.toQuaternionf(), - Vector3f(bone.scale), - Quaternionf(), - ) - } - } \ No newline at end of file diff --git a/plugin/settings.gradle.kts b/settings.gradle.kts similarity index 62% rename from plugin/settings.gradle.kts rename to settings.gradle.kts index 66b44dc..a8a2926 100644 --- a/plugin/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,6 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } -rootProject.name = "plugin" +rootProject.name = "bbconv" +include("plugin", "api") From a3604e313053060b7ee242b57b643d65806ba216 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 3 May 2025 23:27:55 +0200 Subject: [PATCH 2/5] feat: half working animation framework --- .../api/animation/bone/BoneAnimation.kt | 10 ++ .../animation/bone/BoneAnimationContext.kt | 17 ++ .../animation/bone/GroupedBoneAnimation.kt | 23 +++ .../animation/bone/KeyFramedBoneAnimation.kt | 24 +++ .../api/animation/entity/EntityAnimation.kt | 15 ++ .../entity/EntityAnimationController.kt | 168 ++++++++++++++++++ .../animation/entity/EntityAnimationData.kt | 25 +++ .../bbconv/api/animation/keyframe/Keyframe.kt | 8 + .../bbconv/api/animation/keyframe/Timeline.kt | 75 ++++++++ .../bbconv/api/entity/RenderedBone.kt | 20 ++- .../bbconv/api/entity/RenderedEntity.kt | 49 ++++- .../resourcepack/bbconv/util/Interpolation.kt | 87 ++++++--- plugin/build.gradle.kts | 2 +- .../solidresourcepack/bbconv/plugin/Plugin.kt | 14 +- 14 files changed, 497 insertions(+), 40 deletions(-) create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimation.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimationContext.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimation.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationData.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Keyframe.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimation.kt new file mode 100644 index 0000000..9fc9227 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimation.kt @@ -0,0 +1,10 @@ +package io.github.solid.resourcepack.bbconv.api.animation.bone + +import org.bukkit.util.Transformation + +/** + * Produces a Transformation based on the current animation context + */ +fun interface BoneAnimation { + fun animate(context: BoneAnimationContext): Transformation +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimationContext.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimationContext.kt new file mode 100644 index 0000000..bce55bb --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/BoneAnimationContext.kt @@ -0,0 +1,17 @@ +package io.github.solid.resourcepack.bbconv.api.animation.bone + +import io.github.solid.resourcepack.bbconv.api.entity.RenderedBone +import io.github.solid.resourcepack.bbconv.api.entity.RenderedEntity + +/** + * Context of an active BoneAnimation. + * This holds the bone itself, the current entity and the + * elapsed time for the current animation and the initial state (transformation property pre mutation) of the bone. + * + * Mutating the transformation property will apply this mutation to the [RenderedBone] + */ +data class BoneAnimationContext( + val entity: RenderedEntity, + val elapsedTime: Float, + val self: RenderedBone, +) diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt new file mode 100644 index 0000000..96bd9fd --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt @@ -0,0 +1,23 @@ +package io.github.solid.resourcepack.bbconv.api.animation.bone + +import org.bukkit.util.Transformation +import org.joml.Quaternionf +import org.joml.Vector3f + +class GroupedBoneAnimation( + private val animations: List +) : BoneAnimation { + override fun animate(context: BoneAnimationContext): Transformation { + var result = Transformation(Vector3f(), Quaternionf(), Vector3f(), Quaternionf()) + animations.forEach { + val transformation = it.animate(context) + result = Transformation( + result.translation.add(transformation.translation), + result.leftRotation.add(transformation.leftRotation), + result.scale.add(transformation.scale), + Quaternionf() + ) + } + return result + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt new file mode 100644 index 0000000..1261793 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt @@ -0,0 +1,24 @@ +package io.github.solid.resourcepack.bbconv.api.animation.bone + +import io.github.solid.resourcepack.bbconv.api.animation.keyframe.Timeline +import io.github.solid.resourcepack.bbconv.config.Animator +import org.bukkit.util.Transformation +import org.joml.Quaternionf +import org.joml.Vector3f + +/** + * Represents a keyframe based bone animation (being read from configs or other non-procedural sources) + */ +class KeyFramedBoneAnimation( + private val animator: Animator +) : BoneAnimation { + override fun animate(context: BoneAnimationContext): Transformation { + val scale = if (animator.scale.size >= 2) Timeline.ofScale(animator.scale) + .interpolate(context.elapsedTime) else Vector3f() + val position = if (animator.position.size >= 2) Timeline.ofPosition(animator.position) + .interpolate(context.elapsedTime) else Vector3f() + val rotation = if (animator.rotation.size >= 2) Timeline.ofRotation(animator.rotation) + .interpolate(context.elapsedTime) else Quaternionf() + return Transformation(position, rotation, Vector3f(), Quaternionf()) + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimation.kt new file mode 100644 index 0000000..47ea63e --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimation.kt @@ -0,0 +1,15 @@ +package io.github.solid.resourcepack.bbconv.api.animation.entity + +import io.github.solid.resourcepack.bbconv.api.animation.bone.BoneAnimation +import io.github.solid.resourcepack.bbconv.api.animation.bone.KeyFramedBoneAnimation +import io.github.solid.resourcepack.bbconv.config.Animation + +typealias EntityAnimation = MutableMap + +object EntityAnimations { + fun of(animation: Animation): EntityAnimation { + return animation.animators.associate { animator -> animator.bone to KeyFramedBoneAnimation(animator) } + .toMutableMap() + } +} + diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt new file mode 100644 index 0000000..fcd3ab5 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt @@ -0,0 +1,168 @@ +package io.github.solid.resourcepack.bbconv.api.animation.entity + +import io.github.solid.resourcepack.bbconv.api.animation.bone.BoneAnimation +import io.github.solid.resourcepack.bbconv.api.animation.bone.BoneAnimationContext +import io.github.solid.resourcepack.bbconv.api.entity.RenderedBone +import io.github.solid.resourcepack.bbconv.api.entity.RenderedEntity +import io.github.solid.resourcepack.bbconv.config.OpenModelConfig +import org.bukkit.util.Transformation +import org.joml.Quaternionf +import org.joml.Vector3f + +class EntityAnimationController( + private val entity: RenderedEntity, +) { + val loadedAnimations = AnimationInfos.of(entity.config).toMutableMap() + private val activeAnimations: MutableMap = mutableMapOf() + + fun play(animation: EntityAnimation) { + val type = loadedAnimations.toList().firstOrNull { it.second == animation }?.first ?: return + activeAnimations[type] = 0f + } + + fun play(animation: String) { + val type = loadedAnimations.toList().firstOrNull { it.first.name == animation }?.first ?: return + activeAnimations[type] = 0f + } + + fun cancel(animation: EntityAnimation) { + val type = loadedAnimations.toList().firstOrNull { it.second == animation }?.first ?: return + activeAnimations.remove(type) + } + + fun cancel(animation: String) { + val type = loadedAnimations.toList().firstOrNull { it.first.name == animation }?.first ?: return + activeAnimations.remove(type) + } + + fun reset() { + activeAnimations.clear() + } + + fun animate(delta: Float) { + val result = mutableMapOf() + activeAnimations.forEach { (type, currentTime) -> + val animation = loadedAnimations[type] ?: return@forEach + var time = currentTime + delta + if (time > type.duration) { + if (!type.looped) { + activeAnimations.remove(type) + return@forEach + } + time = 0f; + } + val mappedBones = bonesToAnimations(entity.rootBones, animation) + mappedBones.forEach { (bone, anim) -> + result.putAll( + partialAnimate( + anim, createContext(bone, time), result[bone] ?: Transformation( + Vector3f(), Quaternionf(), Vector3f(), + Quaternionf() + ) + ) + ) + } + activeAnimations[type] = time + } + result.forEach { (bone, transformation) -> + val transformation = appendTransformation(bone.initialTransformation, transformation) + println("changes in bone ${bone.bone.id}: $transformation") + bone.getDisplay().transformation = transformation + } + } + + private fun createContext(bone: RenderedBone, time: Float): BoneAnimationContext { + return BoneAnimationContext( + entity, + time, + bone, + ) + } + + private fun bonesToAnimations( + bones: List, + animation: EntityAnimation + ): MutableMap { + val result = bones.filter { animation.keys.contains(it.bone.id) } + .associateWith { bone -> animation.toList().first { it.first == bone.bone.id }.second } + .toMutableMap() + bones.forEach { bone -> + result.putAll(bonesToAnimations(bone.children, animation)) + } + return result + } + + private fun partialAnimate( + animation: BoneAnimation, + ctx: BoneAnimationContext, + transformation: Transformation + ): Map { + val result = appendTransformation(transformation, animation.animate(ctx)) + val returned = mutableMapOf() + returned[ctx.self] = result + returned.putAll(animateChildren(result, ctx.self)) + return returned + } + + private fun animateChildren( + transformation: Transformation, + parent: RenderedBone + ): Map { + val result = mutableMapOf() + parent.children.forEach { child -> + + val initial = child.initialTransformation + val parentTrans = transformation + + val localTranslation = Vector3f(initial.translation) + .mul(parentTrans.scale) + .rotate(parentTrans.leftRotation) + + val worldTranslation = Vector3f(parentTrans.translation).add(localTranslation) + + val worldRotation = Quaternionf(parentTrans.leftRotation).mul(initial.leftRotation) + + val worldScale = Vector3f(parentTrans.scale).mul(initial.scale) + + val childTransformation = Transformation( + worldTranslation, + worldRotation, + worldScale, + Quaternionf() + ) + result[child] = childTransformation + result.putAll(animateChildren(childTransformation, child)) + } + return result + } + + private fun appendTransformation(first: Transformation, second: Transformation): Transformation { + return Transformation( + first.translation.add(second.translation), + Quaternionf(second.leftRotation).mul(first.leftRotation), + first.scale.add(second.scale), + Quaternionf() + ) + } +} + +object AnimationInfos { + + private val cache = mutableMapOf>() + + @JvmStatic + fun of(config: OpenModelConfig): Map { + if (cache.containsKey(config)) { + return cache[config]!! + } + val result = config.animations.associate { animation -> + EntityAnimationData.of(animation) to EntityAnimations.of(animation) + } + cache[config] = result + return result + } + + fun clearCache() { + cache.clear() + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationData.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationData.kt new file mode 100644 index 0000000..249f577 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationData.kt @@ -0,0 +1,25 @@ +package io.github.solid.resourcepack.bbconv.api.animation.entity + +import io.github.solid.resourcepack.bbconv.config.Animation + +data class EntityAnimationData( + val name: String, + /** + * Negative if the duration is indefinite/procedural + */ + val duration: Float = -1f, + val looped: Boolean = false, + val loopDelay: Float = -1f, +) { + companion object { + @JvmStatic + fun of(animation: Animation): EntityAnimationData { + return EntityAnimationData( + name = animation.name, + duration = animation.length.toFloat(), + looped = animation.loop, + loopDelay = animation.loopDelay + ) + } + } +} diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Keyframe.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Keyframe.kt new file mode 100644 index 0000000..db7a6fc --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Keyframe.kt @@ -0,0 +1,8 @@ +package io.github.solid.resourcepack.bbconv.api.animation.keyframe + +import io.github.solid.resourcepack.bbconv.util.Interpolation + +data class Keyframe( + val data: T, + val interpolation: Interpolation, +) \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt new file mode 100644 index 0000000..ae479ac --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt @@ -0,0 +1,75 @@ +package io.github.solid.resourcepack.bbconv.api.animation.keyframe + +import io.github.solid.resourcepack.bbconv.config.PositionKeyframe +import io.github.solid.resourcepack.bbconv.config.RotationKeyframe +import io.github.solid.resourcepack.bbconv.config.ScaleKeyframe +import io.github.solid.resourcepack.bbconv.util.Interpolation +import io.github.solid.resourcepack.bbconv.util.Interpolator +import org.joml.Quaternionf +import org.joml.Vector3f + +typealias TimelineFrames = Map> + +class Timeline(private val frames: TimelineFrames, private val clazz: Class) { + + private val sortedFrames = frames.toSortedMap(compareBy { it }) + + fun interpolate(time: Float): T { + require(sortedFrames.size >= 2) { "Need at least 2 keyframes, has ${sortedFrames.size}" } + val times = sortedFrames.keys.toList() + + if (time <= times.first()) return sortedFrames[times.first()]!!.data + if (time >= times.last()) return sortedFrames[times.last()]!!.data + + val index = times.indexOfLast { it <= time } + val t0 = times.getOrNull(index - 1) + val t1 = times[index] + val t2 = times[index + 1] + val t3 = times.getOrNull(index + 2) + + val k0 = t0?.let { frames[it] } ?: frames[t1]!! + val k1 = frames[t1]!! + val k2 = frames[t2]!! + val k3 = t3?.let { frames[it] } ?: frames[t2]!! + + val localT = ((time - t1) / (t2 - t1)).coerceIn(0f, 1f) + + val interpolator = Interpolator(k1.interpolation, clazz) + return interpolator.interpolate(localT, k0.data, k1.data, k2.data, k3.data) + } + + companion object { + @JvmStatic + fun ofPosition(frames: List): Timeline { + val converted = frames.associate { + it.time to Keyframe( + it.position.toVectorf(), + Interpolation.fromString(it.interpolation) + ) + } + return Timeline(converted, Vector3f::class.java) + } + + @JvmStatic + fun ofScale(frames: List): Timeline { + val converted = frames.associate { + it.time to Keyframe( + it.scale.toVectorf(), + Interpolation.fromString(it.interpolation) + ) + } + return Timeline(converted, Vector3f::class.java) + } + + @JvmStatic + fun ofRotation(frames: List): Timeline { + val converted = frames.associate { + it.time to Keyframe( + it.leftRotation.toQuaternionf(), + Interpolation.fromString(it.interpolation) + ) + } + return Timeline(converted, Quaternionf::class.java) + } + } +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt index 9a79a0d..b39ca06 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt @@ -12,10 +12,19 @@ import org.joml.Quaternionf import org.joml.Vector3f import java.util.* -class RenderedBone(private val entity: RenderedEntity, private val bone: Bone) { +class RenderedBone( + private val entity: RenderedEntity, + val bone: Bone, + val children: MutableList = mutableListOf() +) { private lateinit var display: ItemDisplay - + val initialTransformation = Transformation( + Vector3f(bone.origin[0], bone.origin[1], bone.origin[2]), + bone.leftRotation.toQuaternionf(), + Vector3f(bone.scale), + Quaternionf(), + ) fun spawn() { if (::display.isInitialized) { @@ -29,12 +38,7 @@ class RenderedBone(private val entity: RenderedEntity, private val bone: Bone) { it.itemModel = NamespacedKey.fromString(bone.model.replaceFirst("item/", "")) } display.setItemStack(stack) - display.transformation = Transformation( - Vector3f(bone.origin[0], bone.origin[1], bone.origin[2]), - bone.leftRotation.toQuaternionf(), - Vector3f(bone.scale), - Quaternionf(), - ) + display.transformation = initialTransformation markDisplay() return } diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt index f606147..3fd8068 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt @@ -1,5 +1,7 @@ package io.github.solid.resourcepack.bbconv.api.entity +import io.github.solid.resourcepack.bbconv.api.animation.entity.EntityAnimationController +import io.github.solid.resourcepack.bbconv.config.Bone import io.github.solid.resourcepack.bbconv.config.OpenModelConfig import org.bukkit.Location import java.util.* @@ -10,15 +12,58 @@ data class RenderedEntity( val id: UUID = UUID.randomUUID(), ) { private val renderedBones = mutableMapOf() + val rootBones = mutableListOf() + + private val animationController = EntityAnimationController(this) init { location.pitch = 0f - config.getBoneMap().filter { it.value.visible }.forEach { (key, bone) -> - renderedBones[key] = RenderedBone(this, bone) + config.boneTree.forEach { bone -> + val bone = initBone(bone) ?: return@forEach + rootBones.add(bone) + } + if (rootBones.isEmpty()) { + findRootBones(config.boneTree) + } + } + + private fun findRootBones(bones: List) { + var found = false + for (bone in bones) { + bone.children.forEach { + if (renderedBones.containsKey(it.id)) { + rootBones.add(renderedBones[it.id]!!) + found = true + } + } + } + if (!found) { + bones.forEach { bone -> + findRootBones(bone.children) + } + } + } + + private fun initBone(bone: Bone, parent: RenderedBone? = null): RenderedBone? { + var rendered: RenderedBone? = null + if (bone.visible) { + rendered = RenderedBone(this, bone) + parent?.children?.add(rendered) + renderedBones[bone.id] = rendered } + bone.children.forEach { child -> + initBone(child, rendered) + } + return rendered + } + + fun getAnimationController(): EntityAnimationController { + return animationController } fun spawn() { renderedBones.values.forEach { it.spawn() } } + + } \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt index 8d60bd8..a340c6f 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/Interpolation.kt @@ -5,26 +5,32 @@ import org.joml.Vector3f enum class Interpolation { LINEAR { - override fun interpolate(t: Float, vararg v: Vector3f): Vector3f { - return InterpolationUtils.lerp(v[0], v[1], t) - } - - override fun interpolate(t: Float, vararg q: Quaternionf): Quaternionf { - return InterpolationUtils.slerp(q[0], q[1], t) + override fun interpolate( + t: Float, + functions: InterpolationFunctions, + vararg v: T + ): T { + val first = if (v.size > 2) v[1] else v[0] + val second = if (v.size > 2) v[2] else v[1] + return functions.linear(first, second, t) } }, - CATMULLROM { - override fun interpolate(t: Float, vararg v: Vector3f): Vector3f { - return InterpolationUtils.catmullRom(v[0], v[1], v[2], v[3], t) - } - override fun interpolate(t: Float, vararg q: Quaternionf): Quaternionf { - return InterpolationUtils.quatCatmullRom(q[0], q[1], q[2], q[3], t) + CATMULLROM { + override fun interpolate( + t: Float, + functions: InterpolationFunctions, + vararg v: T + ): T { + return functions.catmullRom(v[0], v[1], v[1], v[3], t) } }; - abstract fun interpolate(t: Float, vararg v: Vector3f): Vector3f - abstract fun interpolate(t: Float, vararg q: Quaternionf): Quaternionf + abstract fun interpolate( + t: Float, + functions: InterpolationFunctions, + vararg v: T + ): T companion object { @JvmStatic @@ -34,34 +40,63 @@ enum class Interpolation { } } +class Interpolator( + private val interpolation: Interpolation, + clazz: Class +) { + private val functions: InterpolationFunctions = InterpolationFunctions.of(clazz) -object InterpolationUtils { + fun interpolate(t: Float, vararg values: T): T { + return interpolation.interpolate(t, functions, *values) + } +} - fun lerp(a: Vector3f, b: Vector3f, t: Float): Vector3f { - return Vector3f().set(a).lerp(b, t) +interface InterpolationFunctions { + fun linear(a: T, b: T, t: Float): T + fun catmullRom(p0: T, p1: T, p2: T, p3: T, t: Float): T + + companion object { + fun of(clazz: Class): InterpolationFunctions { + return when (clazz) { + Vector3f::class.java -> VectorInterpolationFunctions as InterpolationFunctions + Quaternionf::class.java -> QuaternionInterpolationFunctions as InterpolationFunctions + else -> throw IllegalArgumentException("Unsupported type: $clazz") + } + } } +} - fun slerp(a: Quaternionf, b: Quaternionf, t: Float): Quaternionf { - return Quaternionf(a).slerp(b, t) +object VectorInterpolationFunctions : InterpolationFunctions { + override fun linear(a: Vector3f, b: Vector3f, t: Float): Vector3f { + return Vector3f().set(a).lerp(b, t) } - fun catmullRom(p0: Vector3f, p1: Vector3f, p2: Vector3f, p3: Vector3f, t: Float): Vector3f { + override fun catmullRom(p0: Vector3f, p1: Vector3f, p2: Vector3f, p3: Vector3f, t: Float): Vector3f { val t2 = t * t val t3 = t2 * t return Vector3f( 0.5f * ((2f * p1.x) + (-p0.x + p2.x) * t + (2f * p0.x - 5f * p1.x + 4f * p2.x - p3.x) * t2 + (-p0.x + 3f * p1.x - 3f * p2.x + p3.x) * t3), - 0.5f * ((2f * p1.y) + (-p0.y + p2.y) * t + (2f * p0.y - 5f * p1.y + 4f * p2.y - p3.y) * t2 + (-p0.y + 3f * p1.y - 3f * p2.y + p3.y) * t3), - 0.5f * ((2f * p1.z) + (-p0.z + p2.z) * t + (2f * p0.z - 5f * p1.z + 4f * p2.z - p3.z) * t2 + (-p0.z + 3f * p1.z - 3f * p2.z + p3.z) * t3) ) } +} - // fake quaternion catmullrom (optional smooth approximation) +object QuaternionInterpolationFunctions : InterpolationFunctions { + override fun linear(a: Quaternionf, b: Quaternionf, t: Float): Quaternionf { + return Quaternionf(a).slerp(b, t) + } - fun quatCatmullRom(q0: Quaternionf, q1: Quaternionf, q2: Quaternionf, q3: Quaternionf, t: Float): Quaternionf { - val slerp1 = Quaternionf(q1).slerp(q2, t) - val slerp2 = Quaternionf(q0).slerp(q3, t) + override fun catmullRom( + p0: Quaternionf, + p1: Quaternionf, + p2: Quaternionf, + p3: Quaternionf, + t: Float + ): Quaternionf { + val slerp1 = Quaternionf(p1).slerp(p2, t) + val slerp2 = Quaternionf(p0).slerp(p3, t) return slerp1.slerp(slerp2, 2f * t * (1f - t)) // not perfect but smoother than pure slerp } + } \ No newline at end of file diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 8746f45..cdd3036 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -23,7 +23,7 @@ task("launchMinecraftServer") { into(layout.buildDirectory.file("MinecraftServer/plugins")) } } - jarUrl.set(LaunchMinecraftServerTask.JarUrl.Paper("1.21.4")) + jarUrl.set(LaunchMinecraftServerTask.JarUrl.Paper("1.21.5")) agreeEula.set(true) } diff --git a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt b/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt index 461a7d0..ec07959 100644 --- a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt +++ b/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt @@ -15,13 +15,21 @@ class Plugin : JavaPlugin() { this.getCommand("t")?.setExecutor(this) } + private lateinit var entity: RenderedEntity + override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { val path = File(this.dataFolder, "baseconfig.json").toPath() val config = OpenModelLoader(path).load() ?: return false - val entity = RenderedEntity((sender as Player).location, config) - entity.spawn() - + if (!::entity.isInitialized) { + entity = RenderedEntity((sender as Player).location, config) + entity.spawn() + if (args.isNotEmpty()) { + entity.getAnimationController().play(args[0]) + } + return true + } + entity.getAnimationController().animate(0.10f) return true } From b94861b7131ebf7d6927bfc5e42099de7c0f7c67 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 4 May 2025 22:56:34 +0200 Subject: [PATCH 3/5] fix: everything except rotations on child bones --- .../entity/EntityAnimationController.kt | 19 ++++++++++----- .../bbconv/api/animation/keyframe/Timeline.kt | 4 ++-- .../bbconv/api/entity/RenderedBone.kt | 12 ++++++++-- .../solidresourcepack/bbconv/plugin/Plugin.kt | 23 +++++++++++++++---- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt index fcd3ab5..f7e8b47 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt @@ -25,6 +25,10 @@ class EntityAnimationController( activeAnimations[type] = 0f } + fun get(animation: String): EntityAnimationData? { + return loadedAnimations.toList().firstOrNull { it.first.name == animation }?.first + } + fun cancel(animation: EntityAnimation) { val type = loadedAnimations.toList().firstOrNull { it.second == animation }?.first ?: return activeAnimations.remove(type) @@ -65,8 +69,11 @@ class EntityAnimationController( activeAnimations[type] = time } result.forEach { (bone, transformation) -> - val transformation = appendTransformation(bone.initialTransformation, transformation) - println("changes in bone ${bone.bone.id}: $transformation") + val initial = bone.getInitialTransformation() + if(initial.leftRotation != Quaternionf()) { + println("bone ${bone.bone.id}: ${initial.leftRotation}") + } + val transformation = appendTransformation(transformation, initial) bone.getDisplay().transformation = transformation } } @@ -111,7 +118,7 @@ class EntityAnimationController( val result = mutableMapOf() parent.children.forEach { child -> - val initial = child.initialTransformation + val initial = child.getInitialTransformation() val parentTrans = transformation val localTranslation = Vector3f(initial.translation) @@ -138,9 +145,9 @@ class EntityAnimationController( private fun appendTransformation(first: Transformation, second: Transformation): Transformation { return Transformation( - first.translation.add(second.translation), - Quaternionf(second.leftRotation).mul(first.leftRotation), - first.scale.add(second.scale), + Vector3f(first.translation).add(Vector3f(second.translation)), + Quaternionf(second.leftRotation).mul(Quaternionf(first.leftRotation)), + Vector3f(first.scale).add(Vector3f(second.scale)), Quaternionf() ) } diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt index ae479ac..e365e57 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/keyframe/Timeline.kt @@ -43,7 +43,7 @@ class Timeline(private val frames: TimelineFrames, private val clazz: Clas fun ofPosition(frames: List): Timeline { val converted = frames.associate { it.time to Keyframe( - it.position.toVectorf(), + it.position.toVectorf().div(16f), Interpolation.fromString(it.interpolation) ) } @@ -54,7 +54,7 @@ class Timeline(private val frames: TimelineFrames, private val clazz: Clas fun ofScale(frames: List): Timeline { val converted = frames.associate { it.time to Keyframe( - it.scale.toVectorf(), + it.scale.toVectorf().div(16f), Interpolation.fromString(it.interpolation) ) } diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt index b39ca06..2cbe716 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt @@ -19,13 +19,20 @@ class RenderedBone( ) { private lateinit var display: ItemDisplay - val initialTransformation = Transformation( + private val initialTransformation = Transformation( Vector3f(bone.origin[0], bone.origin[1], bone.origin[2]), bone.leftRotation.toQuaternionf(), Vector3f(bone.scale), Quaternionf(), ) + fun getInitialTransformation(): Transformation { + return Transformation( + Vector3f(initialTransformation.translation), Quaternionf(initialTransformation.leftRotation), + Vector3f(initialTransformation.scale), Quaternionf() + ) + } + fun spawn() { if (::display.isInitialized) { return @@ -38,7 +45,8 @@ class RenderedBone( it.itemModel = NamespacedKey.fromString(bone.model.replaceFirst("item/", "")) } display.setItemStack(stack) - display.transformation = initialTransformation + display.transformation = getInitialTransformation() + display.interpolationDuration = 1 markDisplay() return } diff --git a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt b/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt index ec07959..eacad2b 100644 --- a/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt +++ b/plugin/src/main/kotlin/io/solidresourcepack/bbconv/plugin/Plugin.kt @@ -2,10 +2,12 @@ package io.solidresourcepack.bbconv.plugin import io.github.solid.resourcepack.bbconv.api.entity.RenderedEntity import io.github.solid.resourcepack.bbconv.config.OpenModelLoader +import org.bukkit.Bukkit import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.scheduler.BukkitTask import java.io.File @@ -15,21 +17,32 @@ class Plugin : JavaPlugin() { this.getCommand("t")?.setExecutor(this) } - private lateinit var entity: RenderedEntity + private var entity: RenderedEntity? = null override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { val path = File(this.dataFolder, "baseconfig.json").toPath() val config = OpenModelLoader(path).load() ?: return false - if (!::entity.isInitialized) { + if (entity == null) { entity = RenderedEntity((sender as Player).location, config) - entity.spawn() + entity!!.spawn() if (args.isNotEmpty()) { - entity.getAnimationController().play(args[0]) + val type = args[0] + val animation = entity!!.getAnimationController().get(type) ?: return false + entity!!.getAnimationController().play(type) + var elapsed = 0f + var task: BukkitTask? = null + task = Bukkit.getScheduler().runTaskTimer(this, Runnable { + entity!!.getAnimationController().animate(0.05f) + elapsed += 0.05f + if (elapsed > animation.duration) { + task?.cancel() + entity = null + } + }, 5L, 1L) } return true } - entity.getAnimationController().animate(0.10f) return true } From 2ae1f348a8608417743ef68ec09e959731d2b4bb Mon Sep 17 00:00:00 2001 From: David Date: Wed, 7 May 2025 23:04:27 +0200 Subject: [PATCH 4/5] fix: rotations --- .../animation/bone/GroupedBoneAnimation.kt | 3 +- .../animation/bone/KeyFramedBoneAnimation.kt | 2 +- .../entity/EntityAnimationController.kt | 48 ++++++++----------- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt index 96bd9fd..59976dc 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/GroupedBoneAnimation.kt @@ -3,6 +3,7 @@ package io.github.solid.resourcepack.bbconv.api.animation.bone import org.bukkit.util.Transformation import org.joml.Quaternionf import org.joml.Vector3f +import org.joml.times class GroupedBoneAnimation( private val animations: List @@ -13,7 +14,7 @@ class GroupedBoneAnimation( val transformation = it.animate(context) result = Transformation( result.translation.add(transformation.translation), - result.leftRotation.add(transformation.leftRotation), + Quaternionf(transformation.leftRotation).times(result.leftRotation), result.scale.add(transformation.scale), Quaternionf() ) diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt index 1261793..6f001fb 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/bone/KeyFramedBoneAnimation.kt @@ -19,6 +19,6 @@ class KeyFramedBoneAnimation( .interpolate(context.elapsedTime) else Vector3f() val rotation = if (animator.rotation.size >= 2) Timeline.ofRotation(animator.rotation) .interpolate(context.elapsedTime) else Quaternionf() - return Transformation(position, rotation, Vector3f(), Quaternionf()) + return Transformation(position, rotation, scale, Quaternionf()) } } \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt index f7e8b47..06aa290 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt @@ -60,8 +60,7 @@ class EntityAnimationController( result.putAll( partialAnimate( anim, createContext(bone, time), result[bone] ?: Transformation( - Vector3f(), Quaternionf(), Vector3f(), - Quaternionf() + Vector3f(), Quaternionf(), Vector3f(), Quaternionf() ) ) ) @@ -69,11 +68,7 @@ class EntityAnimationController( activeAnimations[type] = time } result.forEach { (bone, transformation) -> - val initial = bone.getInitialTransformation() - if(initial.leftRotation != Quaternionf()) { - println("bone ${bone.bone.id}: ${initial.leftRotation}") - } - val transformation = appendTransformation(transformation, initial) + val transformation = appendTransformation(transformation, bone.getInitialTransformation()) bone.getDisplay().transformation = transformation } } @@ -87,12 +82,10 @@ class EntityAnimationController( } private fun bonesToAnimations( - bones: List, - animation: EntityAnimation + bones: List, animation: EntityAnimation ): MutableMap { val result = bones.filter { animation.keys.contains(it.bone.id) } - .associateWith { bone -> animation.toList().first { it.first == bone.bone.id }.second } - .toMutableMap() + .associateWith { bone -> animation.toList().first { it.first == bone.bone.id }.second }.toMutableMap() bones.forEach { bone -> result.putAll(bonesToAnimations(bone.children, animation)) } @@ -100,9 +93,7 @@ class EntityAnimationController( } private fun partialAnimate( - animation: BoneAnimation, - ctx: BoneAnimationContext, - transformation: Transformation + animation: BoneAnimation, ctx: BoneAnimationContext, transformation: Transformation ): Map { val result = appendTransformation(transformation, animation.animate(ctx)) val returned = mutableMapOf() @@ -112,30 +103,24 @@ class EntityAnimationController( } private fun animateChildren( - transformation: Transformation, - parent: RenderedBone + transformation: Transformation, parent: RenderedBone ): Map { val result = mutableMapOf() parent.children.forEach { child -> val initial = child.getInitialTransformation() val parentTrans = transformation - - val localTranslation = Vector3f(initial.translation) - .mul(parentTrans.scale) - .rotate(parentTrans.leftRotation) - - val worldTranslation = Vector3f(parentTrans.translation).add(localTranslation) - - val worldRotation = Quaternionf(parentTrans.leftRotation).mul(initial.leftRotation) - - val worldScale = Vector3f(parentTrans.scale).mul(initial.scale) + val worldTranslation = Vector3f(parentTrans.translation).add( + Vector3f(initial.translation).mul(parentTrans.scale).rotate(parentTrans.leftRotation) + ) + val worldRotation = + delta(initial.leftRotation, Quaternionf(parentTrans.leftRotation).mul(initial.leftRotation)) + val worldScale = Vector3f(parentTrans.scale).div(parent.bone.scale).mul(initial.scale) val childTransformation = Transformation( worldTranslation, worldRotation, - worldScale, - Quaternionf() + worldScale, Quaternionf() ) result[child] = childTransformation result.putAll(animateChildren(childTransformation, child)) @@ -143,10 +128,15 @@ class EntityAnimationController( return result } + private fun delta(from: Quaternionf, to: Quaternionf): Quaternionf { + val invFrom = Quaternionf(from).invert() + return Quaternionf(to).mul(invFrom) + } + private fun appendTransformation(first: Transformation, second: Transformation): Transformation { return Transformation( Vector3f(first.translation).add(Vector3f(second.translation)), - Quaternionf(second.leftRotation).mul(Quaternionf(first.leftRotation)), + Quaternionf(first.leftRotation).mul(Quaternionf(second.leftRotation)), Vector3f(first.scale).add(Vector3f(second.scale)), Quaternionf() ) From c785681388de3f02530f2209bd8a9882af8c5034 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 8 May 2025 22:23:09 +0200 Subject: [PATCH 5/5] refactor: play with translations --- .../entity/EntityAnimationController.kt | 36 ++++++++++--------- .../bbconv/api/entity/RenderedBone.kt | 22 ++++++++---- .../bbconv/api/entity/RenderedEntity.kt | 2 +- .../resourcepack/bbconv/config/DevFlags.kt | 5 +++ .../bbconv/util/QuaternionMath.kt | 10 ++++++ 5 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/DevFlags.kt create mode 100644 api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/QuaternionMath.kt diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt index 06aa290..ae50ead 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/animation/entity/EntityAnimationController.kt @@ -5,6 +5,7 @@ import io.github.solid.resourcepack.bbconv.api.animation.bone.BoneAnimationConte import io.github.solid.resourcepack.bbconv.api.entity.RenderedBone import io.github.solid.resourcepack.bbconv.api.entity.RenderedEntity import io.github.solid.resourcepack.bbconv.config.OpenModelConfig +import io.github.solid.resourcepack.bbconv.util.QuaternionMath import org.bukkit.util.Transformation import org.joml.Quaternionf import org.joml.Vector3f @@ -107,35 +108,38 @@ class EntityAnimationController( ): Map { val result = mutableMapOf() parent.children.forEach { child -> - val initial = child.getInitialTransformation() val parentTrans = transformation - val worldTranslation = Vector3f(parentTrans.translation).add( - Vector3f(initial.translation).mul(parentTrans.scale).rotate(parentTrans.leftRotation) - ) - val worldRotation = - delta(initial.leftRotation, Quaternionf(parentTrans.leftRotation).mul(initial.leftRotation)) - val worldScale = Vector3f(parentTrans.scale).div(parent.bone.scale).mul(initial.scale) + + // Calculate rotated local translation + val rotatedTranslation = Vector3f(initial.translation) + parentTrans.leftRotation.transform(rotatedTranslation) + + val deltaTranslation = Vector3f(rotatedTranslation).sub(initial.translation) + + // Rotation delta: how the rotation has changed + val rotatedRotation = Quaternionf(parentTrans.leftRotation).mul(initial.leftRotation) + val deltaRotation = QuaternionMath.delta(initial.leftRotation, rotatedRotation) + + // Scale delta: how the scale has changed + val deltaScale = Vector3f(parentTrans.scale).div(parent.bone.scale).mul(initial.scale) val childTransformation = Transformation( - worldTranslation, - worldRotation, - worldScale, Quaternionf() + deltaTranslation, + deltaRotation, + deltaScale, + Quaternionf() ) + result[child] = childTransformation result.putAll(animateChildren(childTransformation, child)) } return result } - private fun delta(from: Quaternionf, to: Quaternionf): Quaternionf { - val invFrom = Quaternionf(from).invert() - return Quaternionf(to).mul(invFrom) - } - private fun appendTransformation(first: Transformation, second: Transformation): Transformation { return Transformation( - Vector3f(first.translation).add(Vector3f(second.translation)), + Vector3f(first.translation).add(second.translation), Quaternionf(first.leftRotation).mul(Quaternionf(second.leftRotation)), Vector3f(first.scale).add(Vector3f(second.scale)), Quaternionf() diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt index 2cbe716..03a530d 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedBone.kt @@ -1,6 +1,7 @@ package io.github.solid.resourcepack.bbconv.api.entity import io.github.solid.resourcepack.bbconv.config.Bone +import io.github.solid.resourcepack.bbconv.config.DevFlags import org.bukkit.Material import org.bukkit.NamespacedKey import org.bukkit.entity.EntityType @@ -15,13 +16,14 @@ import java.util.* class RenderedBone( private val entity: RenderedEntity, val bone: Bone, - val children: MutableList = mutableListOf() + val children: MutableList = mutableListOf(), + private val parent: RenderedBone?, ) { private lateinit var display: ItemDisplay private val initialTransformation = Transformation( Vector3f(bone.origin[0], bone.origin[1], bone.origin[2]), - bone.leftRotation.toQuaternionf(), + if (parent != null) Quaternionf(parent.bone.leftRotation.toQuaternionf()).mul(bone.leftRotation.toQuaternionf()) else bone.leftRotation.toQuaternionf(), Vector3f(bone.scale), Quaternionf(), ) @@ -40,11 +42,17 @@ class RenderedBone( display = entity.location.world.spawnEntity(entity.location, EntityType.ITEM_DISPLAY) as ItemDisplay display.isInvisible = !bone.visible display.itemDisplayTransform = ItemDisplay.ItemDisplayTransform.FIXED - val stack = ItemStack(Material.BONE) - stack.editMeta { - it.itemModel = NamespacedKey.fromString(bone.model.replaceFirst("item/", "")) + if(DevFlags.ANIMATION_DEBUG) { + val stack = ItemStack(Material.CYAN_CONCRETE) + display.setItemStack(stack) + }else { + val stack = ItemStack(Material.BONE) + stack.editMeta { + it.itemModel = NamespacedKey.fromString(bone.model.replaceFirst("item/", "")) + } + display.setItemStack(stack) } - display.setItemStack(stack) + display.transformation = getInitialTransformation() display.interpolationDuration = 1 markDisplay() @@ -85,7 +93,7 @@ class RenderedBone( PersistentDataType.STRING ) if (entityId == entity.id.toString() && boneId == bone.id) { - val rendered = RenderedBone(entity, bone) + val rendered = RenderedBone(entity, bone, parent = null) rendered.display = display return Optional.of(rendered) } diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt index 3fd8068..bff6e4c 100644 --- a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/api/entity/RenderedEntity.kt @@ -47,7 +47,7 @@ data class RenderedEntity( private fun initBone(bone: Bone, parent: RenderedBone? = null): RenderedBone? { var rendered: RenderedBone? = null if (bone.visible) { - rendered = RenderedBone(this, bone) + rendered = RenderedBone(this, bone, parent = parent) parent?.children?.add(rendered) renderedBones[bone.id] = rendered } diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/DevFlags.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/DevFlags.kt new file mode 100644 index 0000000..6d07487 --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/config/DevFlags.kt @@ -0,0 +1,5 @@ +package io.github.solid.resourcepack.bbconv.config + +object DevFlags { + const val ANIMATION_DEBUG = false +} \ No newline at end of file diff --git a/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/QuaternionMath.kt b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/QuaternionMath.kt new file mode 100644 index 0000000..62bf04b --- /dev/null +++ b/api/src/main/kotlin/io/github/solid/resourcepack/bbconv/util/QuaternionMath.kt @@ -0,0 +1,10 @@ +package io.github.solid.resourcepack.bbconv.util + +import org.joml.Quaternionf + +object QuaternionMath { + fun delta(from: Quaternionf, to: Quaternionf): Quaternionf { + val invFrom = Quaternionf(from).invert() + return Quaternionf(to).mul(invFrom) + } +} \ No newline at end of file