MotionScoreSmoke.kt
src/jvmMain/kotlin/afield/animation/mocap/MotionScoreSmoke.kt · 65 lines
@file:afield.runtime.completion.CompletionCategory("Animation")
package afield.animation.mocap
import afield.math.linalg.Vec3
import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.milliseconds
FIELD_TEST id: motion.score.gpu runner: jvm-main entrypoint: afield.animation.mocap.MotionScoreSmoke tags: [smoke, optional, animation, motion, gpu, closd] timeoutSec: 240 expect: exitCode: 0 stdoutContains: - "Motion score smoke"
object MotionScoreSmoke
Real physical-body authoring, moving anchor, observation, reset and reused-handle ordering.
@JvmStatic fun main(args: Array<String>)
body — 42 lines, from line 23
@JvmStatic fun main(args: Array<String>) {
if (System.getenv("FIELD_MOTION_HOME").isNullOrBlank()) {
println("Motion score smoke SKIPPED: set FIELD_MOTION_HOME for actual CLoSD physics")
return
}
CLoSD.openAsync().get(180,TimeUnit.SECONDS).use { dancer ->
dancer.prompt("A person walks forward.")
dancer.advance(.1).get(120,TimeUnit.SECONDS)
val foot=dancer.body("L_Ankle").tether("left") {
stiffness=8000.0; damping=120.0; maxForce=2500.0; ramp=200.milliseconds
}
foot.accepted.get(5,TimeUnit.SECONDS)
val held=dancer.advance(2.0).get(120,TimeUnit.SECONDS)
check(held.events.any { it.optString("type")=="tetherPhysics" })
check(foot.observation?.active==true && foot.observation!!.forceN>0)
check(foot.observation!!.forceN<=2500.00001)
foot.tune { velocity=Vec3(.05f,0f,0f) }.get(5,TimeUnit.SECONDS)
dancer.advance(.1).get(120,TimeUnit.SECONDS)
val a=foot.observation!!.anchor
foot.moveTo(Vec3(a[0].toFloat()+.1f,a[1].toFloat(),a[2].toFloat())).get(5,TimeUnit.SECONDS)
dancer.advance(.1).get(120,TimeUnit.SECONDS)
foot.release().get(5,TimeUnit.SECONDS)
dancer.advance(.1).get(120,TimeUnit.SECONDS)
check(foot.observation?.reason=="released")
check(runCatching { foot.tune { stiffness=1.0 } }.isFailure)
dancer.reset(dancer.editableTime).get(5,TimeUnit.SECONDS)
dancer.advance(.1).get(120,TimeUnit.SECONDS)
check(foot.observation?.reason=="reset")
check(foot.release().get(5,TimeUnit.SECONDS)==null)
// Reusing an id must not inherit the previous reset state and swallow this release.
val again=dancer.body("L_Ankle").tether("left")
again.release().get(5,TimeUnit.SECONDS)
dancer.advance(.1).get(120,TimeUnit.SECONDS)
check(again.observation?.reason=="released")
dancer.body("Torso").impulse(Vec3(10f,0f,0f)).get(5,TimeUnit.SECONDS)
val impulse=dancer.advance(.1).get(120,TimeUnit.SECONDS)
check(impulse.events.count { it.optString("type")=="impulsePhysics" }==1)
check(dancer.failure==null)
}
println("Motion score smoke passed: actual GPU tether/tune/move/release/reset, reused id ordering and one-shot impulse")
}
}