Skip to content

Commit

Permalink
Merge pull request #110 from HollowHorizon/main
Browse files Browse the repository at this point in the history
Better animation blending & simple animation controller
  • Loading branch information
fabmax authored Jan 18, 2025
2 parents 7ec216d + 213e71d commit b735352
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 86 deletions.
64 changes: 51 additions & 13 deletions kool-core/src/commonMain/kotlin/de/fabmax/kool/math/Quat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package de.fabmax.kool.math

import de.fabmax.kool.util.Float32Buffer
import de.fabmax.kool.util.MixedBuffer
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.*

fun QuatF.toQuatD() = QuatD(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
fun QuatF.toMutableQuatD(result: MutableQuatD = MutableQuatD()) = result.set(x.toDouble(), y.toDouble(), z.toDouble(), w.toDouble())
Expand Down Expand Up @@ -113,11 +111,31 @@ open class QuatF(open val x: Float, open val y: Float, open val z: Float, open v
* [MutableVec4f]: result = that * weight + this * (1 - weight).
*/
fun mix(that: QuatF, weight: Float, result: MutableQuatF = MutableQuatF()): MutableQuatF {
result.x = that.x * weight + x * (1f - weight)
result.y = that.y * weight + y * (1f - weight)
result.z = that.z * weight + z * (1f - weight)
result.w = that.w * weight + w * (1f - weight)
return result.norm()
val dot = x * that.x + y * that.y + z * that.z + w * that.w
val absCosom = abs(dot)

val scale0: Float
val scale1: Float

if (1.0f - absCosom > FUZZY_EQ_F) {
val sinSqr = 1.0f - absCosom * absCosom
val sinom = 1.0f / sqrt(sinSqr)
val omega = atan2(sqrt(sinSqr), absCosom)
scale0 = sin((1.0f - weight) * omega) * sinom
scale1 = sin(weight * omega) * sinom
} else {
scale0 = 1.0f - weight
scale1 = weight
}

val adjustedScale = if (dot >= 0.0f) scale1 else -scale1

result.x = scale0 * x + adjustedScale * that.x
result.y = scale0 * y + adjustedScale * that.y
result.z = scale0 * z + adjustedScale * that.z
result.w = scale0 * w + adjustedScale * that.w

return result
}

/**
Expand Down Expand Up @@ -434,11 +452,31 @@ open class QuatD(open val x: Double, open val y: Double, open val z: Double, ope
* [MutableVec4d]: result = that * weight + this * (1 - weight).
*/
fun mix(that: QuatD, weight: Double, result: MutableQuatD = MutableQuatD()): MutableQuatD {
result.x = that.x * weight + x * (1.0 - weight)
result.y = that.y * weight + y * (1.0 - weight)
result.z = that.z * weight + z * (1.0 - weight)
result.w = that.w * weight + w * (1.0 - weight)
return result.norm()
val dot = x * that.x + y * that.y + z * that.z + w * that.w
val absCosom = abs(dot)

val scale0: Double
val scale1: Double

if (1.0 - absCosom > FUZZY_EQ_D) {
val sinSqr = 1.0 - absCosom * absCosom
val sinom = 1.0 / sqrt(sinSqr)
val omega = atan2(sqrt(sinSqr), absCosom)
scale0 = sin((1.0 - weight) * omega) * sinom
scale1 = sin(weight * omega) * sinom
} else {
scale0 = 1.0 - weight
scale1 = weight
}

val adjustedScale = if (dot >= 0.0) scale1 else -scale1

result.x = scale0 * x + adjustedScale * that.x
result.y = scale0 * y + adjustedScale * that.y
result.z = scale0 * z + adjustedScale * that.z
result.w = scale0 * w + adjustedScale * that.w

return result
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ data class GltfFile(
val modelAnim = Animation(anim.name)
modelAnimations += modelAnim

val animNodes = mutableMapOf<Node, AnimationNode>()
val animNodes = mutableMapOf<Node, AnimatedTransformGroup>()
anim.channels.forEach { channel ->
val nodeGrp = modelNodes[channel.target.nodeRef]
if (nodeGrp != null) {
Expand All @@ -240,7 +240,7 @@ data class GltfFile(
}
}

private fun makeTranslationAnimation(animCh: GltfAnimation.Channel, animNd: AnimationNode, modelAnim: Animation) {
private fun makeTranslationAnimation(animCh: GltfAnimation.Channel, animNd: AnimatedTransformGroup, modelAnim: Animation) {
val inputAcc = animCh.samplerRef.inputAccessorRef
val outputAcc = animCh.samplerRef.outputAccessorRef

Expand All @@ -261,6 +261,7 @@ data class GltfFile(
}
modelAnim.channels += transChannel

val bindTranslation = animNd.initTranslation
val inTime = FloatAccessor(inputAcc)
val outTranslation = Vec3fAccessor(outputAcc)
for (i in 0 until min(inputAcc.count, outputAcc.count)) {
Expand All @@ -269,16 +270,21 @@ data class GltfFile(
val startTan = outTranslation.next()
val point = outTranslation.next()
val endTan = outTranslation.next()
CubicTranslationKey(t, point, startTan, endTan)
CubicTranslationKey(
t,
point - bindTranslation,
startTan - bindTranslation,
endTan - bindTranslation
)
} else {
TranslationKey(t, outTranslation.next())
TranslationKey(t, outTranslation.next() - bindTranslation)
}
transKey.interpolation = interpolation
transChannel.keys[t] = transKey
}
}

private fun makeRotationAnimation(animCh: GltfAnimation.Channel, animNd: AnimationNode, modelAnim: Animation) {
private fun makeRotationAnimation(animCh: GltfAnimation.Channel, animNd: AnimatedTransformGroup, modelAnim: Animation) {
val inputAcc = animCh.samplerRef.inputAccessorRef
val outputAcc = animCh.samplerRef.outputAccessorRef

Expand All @@ -299,6 +305,7 @@ data class GltfFile(
}
modelAnim.channels += rotChannel

val bindRotation = animNd.initRotation
val inTime = FloatAccessor(inputAcc)
val outRotation = Vec4fAccessor(outputAcc)
for (i in 0 until min(inputAcc.count, outputAcc.count)) {
Expand All @@ -307,16 +314,21 @@ data class GltfFile(
val startTan = outRotation.next().toQuatF()
val point = outRotation.next().toQuatF()
val endTan = outRotation.next().toQuatF()
CubicRotationKey(t, point, startTan, endTan)
CubicRotationKey(
t,
bindRotation.inverted().mul(point),
bindRotation.inverted().mul(startTan),
bindRotation.inverted().mul(endTan)
)
} else {
RotationKey(t, outRotation.next().toQuatF())
RotationKey(t, bindRotation.inverted().mul(outRotation.next().toQuatF()))
}
rotKey.interpolation = interpolation
rotChannel.keys[t] = rotKey
}
}

private fun makeScaleAnimation(animCh: GltfAnimation.Channel, animNd: AnimationNode, modelAnim: Animation) {
private fun makeScaleAnimation(animCh: GltfAnimation.Channel, animNd: AnimatedTransformGroup, modelAnim: Animation) {
val inputAcc = animCh.samplerRef.inputAccessorRef
val outputAcc = animCh.samplerRef.outputAccessorRef

Expand All @@ -337,6 +349,7 @@ data class GltfFile(
}
modelAnim.channels += scaleChannel

val bindScale = animNd.initScale
val inTime = FloatAccessor(inputAcc)
val outScale = Vec3fAccessor(outputAcc)
for (i in 0 until min(inputAcc.count, outputAcc.count)) {
Expand All @@ -345,9 +358,9 @@ data class GltfFile(
val startTan = outScale.next()
val point = outScale.next()
val endTan = outScale.next()
CubicScaleKey(t, point, startTan, endTan)
CubicScaleKey(t, bindScale / point, bindScale / startTan, bindScale / endTan)
} else {
ScaleKey(t, outScale.next())
ScaleKey(t, bindScale / outScale.next())
}
scaleKey.interpolation = interpolation
scaleChannel.keys[t] = scaleKey
Expand Down
2 changes: 2 additions & 0 deletions kool-core/src/commonMain/kotlin/de/fabmax/kool/scene/Model.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Model(name: String? = null) : Node(name) {
}

fun applyAnimation(deltaT: Float) {
animations.forEach(Animation::reset)

var firstActive = true
for (i in animations.indices) {
if (animations[i].weight > 0f) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import de.fabmax.kool.math.*
import de.fabmax.kool.scene.MatrixTransformF
import de.fabmax.kool.scene.Mesh
import de.fabmax.kool.scene.Node
import de.fabmax.kool.scene.TrsTransformF
import de.fabmax.kool.util.TreeMap
import de.fabmax.kool.util.logE
import kotlin.math.min
Expand All @@ -26,23 +27,21 @@ class Animation(val name: String?) {
animationNodes += channels.map { it.animationNode }.distinct()
}

fun apply(deltaT: Float, firstWeightedTransform: Boolean = true) {
progress = (progress + duration + deltaT * speed) % duration

fun reset() {
for (i in animationNodes.indices) {
animationNodes[i].initTransform()
}
}

fun apply(deltaT: Float, firstWeightedTransform: Boolean = true) {
progress = (progress + duration + deltaT * speed) % duration

for (i in channels.indices) {
channels[i].apply(progress)
}
if (weight == 1f) {
for (i in animationNodes.indices) {
animationNodes[i].applyTransform()
}
} else {
for (i in animationNodes.indices) {
animationNodes[i].applyTransformWeighted(weight, firstWeightedTransform)
}

for (i in animationNodes.indices) {
animationNodes[i].applyTransformWeighted(weight, firstWeightedTransform)
}
}

Expand All @@ -55,7 +54,7 @@ class Animation(val name: String?) {
}
}

abstract class AnimationChannel<T: AnimationKey<T>>(val name: String?, val animationNode: AnimationNode) {
abstract class AnimationChannel<T : AnimationKey<T>>(val name: String?, val animationNode: AnimationNode) {
val keys = TreeMap<Float, T>()
val lastKeyTime: Float
get() = keys.lastKey()
Expand All @@ -75,47 +74,51 @@ abstract class AnimationChannel<T: AnimationKey<T>>(val name: String?, val anima
println("$indent${animKeys[i]}")
}
if (animKeys.size > 5) {
println("$indent ...${animKeys.size-5} more")
println("$indent ...${animKeys.size - 5} more")
}
}
}

class TranslationAnimationChannel(name: String?, animationNode: AnimationNode): AnimationChannel<TranslationKey>(name, animationNode)
class TranslationAnimationChannel(name: String?, animationNode: AnimationNode) :
AnimationChannel<TranslationKey>(name, animationNode)

class RotationAnimationChannel(name: String?, animationNode: AnimationNode): AnimationChannel<RotationKey>(name, animationNode)
class RotationAnimationChannel(name: String?, animationNode: AnimationNode) :
AnimationChannel<RotationKey>(name, animationNode)

class ScaleAnimationChannel(name: String?, animationNode: AnimationNode): AnimationChannel<ScaleKey>(name, animationNode)
class ScaleAnimationChannel(name: String?, animationNode: AnimationNode) :
AnimationChannel<ScaleKey>(name, animationNode)

class WeightAnimationChannel(name: String?, animationNode: AnimationNode): AnimationChannel<WeightKey>(name, animationNode)
class WeightAnimationChannel(name: String?, animationNode: AnimationNode) :
AnimationChannel<WeightKey>(name, animationNode)

interface AnimationNode {
val name: String

fun initTransform() { }
fun initTransform() {}
fun applyTransform()
fun applyTransformWeighted(weight: Float, firstWeightedTransform: Boolean)

fun setTranslation(translation: Vec3f) { }
fun setRotation(rotation: QuatF) { }
fun setScale(scale: Vec3f) { }
fun setTranslation(translation: Vec3f) {}
fun setRotation(rotation: QuatF) {}
fun setScale(scale: Vec3f) {}

fun setWeights(weights: FloatArray) { }
fun setWeights(weights: FloatArray) {}
}

class AnimatedTransformGroup(val target: Node): AnimationNode {
class AnimatedTransformGroup(val target: Node) : AnimationNode {
override val name: String
get() = target.name

private val initTranslation = MutableVec3f()
private val initRotation = MutableQuatF()
private val initScale = MutableVec3f(Vec3f.ONES)
val initTranslation = MutableVec3f()
val initRotation = MutableQuatF()
val initScale = MutableVec3f(Vec3f.ONES)

private val animTranslation = MutableVec3f()
private val animRotation = MutableQuatF()
private val animScale = MutableVec3f()
private val animScale = MutableVec3f(1f, 1f, 1f)

private val quatRotMat = MutableMat4f()
private val weightedTransformMat = MutableMat4f()
private val baseRotation = MutableQuatF()
private val baseScale = MutableVec3f(Vec3f.ONES)

init {
val vec4 = MutableVec4f()
Expand All @@ -129,50 +132,30 @@ class AnimatedTransformGroup(val target: Node): AnimationNode {
}

override fun initTransform() {
animTranslation.set(initTranslation)
animRotation.set(initRotation)
animScale.set(initScale)
var t = target.transform
if (t !is TrsTransformF) {
t = TrsTransformF()
target.transform = t
}
t.setCompositionOf(initTranslation, initRotation, initScale)
}

override fun applyTransform() {
target.transform.setCompositionOf(animTranslation, animRotation, animScale)
}

override fun applyTransformWeighted(weight: Float, firstWeightedTransform: Boolean) {
weightedTransformMat.setIdentity()
weightedTransformMat.translate(animTranslation)
weightedTransformMat.rotate(animRotation)
weightedTransformMat.scale(animScale)

var t = target.transform as? MatrixTransformF
if (t == null) {
t = MatrixTransformF()
var t = target.transform
if (t !is TrsTransformF) {
t = TrsTransformF()
target.transform = t
}

val wm = if (firstWeightedTransform) 0f else 1f

t.matrixF.m00 = t.matrixF.m00 * wm + weightedTransformMat.m00 * weight
t.matrixF.m01 = t.matrixF.m01 * wm + weightedTransformMat.m01 * weight
t.matrixF.m02 = t.matrixF.m02 * wm + weightedTransformMat.m02 * weight
t.matrixF.m03 = t.matrixF.m03 * wm + weightedTransformMat.m03 * weight

t.matrixF.m10 = t.matrixF.m10 * wm + weightedTransformMat.m10 * weight
t.matrixF.m11 = t.matrixF.m11 * wm + weightedTransformMat.m11 * weight
t.matrixF.m12 = t.matrixF.m12 * wm + weightedTransformMat.m12 * weight
t.matrixF.m13 = t.matrixF.m13 * wm + weightedTransformMat.m13 * weight

t.matrixF.m20 = t.matrixF.m20 * wm + weightedTransformMat.m20 * weight
t.matrixF.m21 = t.matrixF.m21 * wm + weightedTransformMat.m21 * weight
t.matrixF.m22 = t.matrixF.m22 * wm + weightedTransformMat.m22 * weight
t.matrixF.m23 = t.matrixF.m23 * wm + weightedTransformMat.m23 * weight

t.matrixF.m30 = t.matrixF.m30 * wm + weightedTransformMat.m30 * weight
t.matrixF.m31 = t.matrixF.m31 * wm + weightedTransformMat.m31 * weight
t.matrixF.m32 = t.matrixF.m32 * wm + weightedTransformMat.m32 * weight
t.matrixF.m33 = t.matrixF.m33 * wm + weightedTransformMat.m33 * weight
t.translate(animTranslation.mul(weight))
t.rotate(baseRotation.mix(animRotation, weight))
t.scale(baseScale.mix(animScale, weight))

target.transform.markDirty()
t.markDirty()
}

override fun setTranslation(translation: Vec3f) {
Expand All @@ -188,7 +171,7 @@ class AnimatedTransformGroup(val target: Node): AnimationNode {
}
}

class MorphAnimatedMesh(val target: Mesh): AnimationNode {
class MorphAnimatedMesh(val target: Mesh) : AnimationNode {
override val name: String
get() = target.name

Expand Down
Loading

0 comments on commit b735352

Please sign in to comment.