diff --git a/studio-frontend/src/app/app.element.ts b/studio-frontend/src/app/app.element.ts index 9f1e3fc..989d64f 100644 --- a/studio-frontend/src/app/app.element.ts +++ b/studio-frontend/src/app/app.element.ts @@ -21,6 +21,7 @@ import { Ref, createRef, ref } from "lit-html/directives/ref.js"; import "@storyteller/studio-web/canvas-provider"; import "@storyteller/studio-web/viewer"; import "@storyteller/studio-web/bvh-viewer"; +import "@storyteller/studio-web/mixamo-viewer"; import "./button.element"; import "./help.element"; import "./icon.element"; @@ -46,6 +47,7 @@ export class AppElement extends LitElement { @state() selection?: string | null; @state() bvhPath?: string; + @state() mixamoPath?: string; @state() mode?: StudioMode; #sceneObjects: studio.SceneObject[] = []; @@ -67,6 +69,9 @@ export class AppElement extends LitElement { if (params.has("bvh")) this.bvhPath = params.get("bvh")!; + if (params.has("mixamo")) + this.mixamoPath = params.get("mixamo")!; + this.mode = match (params.get("mode"), { "studio": () => StudioMode.Editor, "viewer": () => StudioMode.Viewer, @@ -195,6 +200,12 @@ export class AppElement extends LitElement { skyboxId="test_scene" @scene-state=${this.onSceneStateChange} > + ` : this.mixamoPath ? html` + ` : html` ): void { + if (changes.has("mixamoPath") && !!this.mixamoPath) { + // TODO + } + + if (changes.has("skyboxId") && !!this.skyboxId) + studio.setSkybox(this.skyboxId); + } +} diff --git a/studio/assets/mocap/hip-hop-dancing.bin b/studio/assets/mocap/hip-hop-dancing.bin new file mode 100644 index 0000000..896f963 Binary files /dev/null and b/studio/assets/mocap/hip-hop-dancing.bin differ diff --git a/studio/assets/mocap/hip-hop-dancing.gltf b/studio/assets/mocap/hip-hop-dancing.gltf new file mode 100644 index 0000000..2576aef --- /dev/null +++ b/studio/assets/mocap/hip-hop-dancing.gltf @@ -0,0 +1,3171 @@ +{ + "asset": { + "generator": "FBX2glTF v0.9.7", + "version": "2.0" + }, + "scene": 0, + "buffers": [ + { + "byteLength": 2016720, + "uri": "hip-hop-dancing.bin" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 612, + "byteOffset": 0 + }, + { + "buffer": 0, + "byteLength": 1836, + "byteOffset": 612 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 2448 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 4896 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 7344 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 9792 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 12240 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 14688 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 17136 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 19584 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 22032 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 24480 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 26928 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 29376 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 31824 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 34272 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 36720 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 39168 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 41616 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 44064 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 46512 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 48960 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 51408 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 53856 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 56304 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 58752 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 61200 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 63648 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 66096 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 68544 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 70992 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 73440 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 75888 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 78336 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 80784 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 83232 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 85680 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 88128 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 90576 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 93024 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 95472 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 97920 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 100368 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 102816 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 105264 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 107712 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 110160 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 112608 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 115056 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 117504 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 119952 + }, + { + "buffer": 0, + "byteLength": 2448, + "byteOffset": 122400 + }, + { + "buffer": 0, + "byteLength": 169632, + "byteOffset": 124848, + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 190812, + "byteOffset": 294480, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 190812, + "byteOffset": 485292, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 127208, + "byteOffset": 676104, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 127208, + "byteOffset": 803312, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 254416, + "byteOffset": 930520, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 125040, + "byteOffset": 1184936, + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 149676, + "byteOffset": 1309976, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 149676, + "byteOffset": 1459652, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 99784, + "byteOffset": 1609328, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 99784, + "byteOffset": 1709112, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 199568, + "byteOffset": 1808896, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 4160, + "byteOffset": 2008464 + }, + { + "buffer": 0, + "byteLength": 4096, + "byteOffset": 2012624 + } + ], + "scenes": [ + { + "name": "Root Scene", + "nodes": [ + 0 + ] + } + ], + "accessors": [ + { + "componentType": 5126, + "type": "SCALAR", + "count": 153, + "bufferView": 0, + "byteOffset": 0, + "min": [ + 0.0 + ], + "max": [ + 6.33333349227905 + ] + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 153, + "bufferView": 1, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 2, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 3, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 4, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 5, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 6, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 7, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 8, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 9, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 10, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 11, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 12, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 13, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 14, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 15, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 16, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 17, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 18, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 19, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 20, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 21, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 22, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 23, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 24, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 25, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 26, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 27, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 28, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 29, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 30, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 31, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 32, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 33, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 34, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 35, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 36, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 37, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 38, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 39, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 40, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 41, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 42, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 43, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 44, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 45, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 46, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 47, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 48, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 49, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 50, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 153, + "bufferView": 51, + "byteOffset": 0 + }, + { + "componentType": 5123, + "type": "SCALAR", + "count": 84816, + "bufferView": 52, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 15901, + "bufferView": 53, + "byteOffset": 0, + "min": [ + -0.902566194534302, + -0.00032193775405176, + -0.148957028985023 + ], + "max": [ + 0.902565836906433, + 1.80888319015503, + 0.17174169421196 + ] + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 15901, + "bufferView": 54, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC2", + "count": 15901, + "bufferView": 55, + "byteOffset": 0 + }, + { + "componentType": 5123, + "type": "VEC4", + "count": 15901, + "bufferView": 56, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 15901, + "bufferView": 57, + "byteOffset": 0 + }, + { + "componentType": 5123, + "type": "SCALAR", + "count": 62520, + "bufferView": 58, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 12473, + "bufferView": 59, + "byteOffset": 0, + "min": [ + -0.883467078208923, + -0.00035116117214784, + -0.12990514934063 + ], + "max": [ + 0.883466720581055, + 1.59644222259521, + 0.125019237399101 + ] + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 12473, + "bufferView": 60, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC2", + "count": 12473, + "bufferView": 61, + "byteOffset": 0 + }, + { + "componentType": 5123, + "type": "VEC4", + "count": 12473, + "bufferView": 62, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC4", + "count": 12473, + "bufferView": 63, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "MAT4", + "count": 65, + "bufferView": 64, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "MAT4", + "count": 64, + "bufferView": 65, + "byteOffset": 0 + } + ], + "samplers": [ + {} + ], + "materials": [ + { + "name": "Beta_HighLimbsGeoSG3", + "alphaMode": "OPAQUE", + "extras": { + "fromFBX": { + "shadingModel": "Phong", + "isTruePBR": false + } + }, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.669600009918213, + 0.241846427321434, + 0.21092401444912, + 1.0 + ], + "metallicFactor": 0.400000005960464, + "roughnessFactor": 0.593640983104706 + } + }, + { + "name": "Beta_Joints_MAT1", + "alphaMode": "OPAQUE", + "extras": { + "fromFBX": { + "shadingModel": "Phong", + "isTruePBR": false + } + }, + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.266666680574417, + 0.0996231064200401, + 0.0808117911219597, + 1.0 + ], + "metallicFactor": 0.400000005960464, + "roughnessFactor": 0.70710676908493 + } + } + ], + "meshes": [ + { + "name": "Beta_Surface", + "primitives": [ + { + "material": 0, + "mode": 4, + "attributes": { + "JOINTS_0": 56, + "NORMAL": 54, + "POSITION": 53, + "TEXCOORD_0": 55, + "WEIGHTS_0": 57 + }, + "indices": 52 + } + ] + }, + { + "name": "Beta_Joints", + "primitives": [ + { + "material": 1, + "mode": 4, + "attributes": { + "JOINTS_0": 62, + "NORMAL": 60, + "POSITION": 59, + "TEXCOORD_0": 61, + "WEIGHTS_0": 63 + }, + "indices": 58 + } + ] + } + ], + "skins": [ + { + "joints": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65 + ], + "inverseBindMatrices": 64, + "skeleton": 1 + }, + { + "joints": [ + 1, + 2, + 3, + 4, + 5, + 6, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65 + ], + "inverseBindMatrices": 65, + "skeleton": 1 + } + ], + "animations": [ + { + "name": "mixamo.com", + "channels": [ + { + "sampler": 0, + "target": { + "node": 1, + "path": "translation" + } + }, + { + "sampler": 1, + "target": { + "node": 1, + "path": "rotation" + } + }, + { + "sampler": 2, + "target": { + "node": 2, + "path": "rotation" + } + }, + { + "sampler": 3, + "target": { + "node": 3, + "path": "rotation" + } + }, + { + "sampler": 4, + "target": { + "node": 4, + "path": "rotation" + } + }, + { + "sampler": 5, + "target": { + "node": 5, + "path": "rotation" + } + }, + { + "sampler": 6, + "target": { + "node": 6, + "path": "rotation" + } + }, + { + "sampler": 7, + "target": { + "node": 8, + "path": "rotation" + } + }, + { + "sampler": 8, + "target": { + "node": 9, + "path": "rotation" + } + }, + { + "sampler": 9, + "target": { + "node": 10, + "path": "rotation" + } + }, + { + "sampler": 10, + "target": { + "node": 11, + "path": "rotation" + } + }, + { + "sampler": 11, + "target": { + "node": 12, + "path": "rotation" + } + }, + { + "sampler": 12, + "target": { + "node": 13, + "path": "rotation" + } + }, + { + "sampler": 13, + "target": { + "node": 14, + "path": "rotation" + } + }, + { + "sampler": 14, + "target": { + "node": 16, + "path": "rotation" + } + }, + { + "sampler": 15, + "target": { + "node": 17, + "path": "rotation" + } + }, + { + "sampler": 16, + "target": { + "node": 18, + "path": "rotation" + } + }, + { + "sampler": 17, + "target": { + "node": 20, + "path": "rotation" + } + }, + { + "sampler": 18, + "target": { + "node": 21, + "path": "rotation" + } + }, + { + "sampler": 19, + "target": { + "node": 22, + "path": "rotation" + } + }, + { + "sampler": 20, + "target": { + "node": 24, + "path": "rotation" + } + }, + { + "sampler": 21, + "target": { + "node": 25, + "path": "rotation" + } + }, + { + "sampler": 22, + "target": { + "node": 26, + "path": "rotation" + } + }, + { + "sampler": 23, + "target": { + "node": 28, + "path": "rotation" + } + }, + { + "sampler": 24, + "target": { + "node": 29, + "path": "rotation" + } + }, + { + "sampler": 25, + "target": { + "node": 30, + "path": "rotation" + } + }, + { + "sampler": 26, + "target": { + "node": 32, + "path": "rotation" + } + }, + { + "sampler": 27, + "target": { + "node": 33, + "path": "rotation" + } + }, + { + "sampler": 28, + "target": { + "node": 34, + "path": "rotation" + } + }, + { + "sampler": 29, + "target": { + "node": 35, + "path": "rotation" + } + }, + { + "sampler": 30, + "target": { + "node": 36, + "path": "rotation" + } + }, + { + "sampler": 31, + "target": { + "node": 37, + "path": "rotation" + } + }, + { + "sampler": 32, + "target": { + "node": 38, + "path": "rotation" + } + }, + { + "sampler": 33, + "target": { + "node": 40, + "path": "rotation" + } + }, + { + "sampler": 34, + "target": { + "node": 41, + "path": "rotation" + } + }, + { + "sampler": 35, + "target": { + "node": 42, + "path": "rotation" + } + }, + { + "sampler": 36, + "target": { + "node": 44, + "path": "rotation" + } + }, + { + "sampler": 37, + "target": { + "node": 45, + "path": "rotation" + } + }, + { + "sampler": 38, + "target": { + "node": 46, + "path": "rotation" + } + }, + { + "sampler": 39, + "target": { + "node": 48, + "path": "rotation" + } + }, + { + "sampler": 40, + "target": { + "node": 52, + "path": "rotation" + } + }, + { + "sampler": 41, + "target": { + "node": 53, + "path": "rotation" + } + }, + { + "sampler": 42, + "target": { + "node": 54, + "path": "rotation" + } + }, + { + "sampler": 43, + "target": { + "node": 56, + "path": "rotation" + } + }, + { + "sampler": 44, + "target": { + "node": 57, + "path": "rotation" + } + }, + { + "sampler": 45, + "target": { + "node": 58, + "path": "rotation" + } + }, + { + "sampler": 46, + "target": { + "node": 59, + "path": "rotation" + } + }, + { + "sampler": 47, + "target": { + "node": 61, + "path": "rotation" + } + }, + { + "sampler": 48, + "target": { + "node": 62, + "path": "rotation" + } + }, + { + "sampler": 49, + "target": { + "node": 63, + "path": "rotation" + } + }, + { + "sampler": 50, + "target": { + "node": 64, + "path": "rotation" + } + } + ], + "samplers": [ + { + "input": 0, + "interpolation": "LINEAR", + "output": 1 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 2 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 3 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 4 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 5 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 6 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 7 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 8 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 9 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 10 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 11 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 12 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 13 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 14 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 15 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 16 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 17 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 18 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 19 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 20 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 21 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 22 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 23 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 24 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 25 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 26 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 27 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 28 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 29 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 30 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 31 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 32 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 33 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 34 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 35 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 36 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 37 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 38 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 39 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 40 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 41 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 42 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 43 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 44 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 45 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 46 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 47 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 48 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 49 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 50 + }, + { + "input": 0, + "interpolation": "LINEAR", + "output": 51 + } + ] + } + ], + "nodes": [ + { + "name": "RootNode", + "translation": [ + 0.0, + 0.0, + 0.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 1, + 66, + 67 + ] + }, + { + "name": "mixamorig:Hips", + "translation": [ + -7.72729080722456e-08, + 1.04274868965149, + 0.0155431581661105 + ], + "rotation": [ + 0.00645854603499174, + 0.0, + 0.0, + 0.999979138374329 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 2, + 56, + 61 + ] + }, + { + "name": "mixamorig:Spine", + "translation": [ + -4.25583049704081e-12, + 0.10182436555624, + -3.996802716597e-17 + ], + "rotation": [ + -0.080155722796917, + 1.65970161621918e-24, + -2.48068090513698e-07, + 0.99678236246109 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 3 + ] + }, + { + "name": "mixamorig:Spine1", + "translation": [ + 0.0, + 0.100026749074459, + 3.55271359939116e-17 + ], + "rotation": [ + -0.0, + -3.30872245021211e-24, + -3.88774887899923e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 4 + ] + }, + { + "name": "mixamorig:Spine2", + "translation": [ + 1.13754221945427e-16, + 0.0932209119200706, + 2.82368106674369e-09 + ], + "rotation": [ + 0.0128856198862195, + -4.7711740114309e-10, + -3.70240442748582e-08, + 0.999916970729828 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 5, + 8, + 32 + ] + }, + { + "name": "mixamorig:Neck", + "translation": [ + 4.14748834234868e-12, + 0.168653219938278, + 5.68434175902586e-16 + ], + "rotation": [ + -1.38777878078145e-17, + -3.30872245021211e-24, + -2.60561892954204e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 6 + ] + }, + { + "name": "mixamorig:Head", + "translation": [ + -1.07567477147796e-09, + 0.0934192314743996, + 0.0284104086458683 + ], + "rotation": [ + -1.38777878078145e-17, + -9.92616735063633e-24, + 2.60561892954204e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 7 + ] + }, + { + "name": "mixamorig:HeadTop_End", + "translation": [ + 9.85697390376572e-09, + 0.20962780714035, + 0.101229347288609 + ], + "rotation": [ + 8.32667268468867e-17, + -3.30872245021211e-24, + 2.56425989891439e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:RightShoulder", + "translation": [ + -0.0456997007131577, + 0.1119584441185, + -0.00806631799787283 + ], + "rotation": [ + -0.484430640935898, + 0.570963680744171, + -0.526163637638092, + -0.403087168931961 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 9 + ] + }, + { + "name": "mixamorig:RightArm", + "translation": [ + 2.66453519954337e-17, + 0.108381755650043, + -2.84217087951293e-16 + ], + "rotation": [ + -0.0246162414550781, + 0.00256229168735445, + -0.103498414158821, + 0.994321644306183 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 10 + ] + }, + { + "name": "mixamorig:RightForeArm", + "translation": [ + -9.96406429587537e-11, + 0.27841517329216, + 2.84217087951293e-16 + ], + "rotation": [ + 4.15476157156535e-23, + 0.0, + 1.32348898008484e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 11 + ] + }, + { + "name": "mixamorig:RightHand", + "translation": [ + 2.28585989248131e-07, + 0.283288389444351, + 4.47253860613728e-08 + ], + "rotation": [ + 9.44871780744909e-23, + 5.29395592033938e-23, + -2.64697796016969e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 12, + 16, + 20, + 24, + 28 + ] + }, + { + "name": "mixamorig:RightHandThumb1", + "translation": [ + 0.0268185175955296, + 0.0246480498462915, + 0.0157398395240307 + ], + "rotation": [ + 0.253284484148026, + 0.0616182722151279, + -0.228210255503654, + 0.938067257404327 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 13 + ] + }, + { + "name": "mixamorig:RightHandThumb2", + "translation": [ + -1.68753897625441e-16, + 0.0418898537755013, + -2.84217087951293e-16 + ], + "rotation": [ + -0.00231574918143451, + -0.00117044895887375, + 0.00930010993033648, + 0.999953389167786 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 14 + ] + }, + { + "name": "mixamorig:RightHandThumb3", + "translation": [ + -1.45439209873148e-16, + 0.0341627113521099, + 0.0 + ], + "rotation": [ + -0.00166473828721792, + -0.000807781063485891, + 0.00691974768415093, + 0.999974370002747 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 15 + ] + }, + { + "name": "mixamorig:RightHandThumb4", + "translation": [ + -1.32116533048251e-16, + 0.0257497727870941, + -2.84217087951293e-16 + ], + "rotation": [ + 0.00577485375106335, + -0.117509201169014, + -0.0487437099218369, + 0.991858005523682 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:RightHandIndex1", + "translation": [ + 0.0225983504205942, + 0.091082826256752, + 0.0051786215044558 + ], + "rotation": [ + -6.0593407624765e-07, + 9.95352897326107e-11, + -0.000164267534273677, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 17 + ] + }, + { + "name": "mixamorig:RightHandIndex2", + "translation": [ + -8.43769488127207e-17, + 0.0370000004768372, + 0.0 + ], + "rotation": [ + 1.74034091066008e-23, + -2.64653320039132e-23, + 0.000255644234130159, + 0.999999940395355 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 18 + ] + }, + { + "name": "mixamorig:RightHandIndex3", + "translation": [ + -3.42836860686886e-15, + 0.028499998152256, + -8.89315270730107e-12 + ], + "rotation": [ + -5.79545436153882e-23, + 5.09547792062601e-27, + -8.79219733178616e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 19 + ] + }, + { + "name": "mixamorig:RightHandIndex4", + "translation": [ + 7.90478759651794e-15, + 0.0277219042181969, + 5.11420246429584e-12 + ], + "rotation": [ + 8.65985967379856e-08, + 0.00100269203539938, + 8.70583244250156e-05, + 0.999999523162842 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:RightHandMiddle1", + "translation": [ + -2.36873233916413e-07, + 0.0953250080347061, + 3.9855689237811e-07 + ], + "rotation": [ + -9.78174057308934e-07, + 4.90853580004114e-10, + -0.000501805916428566, + 0.99999988079071 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 21 + ] + }, + { + "name": "mixamorig:RightHandMiddle2", + "translation": [ + -1.06581407981735e-16, + 0.0370000004768372, + 0.0 + ], + "rotation": [ + -2.8641118612229e-22, + -2.43412769807742e-25, + 0.000849871430546045, + 0.999999642372131 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 22 + ] + }, + { + "name": "mixamorig:RightHandMiddle3", + "translation": [ + -2.52242663571539e-15, + 0.0295000001788139, + -1.54113583006321e-10 + ], + "rotation": [ + -6.51138854706014e-23, + -5.29186070577511e-23, + -0.000321842002449557, + 0.999999940395355 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 23 + ] + }, + { + "name": "mixamorig:RightHandMiddle4", + "translation": [ + 1.13686835180517e-15, + 0.029466288164258, + 7.20586923463884e-11 + ], + "rotation": [ + 1.54982686240146e-07, + 0.000928814173676074, + 0.0001663944858592, + 0.999999582767487 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:RightHandRing1", + "translation": [ + -0.0186513308435678, + 0.0910359174013138, + 0.000430696498369798 + ], + "rotation": [ + 6.96316604376079e-08, + 1.08506528415098e-11, + 0.000155829300638288, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 25 + ] + }, + { + "name": "mixamorig:RightHandRing2", + "translation": [ + -5.32907039908674e-17, + 0.0337927304208279, + 0.0 + ], + "rotation": [ + 4.13913423703292e-23, + -9.16880704186738e-27, + -0.000221515088924207, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 26 + ] + }, + { + "name": "mixamorig:RightHandRing3", + "translation": [ + 2.62900815619369e-15, + 0.0288968458771706, + 8.20222102043688e-12 + ], + "rotation": [ + 2.09479473249325e-23, + -2.64721430289689e-23, + -0.000112805100798141, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 27 + ] + }, + { + "name": "mixamorig:RightHandRing4", + "translation": [ + 4.72510892175412e-15, + 0.0263881552964449, + 5.68206809728466e-12 + ], + "rotation": [ + -1.33964519477558e-07, + 0.000146075006341562, + -0.000799286179244518, + 0.999999642372131 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:RightHandPinky1", + "translation": [ + -0.0380627736449242, + 0.0807667598128319, + 0.00486703682690859 + ], + "rotation": [ + 4.67071913590189e-06, + 2.43046982539852e-09, + 0.000520362984389067, + 0.99999988079071 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 29 + ] + }, + { + "name": "mixamorig:RightHandPinky2", + "translation": [ + -1.06581407981735e-16, + 0.0359999984502792, + 0.0 + ], + "rotation": [ + -5.5537059807588e-22, + 2.59998172951333e-23, + 0.000846231647301465, + 0.999999642372131 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 30 + ] + }, + { + "name": "mixamorig:RightHandPinky3", + "translation": [ + -5.3290702005634e-16, + 0.0209999978542328, + -2.11638845337703e-10 + ], + "rotation": [ + 1.44227946696892e-21, + -2.71862767183602e-23, + -0.000496777822263539, + 0.99999988079071 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 31 + ] + }, + { + "name": "mixamorig:RightHandPinky4", + "translation": [ + 1.29674042076438e-15, + 0.0211578104645014, + 1.44073614150031e-10 + ], + "rotation": [ + -6.4206595595806e-08, + 0.00156948121730238, + -4.12852823501453e-05, + 0.99999874830246 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:LeftShoulder", + "translation": [ + 0.0457044579088688, + 0.111956439912319, + -0.00806627236306667 + ], + "rotation": [ + 0.48442268371582, + 0.570970475673676, + -0.52616161108017, + 0.403089731931686 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 33 + ] + }, + { + "name": "mixamorig:LeftArm", + "translation": [ + -2.66453519954337e-17, + 0.108377054333687, + 0.0 + ], + "rotation": [ + -0.0246063582599163, + -0.00256139459088445, + 0.103504531085491, + 0.994321286678314 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 34 + ] + }, + { + "name": "mixamorig:LeftForeArm", + "translation": [ + 9.76996256376182e-17, + 0.278415203094482, + -2.84217087951293e-16 + ], + "rotation": [ + -1.3721941358974e-22, + -2.64697796016969e-23, + -5.29395592033938e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 35 + ] + }, + { + "name": "mixamorig:LeftHand", + "translation": [ + 5.43873497349523e-08, + 0.283288329839706, + -5.56424861741789e-09 + ], + "rotation": [ + -1.37219262128447e-22, + -2.64697796016969e-23, + -5.29395592033938e-23, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 36, + 40, + 44, + 48, + 52 + ] + }, + { + "name": "mixamorig:LeftHandThumb1", + "translation": [ + -0.0268173143267632, + 0.02466144785285, + 0.0157619286328554 + ], + "rotation": [ + 0.253096461296082, + -0.0613232329487801, + 0.227352753281593, + 0.938345551490784 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 37 + ] + }, + { + "name": "mixamorig:LeftHandThumb2", + "translation": [ + -5.99520390945937e-17, + 0.0418709442019463, + 2.84217087951293e-16 + ], + "rotation": [ + -0.00206703203730285, + 0.00104273075703532, + -0.00821340829133987, + 0.999963581562042 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 38 + ] + }, + { + "name": "mixamorig:LeftHandThumb3", + "translation": [ + 9.99200679149249e-18, + 0.0341838821768761, + 5.68434175902586e-16 + ], + "rotation": [ + -0.00153135892469436, + 0.000745255209039897, + -0.00607311958447099, + 0.999980092048645 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 39 + ] + }, + { + "name": "mixamorig:LeftHandThumb4", + "translation": [ + 1.0103029153512e-16, + 0.025805814191699, + -2.84217087951293e-16 + ], + "rotation": [ + 0.00515340734273195, + 0.12268278002739, + 0.0416515357792377, + 0.991558134555817 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:LeftHandIndex1", + "translation": [ + -0.0225986279547215, + 0.0910930931568146, + 0.00518034305423498 + ], + "rotation": [ + 6.66575999730412e-07, + -2.93803488482514e-11, + -4.40765179519076e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 41 + ] + }, + { + "name": "mixamorig:LeftHandIndex2", + "translation": [ + -6.21724896437066e-17, + 0.0370000004768372, + -2.84217087951293e-16 + ], + "rotation": [ + 3.40721356906215e-07, + 3.58091958863049e-11, + 0.000105098180938512, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 42 + ] + }, + { + "name": "mixamorig:LeftHandIndex3", + "translation": [ + -7.17648156341438e-15, + 0.028499998152256, + 5.68434175902586e-16 + ], + "rotation": [ + 3.34750552610785e-07, + -2.2176925226769e-11, + -6.62491074763238e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 43 + ] + }, + { + "name": "mixamorig:LeftHandIndex4", + "translation": [ + 8.58868538626285e-15, + 0.0277486555278301, + 2.84217087951293e-16 + ], + "rotation": [ + 1.05946043049698e-08, + -0.000377563264919445, + -1.30695379993995e-05, + 0.999999940395355 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:LeftHandMiddle1", + "translation": [ + -3.66043593658105e-07, + 0.0953340157866478, + -3.25404727163914e-07 + ], + "rotation": [ + 4.08473596280601e-07, + -1.29267213253015e-11, + -3.16464065690525e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 45 + ] + }, + { + "name": "mixamorig:LeftHandMiddle2", + "translation": [ + -1.77635679969558e-17, + 0.0370000004768372, + -2.84217087951293e-16 + ], + "rotation": [ + -1.63583848689086e-07, + -3.46734620182421e-12, + 2.11961414606776e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 46 + ] + }, + { + "name": "mixamorig:LeftHandMiddle3", + "translation": [ + -2.87059262536575e-14, + 0.0295000001788139, + -2.84217087951293e-16 + ], + "rotation": [ + -4.27589924356653e-07, + -8.84662308686668e-12, + 2.06895019800868e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 47 + ] + }, + { + "name": "mixamorig:LeftHandMiddle4", + "translation": [ + -8.7112534254228e-14, + 0.0295286346226931, + -1.42108538681691e-15 + ], + "rotation": [ + -2.67594248981595e-08, + -0.00102414074353874, + 2.79847572528524e-05, + 0.999999463558197 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:LeftHandRing1", + "translation": [ + 0.0186508111655712, + 0.0910452827811241, + 0.000430282350862399 + ], + "rotation": [ + 6.24899598733464e-07, + 3.94516450757942e-12, + 6.31327748124022e-06, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 49 + ] + }, + { + "name": "mixamorig:LeftHandRing2", + "translation": [ + -3.55271359939116e-17, + 0.0315000005066395, + 0.0 + ], + "rotation": [ + -1.97563284358411e-22, + 2.64697796016969e-23, + 5.60519385729927e-45, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 50 + ] + }, + { + "name": "mixamorig:LeftHandRing3", + "translation": [ + 2.64328914312273e-08, + 0.0295000001788139, + 5.20985965479781e-09 + ], + "rotation": [ + -4.78639094945216e-22, + -2.64697796016969e-23, + -1.26116861789234e-44, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 51 + ] + }, + { + "name": "mixamorig:LeftHandRing4", + "translation": [ + -2.3274330374079e-07, + 0.026443162932992, + -1.46075755935726e-08 + ], + "rotation": [ + -2.73169348474767e-07, + 0.000493437342811376, + 1.60470226546749e-05, + 0.99999988079071 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:LeftHandPinky1", + "translation": [ + 0.0380626320838928, + 0.0807777866721153, + 0.00486877001821995 + ], + "rotation": [ + -4.97553810419049e-06, + 1.01780974759436e-08, + 0.00204480229876935, + 0.999997913837433 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 53 + ] + }, + { + "name": "mixamorig:LeftHandPinky2", + "translation": [ + -1.77635679969558e-17, + 0.0359999984502792, + 5.68434175902586e-16 + ], + "rotation": [ + 6.28546672487573e-07, + -1.32409652686682e-10, + -0.000210660000448115, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 54 + ] + }, + { + "name": "mixamorig:LeftHandPinky3", + "translation": [ + 2.41584532699533e-15, + 0.0210000015795231, + 2.84217087951293e-16 + ], + "rotation": [ + 1.05605415967602e-07, + -7.08423424089499e-12, + -6.70821100356989e-05, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 55 + ] + }, + { + "name": "mixamorig:LeftHandPinky4", + "translation": [ + 1.10489391683751e-14, + 0.0212553143501282, + 0.0 + ], + "rotation": [ + 2.20347089907591e-07, + -0.000783398805651814, + -0.00029538557282649, + 0.999999642372131 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:RightUpLeg", + "translation": [ + -0.0820778906345367, + -0.0677176415920258, + -0.0151221863925457 + ], + "rotation": [ + 2.70476707697753e-09, + 0.0103564755991101, + 0.999946355819702, + 2.6115276341443e-07 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 57 + ] + }, + { + "name": "mixamorig:RightLeg", + "translation": [ + 6.1972739440197e-11, + 0.443714618682861, + -1.66200379163089e-15 + ], + "rotation": [ + -0.0380913764238358, + -8.16315237628373e-10, + 2.14148929700286e-08, + 0.999274253845215 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 58 + ] + }, + { + "name": "mixamorig:RightFoot", + "translation": [ + -5.53829308802545e-11, + 0.445278108119965, + 1.77635673352113e-16 + ], + "rotation": [ + 0.459740072488785, + 1.86290701836518e-23, + -1.24328465744838e-07, + 0.8880535364151 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 59 + ] + }, + { + "name": "mixamorig:RightToeBase", + "translation": [ + 0.0, + 0.138168662786484, + 5.32907039908674e-17 + ], + "rotation": [ + 0.33524152636528, + -1.05358534895936e-23, + -1.11300892058352e-07, + 0.942132234573364 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 60 + ] + }, + { + "name": "mixamorig:RightToe_End", + "translation": [ + 0.0, + 0.0927812084555626, + 4.2891038605271e-17 + ], + "rotation": [ + 6.77763359592394e-21, + 0.0116080678999424, + -7.86805323028623e-23, + 0.999932646751404 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "mixamorig:LeftUpLeg", + "translation": [ + 0.0820778831839561, + -0.0677180290222168, + -0.0151221379637718 + ], + "rotation": [ + 2.81914824817875e-09, + 0.0103679597377777, + 0.999946236610413, + 2.71895032710745e-07 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 62 + ] + }, + { + "name": "mixamorig:LeftLeg", + "translation": [ + 6.46826758821106e-11, + 0.443713903427124, + 3.75255368559018e-16 + ], + "rotation": [ + -0.0381124503910542, + -8.17260259466934e-10, + 2.14278141896784e-08, + 0.999273478984833 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 63 + ] + }, + { + "name": "mixamorig:LeftFoot", + "translation": [ + -5.54777890293678e-11, + 0.445278465747833, + -2.22044604925031e-16 + ], + "rotation": [ + 0.459748595952988, + -2.98066612307818e-23, + -1.16685178852549e-07, + 0.888049125671387 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 64 + ] + }, + { + "name": "mixamorig:LeftToeBase", + "translation": [ + 1.77635679969558e-17, + 0.138168647885323, + -2.66453519954337e-17 + ], + "rotation": [ + 0.335241496562958, + -1.75597566048502e-23, + -1.14520787519723e-07, + 0.942132234573364 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "children": [ + 65 + ] + }, + { + "name": "mixamorig:LeftToe_End", + "translation": [ + 0.0, + 0.0927813723683357, + -1.31253512354117e-17 + ], + "rotation": [ + 5.02820374861371e-24, + -0.011869166046381, + 4.23605987251787e-22, + 0.999929547309875 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + }, + { + "name": "Beta_Surface", + "translation": [ + 0.0, + 0.0, + 0.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "mesh": 0, + "skin": 0 + }, + { + "name": "Beta_Joints", + "translation": [ + 0.0, + 0.0, + 0.0 + ], + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "mesh": 1, + "skin": 1 + } + ] +} diff --git a/studio/src/anim/mod.rs b/studio/src/anim/mod.rs new file mode 100644 index 0000000..1f03b7c --- /dev/null +++ b/studio/src/anim/mod.rs @@ -0,0 +1,141 @@ +use bevy::{ + math::Affine3A, + prelude::*, + render::{mesh::skinning::SkinnedMesh, view::NoFrustumCulling}, + utils::{EntityHashMap, EntityHashSet}, +}; +use space_editor::space_prefab::editor_registry::EditorRegistryExt; + +use crate::hierarchy; +pub use retargeting::*; + +mod retargeting; + +pub struct AnimPlugin; +impl Plugin for AnimPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .editor_registry::() + .register_type::() + .editor_registry::() + .register_type::() + .editor_registry::() + .register_type::() + .editor_registry::() + .register_type::() + .editor_registry::() + .register_type::() + .editor_registry::(); + + app.add_systems(Update, process_skinned_meshes); + + app.add_plugins(retargeting::RetargetingPlugin); + } +} + +#[derive(Component, Clone, Copy, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct Skeleton; + +#[derive(Component, Clone, Copy, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct SkeletalJoint(pub Color); + +#[derive(Component, Clone, Copy, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct ProcessedSkinnedMesh; + +#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] +#[reflect(Component)] +pub struct BindPoseLocal(pub Transform); + +#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] +#[reflect(Component)] +pub struct BindPoseGlobal(pub Affine3A); + +#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] +#[reflect(Component)] +pub struct InvBindPose(pub Affine3A); + +fn process_skinned_meshes( + mut cmd: Commands, + q_skinned_meshes: Query<(Entity, &SkinnedMesh), Without>, + mut q_xforms: ParamSet<(TransformHelper, Query<&Transform>)>, + q_parents: Query>, +) { + if q_skinned_meshes.is_empty() { + return; + } + + // Map of skinned-mesh parent entities to the set of joints bound to those + // skinned meshes. We use the skinned-mesh parent because individual skinned + // meshes in glTF format are often only parts of the whole "model," in which + // case the parts will be siblings in the hierarchy. + // + // I.e., considering a hierarchy like this: + // ``` + // └ RootNode + // ├ Body [SkinnedMesh] + // ├ Head [SkinnedMesh] + // ├ Eyes [SkinnedMesh] + // └ Hips [Joint] + // ├ UpperLeg_L [Joint] + // │ └ ... + // ├ UpperLeg_R [Joint] + // │ └ ... + // └ Spine [Joint] + // └ ... + // ``` + // The hashmap should contain an entry like this: + // ``` + // { + // RootNode: { Hips, UpperLeg_L, UpperLeg_R, Spine, ... } + // } + // ``` + // Building this hashmap before processing each joint avoids redundancy in + // the common case where the same joints are bound to multiple skinned meshes + let mut joint_collections = EntityHashMap::>::default(); + for (ent, skinned_mesh) in q_skinned_meshes.iter() { + cmd.entity(ent) + .insert(NoFrustumCulling) + .insert(ProcessedSkinnedMesh); + + let Some(parent) = q_parents.get(ent).ok().flatten() else { + continue; + }; + let parent_ent = **parent; + + let joints = match joint_collections.get_mut(&parent_ent) { + Some(set) => set, + None => { + let (_, set) = + joint_collections.insert_unique_unchecked(parent_ent, Default::default()); + set + } + }; + + joints.extend(skinned_mesh.joints.iter()); + } + + for joints in joint_collections.values() { + // Find the common ancestor of this joint collection and tag it as the + // skeleton + let joints = joints.iter().copied().collect::>(); + if let Some(root) = hierarchy::nearest_common_ancestor(&joints, &q_parents) { + cmd.entity(root).insert(Skeleton); + } + + // Tag each joint and find its bind poses + for &joint in joints.iter() { + let bind_pose_global = q_xforms.p0().compute_global_transform(joint).unwrap(); + let bind_pose_local = *q_xforms.p1().get(joint).unwrap(); + + cmd.entity(joint).insert(( + SkeletalJoint(Color::BLACK), + BindPoseLocal(bind_pose_local), + BindPoseGlobal(bind_pose_global.affine()), + InvBindPose(bind_pose_global.affine().inverse()), + )); + } + } +} diff --git a/studio/src/anim/retargeting.rs b/studio/src/anim/retargeting.rs new file mode 100644 index 0000000..9159908 --- /dev/null +++ b/studio/src/anim/retargeting.rs @@ -0,0 +1,431 @@ +use bevy::{prelude::*, utils::HashMap}; +use space_editor::space_prefab::editor_registry::EditorRegistryExt; + +use crate::math::NearlyEq; + +use super::{BindPoseGlobal, BindPoseLocal, InvBindPose, SkeletalJoint, Skeleton}; + +pub(super) struct RetargetingPlugin; +impl Plugin for RetargetingPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .editor_registry::() + .register_type::(); + + app.insert_resource(RetargetMap::default()); + app.add_systems(Update, copy_pose); + } +} + +#[derive(Component, Clone, Copy, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct RetargetSource; + +#[derive(Component, Clone, Copy, Debug, Default, Reflect, Deref, DerefMut)] +#[reflect(Component)] +pub struct SkeletonHeight(f32); + +fn copy_pose( + mut cmd: Commands, + r_retarget_map: Res, + q_src_root: Query>, + q_tgt_roots: Query, Without)>, + q_parents: Query<&Parent>, + q_children: Query<&Children>, + q_joint_names: Query<&Name, With>, + q_inv_bind_poses: Query<&InvBindPose>, + q_world_bind_poses: Query<&BindPoseGlobal>, + q_local_bind_poses: Query<&BindPoseLocal>, + q_skel_height: Query<&SkeletonHeight>, + mut q_xforms: Query<&mut Transform>, + q_world_xforms: Query<&GlobalTransform>, +) { + let src_root = match q_src_root.get_single() { + Ok(src_root) => src_root, + Err(err) => { + error!("{err}"); + return; + } + }; + + let src_height = match q_skel_height.get(src_root) { + Ok(skel_height) => **skel_height, + Err(_) => { + let Some(value) = compute_skeleton_height(src_root, &q_children, &q_world_bind_poses) + else { + return; + }; + cmd.entity(src_root).insert(SkeletonHeight(value)); + + value + } + }; + + let src_joints = q_children + .iter_descendants(src_root) + .filter_map(|ent| match q_joint_names.get(ent) { + Ok(name) => Some((ent, name)), + Err(_) => None, + }) + .collect::>(); + + // FIXME: + // This is very much not fast, and probably shouldn't be done every frame. + // Tentative plan to optimize this without adding too much additional + // complexity: + // + // 1) Add some sort of "AnimationRecorder" component to the target root + // 2) Beginning with `src_anim.seek_time == 0.0`, record each local + // rotation and translation performed on the target bones as a new + // keyframe on an `AnimationClip` held by `AnimationRecorder` + // 3) When `src_anim.seek_time == src_anim.duration`, finalize the new + // `AnimationClip` and add it to the asset server + // 4) Stop running this system and instead start playing the new animation + // with an `AnimationPlayer` component on the target root + for tgt_root in q_tgt_roots.iter() { + let tgt_height = match q_skel_height.get(tgt_root) { + Ok(skel_height) => **skel_height, + Err(_) => { + let Some(value) = + compute_skeleton_height(tgt_root, &q_children, &q_world_bind_poses) + else { + continue; + }; + cmd.entity(tgt_root).insert(SkeletonHeight(value)); + + value + } + }; + let xlation_scale = tgt_height / src_height; + + for tgt_joint in q_children.iter_descendants(tgt_root) { + let Ok(tgt_name) = q_joint_names.get(tgt_joint) else { + continue; + }; + + // Look up the source bone in the retarget map + // TODO: Snatching O(n) performance from the jaws of an O(1) data + // structure here by using a many-source to single-target + // hashmap, but then looking up the source from the target + // instead of the other way around + let Some(src_joint) = src_joints.iter().copied().find_map(|(joint, src_name)| { + match r_retarget_map.src_to_tgt.get(src_name.as_str()) { + Some(name) if name == tgt_name.as_str() => Some(joint), + _ => None, + } + }) else { + continue; + }; + + // Get the query data needed to compute the target bone's local rotation + let Ok(src_inv_bind_pose) = q_inv_bind_poses.get(src_joint) else { + continue; + }; + let Ok(src_world_xform) = q_world_xforms.get(src_joint) else { + continue; + }; + let Ok(tgt_world_bind_pose) = q_world_bind_poses.get(tgt_joint) else { + continue; + }; + + // The math here was adapted from this writeup: + // https://wickedengine.net/2022/09/23/animation-retargeting/ + // + // ```pseudocode + // // Apply target-local rotation + // tgt_working_xform = src_world_xform * src_inv_bind_pose + // tgt_working_xform = tgt_inv_parent_xform * tgt_working_xform * tgt_world_bind_pose + // tgt_xform.rotation = tgt_working_xform.rotation + // + // // Check if the translation component is animated + // if src_local_xform.translation != src_local_bind_pose.translation { + // // Apply target-local translation + // src_world_xlation = src_inv_bind_pose * src_local_xform.translation + // tgt_local_xlation = tgt_inv_parent_xform * src_world_xlation + // tgt_xform.translation = tgt_local_xlation + // } + // ``` + let mut tgt_working_xform = src_world_xform.affine() * (**src_inv_bind_pose); + + // The parent bone's world transform must be computed each iteration, + // since it will be affected by the rotations applied on the previous + // iteration. + let tgt_parent = q_parents.get(tgt_joint); + let parent_xform = tgt_parent + .and_then(|parent| q_xforms.get(**parent)) + .copied() + .unwrap_or_default(); + + let mut parent_world_xform = GlobalTransform::from(parent_xform); + if let Ok(parent) = tgt_parent { + for ancestor in q_parents.iter_ancestors(**parent) { + let xform = q_xforms.get(ancestor).copied().unwrap_or_default(); + parent_world_xform = xform * parent_world_xform; + } + } + + let tgt_inv_parent_xform = parent_world_xform.affine().inverse(); + tgt_working_xform = tgt_inv_parent_xform * tgt_working_xform * (**tgt_world_bind_pose); + + // Apply the local rotation + if let Ok(mut tgt_xform) = q_xforms.get_mut(tgt_joint) { + tgt_xform.rotation = Quat::from_affine3(&tgt_working_xform); + } + + // Get the query data needed to compute the target bone's local translation + let Ok(src_local_bind_pose) = q_local_bind_poses.get(src_joint) else { + continue; + }; + let Ok(src_world_bind_pose) = q_world_bind_poses.get(src_joint) else { + continue; + }; + let Ok(src_local_xlation) = q_xforms.get(src_joint).map(|xform| xform.translation) + else { + continue; + }; + + // If the source bone translation != its bind pose translation, + // this translation component is animated (likely root motion) + if !src_local_xlation.nearly_eq_within(src_local_bind_pose.translation, 1e-3) { + let src_world_xlation = src_world_bind_pose.transform_vector3(src_local_xlation); + let tgt_local_xlation = tgt_inv_parent_xform.transform_vector3(src_world_xlation); + + // Apply the local translation + if let Ok(mut tgt_xform) = q_xforms.get_mut(tgt_joint) { + tgt_xform.translation = tgt_local_xlation * xlation_scale; + } + } + } + } +} + +fn compute_skeleton_height( + root: Entity, + q_children: &Query<&Children>, + q_world_bind_poses: &Query<&BindPoseGlobal>, +) -> Option { + let joint_positions = q_children + .iter_descendants(root) + .filter_map(|joint| { + q_world_bind_poses + .get(joint) + .map(|bind_pose| bind_pose.translation) + .ok() + }) + .collect::>(); + + if joint_positions.is_empty() { + return None; + } + + let y_max = joint_positions + .iter() + .fold(f32::MIN, |accum, current| f32::max(accum, current.y)); + + let y_min = joint_positions + .iter() + .fold(f32::MAX, |accum, current| f32::min(accum, current.y)); + + Some(y_max - y_min) +} + +#[derive(Resource, Clone, Debug, Reflect)] +#[reflect(Resource)] +pub struct RetargetMap { + pub src_to_tgt: HashMap, +} + +impl RetargetMap { + const MOCAP_NET: [(&str, &str); 80] = [ + // Spine + ("LowerBack", "DEF-spine"), + ("hip", "DEF-spine"), + ("abdomen", "DEF-spine.002"), + ("chest", "DEF-spine.003"), + ("Neck", "DEF-spine.004"), + ("neck", "DEF-spine.004"), + ("Neck1", "DEF-spine.005"), + ("neck1", "DEF-spine.005"), + ("Head", "DEF-spine.006"), + ("head", "DEF-spine.006"), + // Right arm + ("rcollar", "DEF-shoulder.R"), + ("rCollar", "DEF-shoulder.R"), + ("rshoulder", "DEF-upper_arm.R"), + ("rShldr", "DEF-upper_arm.R"), + ("relbow", "DEF-forearm.R"), + ("rForeArm", "DEF-forearm.R"), + ("rhand", "DEF-hand.R"), + ("rHand", "DEF-hand.R"), + // Right fingers + // -- Index + ("metacarpal1.r", "DEF-palm.01.R"), + ("finger2-1.r", "DEF-f_index.01.R"), + ("finger2-2.r", "DEF-f_index.02.R"), + ("finger2-3.r", "DEF-f_index.03.R"), + // -- Middle + ("metacarpal2.r", "DEF-palm.02.R"), + ("finger3-1.r", "DEF-f_middle.01.R"), + ("finger3-2.r", "DEF-f_middle.02.R"), + ("finger3-3.r", "DEF-f_middle.03.R"), + // -- Ring + ("metacarpal3.r", "DEF-palm.03.R"), + ("finger4-1.r", "DEF-f_ring.01.R"), + ("finger4-2.r", "DEF-f_ring.02.R"), + ("finger4-3.r", "DEF-f_ring.03.R"), + // -- Pinky + ("metacarpal4.r", "DEF-palm.04.R"), + ("finger5-1.r", "DEF-f_pinky.01.R"), + ("finger5-2.r", "DEF-f_pinky.02.R"), + ("finger5-3.r", "DEF-f_pinky.03.R"), + // -- Thumb + ("rthumb", "DEF-thumb.01.R"), + ("finger1-2.r", "DEF-thumb.02.R"), + ("finger1-3.r", "DEF-thumb.03.R"), + // Right leg + ("rhip", "DEF-thigh.R"), + ("rThigh", "DEF-thigh.R"), + ("rknee", "DEF-shin.R"), + ("rShin", "DEF-shin.R"), + ("rfoot", "DEF-foot.R"), + ("rFoot", "DEF-foot.R"), + ("RightToeBase", "DEF-toe.R"), + // Left arm + ("lcollar", "DEF-shoulder.L"), + ("lCollar", "DEF-shoulder.L"), + ("lshoulder", "DEF-upper_arm.L"), + ("lShldr", "DEF-upper_arm.L"), + ("lelbow", "DEF-forearm.L"), + ("lForeArm", "DEF-forearm.L"), + ("lhand", "DEF-hand.L"), + ("lHand", "DEF-hand.L"), + // Left fingers + // -- Index + ("metacarpal1.l", "DEF-palm.01.L"), + ("finger2-1.l", "DEF-f_index.01.L"), + ("finger2-2.l", "DEF-f_index.02.L"), + ("finger2-3.l", "DEF-f_index.03.L"), + // -- Middle + ("metacarpal2.l", "DEF-palm.02.L"), + ("finger3-1.l", "DEF-f_middle.01.L"), + ("finger3-2.l", "DEF-f_middle.02.L"), + ("finger3-3.l", "DEF-f_middle.03.L"), + // -- Ring + ("metacarpal3.l", "DEF-palm.03.L"), + ("finger4-1.l", "DEF-f_ring.01.L"), + ("finger4-2.l", "DEF-f_ring.02.L"), + ("finger4-3.l", "DEF-f_ring.03.L"), + // -- Pinky + ("metacarpal4.l", "DEF-palm.04.L"), + ("finger5-1.l", "DEF-f_pinky.01.L"), + ("finger5-2.l", "DEF-f_pinky.02.L"), + ("finger5-3.l", "DEF-f_pinky.03.L"), + // -- Thumb + ("lthumb", "DEF-thumb.01.L"), + ("finger1-2.l", "DEF-thumb.02.L"), + ("finger1-3.l", "DEF-thumb.03.L"), + // Left leg + ("lhip", "DEF-thigh.L"), + ("lThigh", "DEF-thigh.L"), + ("lknee", "DEF-shin.L"), + ("lShin", "DEF-shin.L"), + ("lfoot", "DEF-foot.L"), + ("lFoot", "DEF-foot.L"), + ("LeftToeBase", "DEF-toe.L"), + // Pelvis + ("RHipJoint", "DEF-pelvis.R"), + ("LHipJoint", "DEF-pelvis.L"), + ]; + + const MIXAMO: [(&str, &str); 60] = [ + // Spine + ("mixamorig:Hips", "DEF-spine"), + ("mixamorig:Spine", "DEF-spine.001"), + ("mixamorig:Spine1", "DEF-spine.002"), + ("mixamorig:Spine2", "DEF-spine.003"), + ("mixamorig:Neck", "DEF-spine.004"), + ("mixamorig:Head", "DEF-spine.006"), + // Right arm + ("mixamorig:RightShoulder", "DEF-shoulder.R"), + ("mixamorig:RightArm", "DEF-upper_arm.R"), + ("mixamorig:RightForeArm", "DEF-forearm.R"), + ("mixamorig:RightHand", "DEF-hand.R"), + // Right fingers + // -- index + ("mixamorig:RightHandIndex1", "DEF-palm.01.R"), + ("mixamorig:RightHandIndex2", "DEF-f_index.01.R"), + ("mixamorig:RightHandIndex3", "DEF-f_index.02.R"), + ("mixamorig:RightHandIndex4", "DEF-f_index.03.R"), + // -- middle + ("mixamorig:RightHandMiddle1", "DEF-palm.02.R"), + ("mixamorig:RightHandMiddle2", "DEF-f_middle.01.R"), + ("mixamorig:RightHandMiddle3", "DEF-f_middle.02.R"), + ("mixamorig:RightHandMiddle4", "DEF-f_middle.03.R"), + // -- ring + ("mixamorig:RightHandRing1", "DEF-palm.03.R"), + ("mixamorig:RightHandRing2", "DEF-f_ring.01.R"), + ("mixamorig:RightHandRing3", "DEF-f_ring.02.R"), + ("mixamorig:RightHandRing4", "DEF-f_ring.03.R"), + // -- pinky + ("mixamorig:RightHandPinky1", "DEF-palm.04.R"), + ("mixamorig:RightHandPinky2", "DEF-f_pinky.01.R"), + ("mixamorig:RightHandPinky3", "DEF-f_pinky.02.R"), + ("mixamorig:RightHandPinky4", "DEF-f_pinky.03.R"), + // -- thumb + ("mixamorig:RightHandThumb1", "DEF-thumb.01.R"), + ("mixamorig:RightHandThumb2", "DEF-thumb.02.R"), + ("mixamorig:RightHandThumb3", "DEF-thumb.03.R"), + // Right Leg + ("mixamorig:RightUpLeg", "DEF-thigh.R"), + ("mixamorig:RightLeg", "DEF-shin.R"), + ("mixamorig:RightFoot", "DEF-foot.R"), + ("mixamorig:RightToeBase", "DEF-toe.R"), + // Left arm + ("mixamorig:LeftShoulder", "DEF-shoulder.L"), + ("mixamorig:LeftArm", "DEF-upper_arm.L"), + ("mixamorig:LeftForeArm", "DEF-forearm.L"), + ("mixamorig:LeftHand", "DEF-hand.L"), + // Left fingers + // -- index + ("mixamorig:LeftHandIndex1", "DEF-palm.01.L"), + ("mixamorig:LeftHandIndex2", "DEF-f_index.01.L"), + ("mixamorig:LeftHandIndex3", "DEF-f_index.02.L"), + ("mixamorig:LeftHandIndex4", "DEF-f_index.03.L"), + // -- middle + ("mixamorig:LeftHandMiddle1", "DEF-palm.02.L"), + ("mixamorig:LeftHandMiddle2", "DEF-f_middle.01.L"), + ("mixamorig:LeftHandMiddle3", "DEF-f_middle.02.L"), + ("mixamorig:LeftHandMiddle4", "DEF-f_middle.03.L"), + // -- ring + ("mixamorig:LeftHandRing1", "DEF-palm.03.L"), + ("mixamorig:LeftHandRing2", "DEF-f_ring.01.L"), + ("mixamorig:LeftHandRing3", "DEF-f_ring.02.L"), + ("mixamorig:LeftHandRing4", "DEF-f_ring.03.L"), + // -- pinky + ("mixamorig:LeftHandPinky1", "DEF-palm.04.L"), + ("mixamorig:LeftHandPinky2", "DEF-f_pinky.01.L"), + ("mixamorig:LeftHandPinky3", "DEF-f_pinky.02.L"), + ("mixamorig:LeftHandPinky4", "DEF-f_pinky.03.L"), + // -- thumb + ("mixamorig:LeftHandThumb1", "DEF-thumb.01.L"), + ("mixamorig:LeftHandThumb2", "DEF-thumb.02.L"), + ("mixamorig:LeftHandThumb3", "DEF-thumb.03.L"), + // Left Leg + ("mixamorig:LeftUpLeg", "DEF-thigh.L"), + ("mixamorig:LeftLeg", "DEF-shin.L"), + ("mixamorig:LeftFoot", "DEF-foot.L"), + ("mixamorig:LeftToeBase", "DEF-toe.L"), + ]; +} + +impl Default for RetargetMap { + fn default() -> Self { + Self { + src_to_tgt: Self::MOCAP_NET + .iter() + .chain(Self::MIXAMO.iter()) + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + } + } +} diff --git a/studio/src/bvh/mod.rs b/studio/src/bvh/mod.rs index 3feec75..8c86734 100644 --- a/studio/src/bvh/mod.rs +++ b/studio/src/bvh/mod.rs @@ -5,7 +5,6 @@ use bevy::{ }, prelude::*, transform::TransformSystem, - utils::HashMap, }; use studio_bvh::{frames::Frame, ChannelType}; @@ -13,8 +12,9 @@ mod error; mod loader; use crate::{ - math::{Lerp, MatrixExtras}, - scene::{BindPoseGlobal, InvBindPose, SceneState, SkeletalJoint}, + anim::{BindPoseGlobal, BindPoseLocal, InvBindPose, RetargetSource, SkeletalJoint}, + math::Lerp, + scene::SceneState, }; use self::loader::BvhLoader; @@ -29,21 +29,39 @@ impl Plugin for BvhPlugin { // module's `SceneState`) to manage the order/frequency at // which these systems run instead of just running them all // every frame + .add_systems(Update, spawn_test_bvh.run_if(resource_exists::())) .add_systems( PostUpdate, - ( - compute_inverse_bindposes, - apply_deferred, - animate_bvh, - retarget_bvh, - ) + (compute_inverse_bindposes, apply_deferred, animate_bvh) .chain() .run_if(in_state(SceneState::Active)) .after(TransformSystem::TransformPropagate), ) .register_type::() - .register_type::() - .insert_resource(RetargetMap::default()); + .register_type::(); + } +} + +// TODO: Rename & reorganize +#[derive(Resource, Deref, DerefMut)] +pub struct TestBvh(pub Handle); + +// TODO: Rename & reorganize +fn spawn_test_bvh( + mut cmd: Commands, + mut er_bvh_events: EventReader>, + r_bvh: Res, +) { + for event in er_bvh_events.read() { + if matches!(event, AssetEvent::LoadedWithDependencies { .. }) { + let ent = cmd.spawn_bvh_with_transform( + &r_bvh.0, + Transform::from_scale(Vec3::splat(0.06)) + .with_translation(Vec3::new(0., 1.0, 0.524)), + ); + + cmd.entity(ent).insert(RetargetSource); + } } } @@ -68,13 +86,20 @@ pub struct BvhAnim { timer: Timer, } +// TODO: Rename fn compute_inverse_bindposes( mut cmd: Commands, - q_bvh_joints: Query<(Entity, &GlobalTransform), (With, Without)>, + q_bvh_joints: Query< + (Entity, &Transform, &GlobalTransform), + (With, Without), + >, ) { - for (ent, world_xform) in q_bvh_joints.iter() { - let inv_bind_pose = world_xform.affine().inverse(); - cmd.entity(ent).insert(InvBindPose(inv_bind_pose)); + for (joint, xform, world_xform) in q_bvh_joints.iter() { + cmd.entity(joint).insert(( + BindPoseLocal(*xform), + BindPoseGlobal(world_xform.affine()), + InvBindPose(world_xform.affine().inverse()), + )); } } @@ -179,76 +204,6 @@ fn lerp_frames( } } -fn retarget_bvh( - r_retarget_map: Res, - mut l_target_ents: Local>, - q_source_bones: Query<(&Name, &InvBindPose, &GlobalTransform), With>, - q_target_ents: Query<(Entity, &Name), (With, Without)>, - mut q_target_xforms: Query< - (&Parent, &BindPoseGlobal, &mut Transform), - (With, Without), - >, - q_world_xforms: Query<&GlobalTransform>, -) { - for (source_name, source_inv_bind_pose, source_world_xform) in q_source_bones.iter() { - // Lookup the target name in the retarget map - let Some(target_name) = r_retarget_map.source_to_target.get(source_name.as_str()) else { - continue; - }; - - // Lookup the target entity in the local target bones hashmap, - // or insert it if this is the system's first run - let target_ent = match l_target_ents.get(target_name) { - Some(ent) => Some(*ent), - None => { - if let Some(ent) = q_target_ents.iter().find_map(|(ent, name)| { - if name.as_str() == target_name { - Some(ent) - } else { - None - } - }) { - l_target_ents.insert(target_name.clone(), ent); - Some(ent) - } else { - None - } - } - }; - - // Lookup the transform data for the matching entity - let Some((target_parent_ent, target_bind_pose_global, mut target_xform)) = - target_ent.and_then(|ent| q_target_xforms.get_mut(ent).ok()) - else { - continue; - }; - - // Copy rotations from source -> target - // - // This code is adapted from the writeup here: - // https://wickedengine.net/2022/09/23/animation-retargeting/ - // with the main difference being that the order of operations is - // exactly reversed (DirectX uses row-major matrices, while Bevy uses - // column-major ordering) - - // [psuedo] local_mat = source_inv_bind_mat * source_world_mat; - let mut local_mat = source_world_xform.affine() * (**source_inv_bind_pose); - local_mat.matrix3 = local_mat.matrix3.orthonormalize(); - - // [psuedo] local_mat = target_world_mat * local_mat * target_inv_parent_mat; - let parent_world_xform = match q_world_xforms.get(**target_parent_ent) { - Ok(world_xform) => world_xform, - Err(_) => &GlobalTransform::IDENTITY, - }; - let target_inv_parent_mat = parent_world_xform.affine().inverse(); - local_mat = target_inv_parent_mat * local_mat * (**target_bind_pose_global); - local_mat.matrix3 = local_mat.matrix3.orthonormalize(); - - // [psuedo] target_xform.rotation = local_mat.rotation; - target_xform.rotation = Quat::from_affine3(&local_mat); - } -} - //============================================================================== // Bvh Anim Component //============================================================================== @@ -352,136 +307,3 @@ fn spawn_bvh_recursive(world: &mut World, entity: &Entity, joint: studio_bvh::Jo world.entity_mut(*entity).push_children(&[child_entity]); } } - -//============================================================================== -// RetargetMap -//============================================================================== - -#[derive(Resource, Debug)] -pub struct RetargetMap { - pub source_to_target: HashMap, -} - -#[rustfmt::skip] -static SOURCE_TO_TARGET: [(&str, &str); 80] = [ - // Spine - ("LowerBack", "DEF-spine"), - ("hip", "DEF-spine"), - ("abdomen", "DEF-spine.002"), - ("chest", "DEF-spine.003"), - ("Neck", "DEF-spine.004"), - ("neck", "DEF-spine.004"), - ("Neck1", "DEF-spine.005"), - ("neck1", "DEF-spine.005"), - ("Head", "DEF-spine.006"), - ("head", "DEF-spine.006"), - - // Right arm - ("rcollar", "DEF-shoulder.R"), - ("rCollar", "DEF-shoulder.R"), - ("rshoulder", "DEF-upper_arm.R"), - ("rShldr", "DEF-upper_arm.R"), - ("relbow", "DEF-forearm.R"), - ("rForeArm", "DEF-forearm.R"), - ("rhand", "DEF-hand.R"), - ("rHand", "DEF-hand.R"), - - // Right fingers - // -- Index - ("metacarpal1.r", "DEF-palm.01.R"), - ("finger2-1.r", "DEF-f_index.01.R"), - ("finger2-2.r", "DEF-f_index.02.R"), - ("finger2-3.r", "DEF-f_index.03.R"), - // -- Middle - ("metacarpal2.r", "DEF-palm.02.R"), - ("finger3-1.r", "DEF-f_middle.01.R"), - ("finger3-2.r", "DEF-f_middle.02.R"), - ("finger3-3.r", "DEF-f_middle.03.R"), - // -- Ring - ("metacarpal3.r", "DEF-palm.03.R"), - ("finger4-1.r", "DEF-f_ring.01.R"), - ("finger4-2.r", "DEF-f_ring.02.R"), - ("finger4-3.r", "DEF-f_ring.03.R"), - // -- Pinky - ("metacarpal4.r", "DEF-palm.04.R"), - ("finger5-1.r", "DEF-f_pinky.01.R"), - ("finger5-2.r", "DEF-f_pinky.02.R"), - ("finger5-3.r", "DEF-f_pinky.03.R"), - // -- Thumb - ("rthumb", "DEF-thumb.01.R"), - ("finger1-2.r", "DEF-thumb.02.R"), - ("finger1-3.r", "DEF-thumb.03.R"), - - // Right leg - ("rhip", "DEF-thigh.R"), - ("rThigh", "DEF-thigh.R"), - ("rknee", "DEF-shin.R"), - ("rShin", "DEF-shin.R"), - ("rfoot", "DEF-foot.R"), - ("rFoot", "DEF-foot.R"), - ("RightToeBase", "DEF-toe.R"), - - // Left arm - ("lcollar", "DEF-shoulder.L"), - ("lCollar", "DEF-shoulder.L"), - ("lshoulder", "DEF-upper_arm.L"), - ("lShldr", "DEF-upper_arm.L"), - ("lelbow", "DEF-forearm.L"), - ("lForeArm", "DEF-forearm.L"), - ("lhand", "DEF-hand.L"), - ("lHand", "DEF-hand.L"), - - // Left fingers - // -- Index - ("metacarpal1.l", "DEF-palm.01.L"), - ("finger2-1.l", "DEF-f_index.01.L"), - ("finger2-2.l", "DEF-f_index.02.L"), - ("finger2-3.l", "DEF-f_index.03.L"), - - // -- Middle - ("metacarpal2.l", "DEF-palm.02.L"), - ("finger3-1.l", "DEF-f_middle.01.L"), - ("finger3-2.l", "DEF-f_middle.02.L"), - ("finger3-3.l", "DEF-f_middle.03.L"), - - // -- Ring - ("metacarpal3.l", "DEF-palm.03.L"), - ("finger4-1.l", "DEF-f_ring.01.L"), - ("finger4-2.l", "DEF-f_ring.02.L"), - ("finger4-3.l", "DEF-f_ring.03.L"), - - // -- Pinky - ("metacarpal4.l", "DEF-palm.04.L"), - ("finger5-1.l", "DEF-f_pinky.01.L"), - ("finger5-2.l", "DEF-f_pinky.02.L"), - ("finger5-3.l", "DEF-f_pinky.03.L"), - - // -- Thumb - ("lthumb", "DEF-thumb.01.L"), - ("finger1-2.l", "DEF-thumb.02.L"), - ("finger1-3.l", "DEF-thumb.03.L"), - - // Left leg - ("lhip", "DEF-thigh.L"), - ("lThigh", "DEF-thigh.L"), - ("lknee", "DEF-shin.L"), - ("lShin", "DEF-shin.L"), - ("lfoot", "DEF-foot.L"), - ("lFoot", "DEF-foot.L"), - ("LeftToeBase", "DEF-toe.L"), - - // Pelvis - ("RHipJoint", "DEF-pelvis.R"), - ("LHipJoint", "DEF-pelvis.L"), -]; - -impl Default for RetargetMap { - fn default() -> Self { - Self { - source_to_target: SOURCE_TO_TARGET - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(), - } - } -} diff --git a/studio/src/debug.rs b/studio/src/debug.rs index 14a76f8..117a1bd 100644 --- a/studio/src/debug.rs +++ b/studio/src/debug.rs @@ -13,11 +13,10 @@ use space_editor::{ }; use crate::{ + anim::SkeletalJoint, camera_controller::{CameraController, CameraMotionSystem}, input::{Action, InputAction, Lifecycle}, - is_editor, - scene::SkeletalJoint, - MainCamera, + is_editor, MainCamera, }; #[derive(Resource, Deref, DerefMut)] diff --git a/studio/src/hierarchy.rs b/studio/src/hierarchy.rs new file mode 100644 index 0000000..0131ce6 --- /dev/null +++ b/studio/src/hierarchy.rs @@ -0,0 +1,98 @@ +use bevy::{ + prelude::*, + utils::{EntityHashMap, EntityHashSet}, +}; + +/// Find the parent entity of `target`. Returns `None` if `target` has no +/// parents. +/// +/// - Time: Constant +/// - Space: Constant +pub(crate) fn find_parent(target: Entity, q_parents: &Query>) -> Option { + q_parents + .get(target) + .ok() + .and_then(|parent| parent.map(|p| **p)) +} + +/// Find the nearest common ancestor entity of a set of `entities`. Returns +/// `None` if the provided `entities` do not have a common ancestor. +/// +/// - Time: O(DN) +/// - Space: O(N) +/// +/// where +/// - D = depth of the most deeply-nested member of `entites` +/// - N = the number of `entities` provided +pub(crate) fn nearest_common_ancestor( + entities: &[Entity], + q_parents: &Query>, +) -> Option { + // Find the depth of each entity in the tree + let mut depths: EntityHashMap = EntityHashMap::default(); + for &ent in entities { + depths.insert(ent, find_depth(ent, q_parents)); + } + + // Find the lowest depth of all the entities + let Some(lowest_depth) = depths.values().copied().reduce(usize::min) else { + return None; + }; + + // Traverse up the tree from each entity until we find each one's ancestor + // at the level of lowest_depth + let mut equalized: EntityHashSet = EntityHashSet::default(); + for ent in entities { + let mut depth = depths[ent]; + let mut result = *ent; + + while depth > lowest_depth { + result = find_parent(result, q_parents).unwrap(); + depth -= 1; + } + + equalized.insert(result); + } + + // NOTE: If a member of `entities` should be considered a valid result (i.e. + // if it should count as its own "ancestor"), then we should check the + // length of `equalized` here and return if it only has one member. + // This isn't a use case we need currently, but it could be supported + // by adding a some kind of mode switch to this function's parameters. + + // Walk up the tree from each of those entities until we find the common ancestor + let mut parents: EntityHashSet = EntityHashSet::default(); + loop { + for &ent in equalized.iter() { + if let Some(parent) = find_parent(ent, q_parents) { + parents.insert(parent); + } else { + return None; + } + } + + if parents.len() == 1 { + break parents.iter().copied().next(); + } + + // Reset the loop: + // equalized = parents + // parents = {} + equalized.clear(); + equalized.extend(parents.iter()); + + parents.clear(); + } +} + +fn find_depth(target: Entity, q_parents: &Query>) -> usize { + let mut result = 0; + let mut parent = find_parent(target, q_parents); + + while let Some(p) = parent { + result += 1; + parent = find_parent(p, q_parents); + } + + result +} diff --git a/studio/src/lib.rs b/studio/src/lib.rs index e69b20e..a788753 100644 --- a/studio/src/lib.rs +++ b/studio/src/lib.rs @@ -2,20 +2,22 @@ use bevy::{app::PluginGroupBuilder, prelude::*}; use bevy_mod_picking::DefaultPickingPlugins; - use bevy_web_asset::WebAssetPlugin; -use bvh::BvhPlugin; + #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; +mod anim; mod bvh; mod camera_controller; mod debug; mod design_system; +mod hierarchy; mod input; mod inspector; mod interaction; mod math; +mod mixamo; mod persistance; mod scene; mod skybox; @@ -24,12 +26,16 @@ pub mod viewport; mod world_grid; pub use crate::{ + anim::AnimPlugin, + bvh::BvhPlugin, camera_controller::CameraControllerPlugin, debug::DebugPlugin, input::StudioInputPlugin, inspector::InspectorPlugin, interaction::InteractionPlugin, math::InterpolationPlugin, + mixamo::{MixamoImportEvent, MixamoPlugin}, + persistance::PersistancePlugin, scene::{LoadSceneEvent, ScenePlugin}, skybox::{SkyboxId, SkyboxPlugin}, ui::UiLayerPlugin, @@ -69,8 +75,8 @@ impl PluginGroup for CommonPlugins { .add(StudioInputPlugin) .add(UiLayerPlugin) .add(WorldGridPlugin) - .add(persistance::PersistancePlugin) - .add(BvhPlugin) + .add(PersistancePlugin) + .add(AnimPlugin) } } @@ -149,16 +155,44 @@ pub fn start_bvh_viewer( let bvh_path = bvh_path.to_string(); + app.add_plugins(BvhPlugin); app.add_systems( Startup, move |mut cmd: Commands, r_assets: Res| { - cmd.insert_resource(scene::TestBvh(r_assets.load(bvh_path.clone()))); + cmd.insert_resource(bvh::TestBvh(r_assets.load(bvh_path.clone()))); }, ); app.run(); } +#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startMixamoViewer))] +pub fn start_mixamo_viewer( + mixamo_path: &str, + skybox_id: &str, + canvas_selector: &str, + canvas_alt: &str, + viewport_size: ViewportSize, +) { + let mut app = start_internal( + "Mannequin.gltf", + skybox_id, + canvas_selector, + canvas_alt, + viewport_size, + StudioMode::Viewer, + ); + + let mixamo_path = mixamo_path.to_string(); + + app.add_plugins(MixamoPlugin); + app.add_systems(Startup, move |mut ew: EventWriter| { + ew.send(MixamoImportEvent(mixamo_path.clone())); + }); + + app.run(); +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] pub fn start( object_id: &str, diff --git a/studio/src/mixamo.rs b/studio/src/mixamo.rs new file mode 100644 index 0000000..4534d7f --- /dev/null +++ b/studio/src/mixamo.rs @@ -0,0 +1,137 @@ +use bevy::{gltf::Gltf, prelude::*, scene::InstanceId}; + +use crate::anim::RetargetSource; + +pub struct MixamoPlugin; +impl Plugin for MixamoPlugin { + fn build(&self, app: &mut App) { + app.add_event::(); + app.add_state::(); + + app.add_systems( + Update, + ( + import_mixamo_scene, + tick_mixamo_import.run_if(resource_exists::()), + tick_scene_spawning.run_if(in_state(MixamoState::Spawning)), + start_mixamo_anim.run_if(in_state(MixamoState::Processing)), + ), + ); + app.add_systems(OnEnter(MixamoState::Spawning), spawn_mixamo_scene); + } +} + +// TODO: Simplify this flow based on rewin's scene module refactor +#[derive(States, Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] +pub enum MixamoState { + #[default] + None, + Spawning, + Processing, +} + +#[derive(Resource, Deref)] +pub struct MixamoImport(pub Handle); + +#[derive(Event, Deref)] +pub struct MixamoImportEvent(pub String); + +#[derive(Resource, Deref)] +pub struct MixamoScene(pub InstanceId); + +fn import_mixamo_scene( + mut cmd: Commands, + r_assets: Res, + mut er: EventReader, +) { + if let Some(event) = er.read().last() { + let path = &**event; + cmd.insert_resource(MixamoImport(r_assets.load(path))); + } +} + +fn tick_mixamo_import( + r_mixamo_import: Res, + ra_gltfs: Res>, + mut er_scene_events: EventReader>, + mut st_next: ResMut>, +) { + for event in er_scene_events.read() { + if let AssetEvent::LoadedWithDependencies { id } = event { + let Some(gltf) = ra_gltfs.get(&**r_mixamo_import) else { + return; + }; + + if gltf.default_scene.is_some() && gltf.default_scene.as_ref().unwrap().id() == *id { + st_next.set(MixamoState::Spawning); + break; + } + } + } +} + +fn spawn_mixamo_scene( + mut cmd: Commands, + ra_gltfs: Res>, + r_mixamo_import: Res, + mut r_scene_spawner: ResMut, +) { + let gltf = ra_gltfs.get(&**r_mixamo_import).unwrap(); + let scene = gltf.default_scene.as_ref().unwrap(); + let parent = cmd + .spawn(( + Name::new("Mixamo"), + SpatialBundle { + visibility: Visibility::Hidden, + transform: Transform::from_xyz(0., 0., -1.), + ..default() + }, + )) + .id(); + + let inst = r_scene_spawner.spawn_as_child(scene.clone(), parent); + cmd.insert_resource(MixamoScene(inst)); +} + +fn tick_scene_spawning( + r_scene_spawner: Res, + r_mixamo_scene: Res, + mut st_next: ResMut>, +) { + if r_scene_spawner.instance_is_ready(**r_mixamo_scene) { + st_next.set(MixamoState::Processing); + } +} + +fn start_mixamo_anim( + mut cmd: Commands, + ra_gltfs: Res>, + r_mixamo_import: Res, + r_mixamo_scene: Res, + ra_animations: Res>, + r_scene_spawner: Res, + q_names: Query<&Name>, + mut st_next: ResMut>, +) { + let gltf = ra_gltfs.get(&**r_mixamo_import).unwrap(); + let src_anim_handle = gltf.animations[0].clone(); + let src_anim = ra_animations.get(src_anim_handle.clone()).unwrap(); + + let Some(src_root) = r_scene_spawner + .iter_instance_entities(**r_mixamo_scene) + .find(|ent| match q_names.get(*ent) { + Ok(name) => src_anim.compatible_with(name), + Err(_) => false, + }) + else { + error!("Failed to find compatible entity for animation"); + return; + }; + + let mut src_player = AnimationPlayer::default(); + src_player.play(src_anim_handle.clone()).repeat(); + + cmd.entity(src_root).insert((RetargetSource, src_player)); + + st_next.set(MixamoState::None); +} diff --git a/studio/src/scene.rs b/studio/src/scene.rs index 7754b36..d61213a 100644 --- a/studio/src/scene.rs +++ b/studio/src/scene.rs @@ -6,22 +6,18 @@ use bevy::{ tonemapping::Tonemapping, }, gltf::Gltf, - math::{vec2, Affine3A}, + math::vec2, pbr::ShadowFilteringMethod, prelude::*, - render::{mesh::skinning::SkinnedMesh, primitives::Aabb, view::NoFrustumCulling}, + render::primitives::Aabb, scene::InstanceId, }; -use space_editor::{ - prelude::PrefabMarker, - space_prefab::{component::SceneAutoChild, editor_registry::EditorRegistryExt}, -}; +use space_editor::{prelude::PrefabMarker, space_prefab::component::SceneAutoChild}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::wasm_bindgen; use crate::{ - bvh::{Bvh, CommandsSpawnBvhExt}, camera_controller::{CameraController, CameraControllerBundle}, debug::DebugPoint, math::{Combinable, Extrema}, @@ -40,14 +36,7 @@ impl Plugin for ScenePlugin { .add_state::() .add_event::() .add_systems(Startup, initialize) - .add_systems( - Update, - ( - spawn_test_bvh.run_if(resource_exists::()), // TODO - init_scene_loading, - ) - .in_set(SceneSpawnerSystem), - ) + .add_systems(Update, init_scene_loading.in_set(SceneSpawnerSystem)) .add_systems( OnEnter(SceneState::Active), (tag_scene_elements, frame_scene_in_view).in_set(SceneSpawnerSystem), @@ -71,10 +60,6 @@ impl Plugin for ScenePlugin { //setup default light app.add_systems(Startup, minimal_scene_setup); - app.editor_registry::(); - app.editor_registry::(); - app.editor_registry::(); - #[cfg(feature = "wasm")] app.add_systems( Update, @@ -190,25 +175,8 @@ pub struct SceneData { pub scene_path: String, } -#[derive(Component)] -pub struct SkeletalJoint(pub Color); - #[derive(Component)] pub struct SceneRoot; -#[derive(Resource, Deref, DerefMut)] -pub struct TestBvh(pub Handle); // TODO - -#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] -#[reflect(Component)] -pub struct BindPoseLocal(pub Transform); - -#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] -#[reflect(Component)] -pub struct BindPoseGlobal(pub Affine3A); - -#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] -#[reflect(Component)] -pub struct InvBindPose(pub Affine3A); fn initialize(mut cmd: Commands) { // Camera, graphics settings @@ -242,23 +210,6 @@ fn initialize(mut cmd: Commands) { )); } -// TODO -fn spawn_test_bvh( - mut commands: Commands, - mut er_bvh_events: EventReader>, - r_bvh: Res, -) { - for event in er_bvh_events.read() { - if matches!(event, AssetEvent::LoadedWithDependencies { .. }) { - commands.spawn_bvh_with_transform( - &r_bvh.0, - Transform::from_scale(Vec3::splat(0.06)) - .with_translation(Vec3::new(0., 1.0, 0.524)), - ); - } - } -} - fn init_scene_loading(mut cmd: Commands, mut er: EventReader) { if let Some(id) = er.read().last() { info!("Loading scene: {}", id.0); @@ -299,8 +250,6 @@ fn tag_scene_elements( Without, ), >, - q_skinned_meshes: Query<(Entity, &SkinnedMesh), Without>, - mut q_xforms: ParamSet<(TransformHelper, Query<&Transform>)>, mesh_entities: Query>>, ) { for (e, children) in q_named_non_primitives.iter() { @@ -317,24 +266,6 @@ fn tag_scene_elements( cmd.entity(e).insert(NoMeshElement); } } - - for (ent, skinned_mesh) in q_skinned_meshes.iter() { - cmd.entity(ent) - .insert(NoFrustumCulling) - .insert(InitedKointSkinnedMesh); - - for &joint in skinned_mesh.joints.iter() { - let global_bind_pose = q_xforms.p0().compute_global_transform(joint).unwrap(); - let local_bind_pose = *q_xforms.p1().get(joint).unwrap(); - - cmd.entity(joint).insert(( - SkeletalJoint(Color::BLACK), - BindPoseLocal(local_bind_pose), - BindPoseGlobal(global_bind_pose.affine()), - InvBindPose(global_bind_pose.affine().inverse()), - )); - } - } } fn frame_scene_in_view( diff --git a/tsconfig.base.json b/tsconfig.base.json index ab92679..34d4b4f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -21,6 +21,7 @@ "@storyteller/studio-web/canvas-provider": ["studio-web/src/lib/canvas-provider.element.ts"], "@storyteller/studio-web/viewer": ["studio-web/src/lib/viewer.element.ts"], "@storyteller/studio-web/bvh-viewer": ["studio-web/src/lib/bvh-viewer.element.ts"], + "@storyteller/studio-web/mixamo-viewer": ["studio-web/src/lib/mixamo-viewer.element.ts"], "@storyteller/utility": ["utility/src/index.ts"] } },