Skip to content

Commit

Permalink
Removed the skeleton property from the .animationset resource. (#…
Browse files Browse the repository at this point in the history
…7990)

* Remap bones to index at runtime

* test fix

* Removed skeleton from the animationset

* Updated tests

* Fixed ModelUtil rename of root animation track to "root"

* Fix for editor tests

* test fixes

* Fixed failing editor save test

---------

Co-authored-by: Mats Gisselson <mats.gisselson@gmail.com>
  • Loading branch information
JCash and matgis committed Sep 20, 2023
1 parent 982a098 commit 3509aa9
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 215 deletions.
Expand Up @@ -412,8 +412,8 @@ public void testObjectAnimations() throws Exception {
@Test
public void testSkeleton() throws Exception {

String[] boneIds = {"Bone", "Bone_001", "Bone_002", "Bone_003", "Bone_004"};
String[] parentIds = {null, "Bone", "Bone", "Bone", "Bone"};
String[] boneIds = {"root", "Bone_001", "Bone_002", "Bone_003", "Bone_004"};
String[] parentIds = {null, "root", "root", "root", "root"};

Rig.Skeleton.Builder skeleton = Rig.Skeleton.newBuilder();
ColladaUtil.loadSkeleton(load("bone_influences.dae"), skeleton, new ArrayList<String>());
Expand Down Expand Up @@ -808,29 +808,6 @@ public void testBlenderRotation() throws Exception {
}
}

/*
* Test that MeshSets and AnimationSets can have bones specified in different order.
*/
@Test
public void testBoneList() throws Exception {
Rig.MeshSet.Builder meshSetBuilder = Rig.MeshSet.newBuilder();
Rig.AnimationSet.Builder animSetBuilder = Rig.AnimationSet.newBuilder();
ColladaUtil.loadMesh(load("bonelist_mesh_test.dae"), meshSetBuilder);
ColladaUtil.loadAnimations(load("bonelist_anim_test.dae"), animSetBuilder, "", new ArrayList<String>());

int meshBoneListCount = meshSetBuilder.getBoneListCount();
int animBoneListCount = animSetBuilder.getBoneListCount();

assertEquals(3, meshBoneListCount);
assertEquals(3, animBoneListCount);

for (int i = 0; i < meshBoneListCount; i++) {
Long meshBone = meshSetBuilder.getBoneList(i);
Long animBone = animSetBuilder.getBoneList(i);
assertEquals(meshBone, animBone);
}
}

/*
* Test collada file with a bone animation that includes both translation and rotation.
*/
Expand Down
Expand Up @@ -3,10 +3,10 @@
// Copyright 2009-2014 Ragnar Svensson, Christian Murray
// Licensed under the Defold License version 1.0 (the "License"); you may not use
// this file except in compliance with the License.
//
//
// You may obtain a copy of the License, together with FAQs at
// https://www.defold.com/license
//
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
Expand Down Expand Up @@ -235,8 +235,7 @@ private ModelImporter.Scene loadBuiltScene(String path,
ModelUtil.loadSkeleton(scene, skeletonBuilder);

ArrayList<String> animationIds = new ArrayList<>();
ArrayList<ModelImporter.Bone> bones = ModelUtil.loadSkeleton(scene);
ModelUtil.loadAnimations(scene, bones, animSetBuilder, "top_anim", animationIds);
ModelUtil.loadAnimations(scene, animSetBuilder, "top_anim", animationIds);
}
return scene;
}
Expand Down Expand Up @@ -333,8 +332,8 @@ public void testBoneInfluences() throws Exception {
@Test
public void testSkeleton() throws Exception {

String[] boneIds = {"Bottom", "Middle", "Top"};
String[] parentIds = {null, "Bottom", "Middle"};
String[] boneIds = {"root", "Middle", "Top"};
String[] parentIds = {null, "root", "Middle"};

Rig.Skeleton.Builder skeletonBuilder = Rig.Skeleton.newBuilder();
ModelImporter.Scene scene = loadBuiltScene("bend2bones.gltf", skeletonBuilder);
Expand Down
Expand Up @@ -57,11 +57,6 @@ public static void collectAnimations(Task.TaskBuilder<Void> taskBuilder, Project
collectAnimations(taskBuilder, project, owner, subAnimSetDescBuilder);
}
}

if(!animSetDescBuilder.getSkeleton().isEmpty()) {
IResource skeleton = BuilderUtil.checkResource(project, owner, "skeleton", animSetDescBuilder.getSkeleton());
taskBuilder.addInput(skeleton);
}
}


Expand Down Expand Up @@ -92,7 +87,7 @@ private void validateAndAddFile(Task<Void> task, String path, ArrayList<String>
}

private void buildAnimations(Task<Void> task, ModelImporter.DataResolver dataResolver, AnimationSetDesc.Builder animSetDescBuilder, AnimationSet.Builder animationSetBuilder,
String parentId, ArrayList<ModelImporter.Bone> bones, ArrayList<String> animFiles) throws CompileExceptionError, IOException {
String parentId, ArrayList<String> animFiles) throws CompileExceptionError, IOException {
ArrayList<String> idList = new ArrayList<>(animSetDescBuilder.getAnimationsCount());

for(AnimationInstanceDesc instance : animSetDescBuilder.getAnimationsList()) {
Expand All @@ -103,7 +98,7 @@ private void buildAnimations(Task<Void> task, ModelImporter.DataResolver dataRes
InputStreamReader subAnimSetDescBuilderISR = new InputStreamReader(animFileIS);
AnimationSetDesc.Builder subAnimSetDescBuilder = AnimationSetDesc.newBuilder();
TextFormat.merge(subAnimSetDescBuilderISR, subAnimSetDescBuilder);
buildAnimations(task, dataResolver, subAnimSetDescBuilder, animationSetBuilder, FilenameUtils.getBaseName(animFile.getPath()), bones, animFiles);
buildAnimations(task, dataResolver, subAnimSetDescBuilder, animationSetBuilder, FilenameUtils.getBaseName(animFile.getPath()), animFiles);
continue;
}
IResource animFile = BuilderUtil.checkResource(this.project, task.input(0), "animation", instance.getAnimation());
Expand All @@ -126,7 +121,7 @@ private void buildAnimations(Task<Void> task, ModelImporter.DataResolver dataRes
if (isCollada)
loadColladaAnimations(animBuilder, animFileIS, animId, parentId);
else
loadModelAnimations(bones, animBuilder, animFileIS, dataResolver, animId, parentId, animFile.getPath(), animationIds);
loadModelAnimations(animBuilder, animFileIS, dataResolver, animId, parentId, animFile.getPath(), animationIds);

} catch (XMLStreamException e) {
throw new CompileExceptionError(animFile, e.getLocation().getLineNumber(), "Failed to load animation: " + e.getLocalizedMessage(), e);
Expand Down Expand Up @@ -169,7 +164,7 @@ static void loadColladaAnimations(AnimationSet.Builder animationSetBuilder, Inpu
animationSetBuilder.addAllAnimations(animBuilder.getAnimationsList());
}

static void loadModelAnimations(ArrayList<ModelImporter.Bone> bones, AnimationSet.Builder animationSetBuilder,
static void loadModelAnimations(AnimationSet.Builder animationSetBuilder,
InputStream is, ModelImporter.DataResolver dataResolver, String animId, String parentId,
String path, ArrayList<String> animationIds) throws IOException {

Expand All @@ -180,7 +175,7 @@ static void loadModelAnimations(ArrayList<ModelImporter.Bone> bones, AnimationSe

// Currently, by design choice, each file must only contain one animation.
// Our current approach is to choose the longest animation (to eliminate target poses etc)
ModelUtil.loadAnimations(scene, bones, animBuilder, animId, localAnimationIds);
ModelUtil.loadAnimations(scene, animBuilder, animId, localAnimationIds);

animationSetBuilder.addAllAnimations(animBuilder.getAnimationsList());

Expand Down Expand Up @@ -213,7 +208,7 @@ public byte[] getData(String path, String uri) {
};

// For the editor
static public void buildAnimations(List<String> paths, ArrayList<ModelImporter.Bone> bones, List<InputStream> streams, ModelImporter.DataResolver dataResolver, List<String> parentIds,
static public void buildAnimations(List<String> paths, List<InputStream> streams, ModelImporter.DataResolver dataResolver, List<String> parentIds,
AnimationSet.Builder animationSetBuilder, ArrayList<String> animationIds) throws IOException, CompileExceptionError {


Expand Down Expand Up @@ -241,31 +236,17 @@ static public void buildAnimations(List<String> paths, ArrayList<ModelImporter.B
if (isCollada)
loadColladaAnimations(animationSetBuilder, stream, animId, parentId);
else
loadModelAnimations(bones, animationSetBuilder, stream, dataResolver, animId, parentId, path, animationIds);
loadModelAnimations(animationSetBuilder, stream, dataResolver, animId, parentId, path, animationIds);

} catch (XMLStreamException e) {
throw new CompileExceptionError(String.format("File %s:%d: Failed to load animation: %s", path, e.getLocation().getLineNumber(), e.getLocalizedMessage()), e);
} catch (LoaderException e) {
throw new CompileExceptionError(String.format("File %s:%d: Failed to load animation: %s", path, -1, e.getLocalizedMessage()), e);
}

}
ModelUtil.setBoneList(animationSetBuilder, bones);
}
// END EDITOR SPECIFIC FUNCTIONS

public ArrayList<ModelImporter.Bone> buildSkeleton(IResource skeletonFile, ModelImporter.DataResolver dataResolver) throws IOException {
String suffix = BuilderUtil.getSuffix(skeletonFile.getPath());
if (suffix.equals("dae")) {
return ColladaUtil.loadSkeleton(skeletonFile.getContent()); // Until our model importer supports collada
}

ModelImporter.Scene skeletonScene = ModelUtil.loadScene(skeletonFile.getContent(), skeletonFile.getPath(), new ModelImporter.Options(), dataResolver);
ArrayList<ModelImporter.Bone> bones = ModelUtil.loadSkeleton(skeletonScene);

return bones;
}

@Override
public void build(Task<Void> task) throws CompileExceptionError, IOException {

Expand All @@ -274,14 +255,7 @@ public void build(Task<Void> task) throws CompileExceptionError, IOException {
AnimationSetDesc.Builder animSetDescBuilder = AnimationSetDesc.newBuilder();
TextFormat.merge(animSetDescISR, animSetDescBuilder);

IResource skeletonFile = BuilderUtil.checkResource(this.project, task.input(0), "skeleton", animSetDescBuilder.getSkeleton());

ResourceDataResolver dataResolver = new ResourceDataResolver(this.project);
ArrayList<ModelImporter.Bone> bones = buildSkeleton(skeletonFile, dataResolver);

if (bones.size() == 0) {
throw new CompileExceptionError(skeletonFile, -1, "No skeleton found in file!");
}

// evaluate hierarchy
AnimationSet.Builder animationSetBuilder = AnimationSet.newBuilder();
Expand All @@ -290,9 +264,7 @@ public void build(Task<Void> task) throws CompileExceptionError, IOException {
animFiles = new ArrayList<String>();
animFiles.add(task.input(0).getAbsPath());

buildAnimations(task, dataResolver, animSetDescBuilder, animationSetBuilder, "", bones, animFiles);

ModelUtil.setBoneList(animationSetBuilder, bones);
buildAnimations(task, dataResolver, animSetDescBuilder, animationSetBuilder, "", animFiles);

// write merged animationset
ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024);
Expand Down
Expand Up @@ -3,10 +3,10 @@
// Copyright 2009-2014 Ragnar Svensson, Christian Murray
// Licensed under the Defold License version 1.0 (the "License"); you may not use
// this file except in compliance with the License.
//
//
// You may obtain a copy of the License, together with FAQs at
// https://www.defold.com/license
//
//
// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
Expand Down Expand Up @@ -332,6 +332,7 @@ private static void sampleScaleTrack(Rig.RigAnimation.Builder animBuilder, Rig.A
}

public static void createAnimationTracks(Rig.RigAnimation.Builder animBuilder,
String boneName,
Bone bone,
RigUtil.AnimationTrack posTrack,
RigUtil.AnimationTrack rotTrack,
Expand All @@ -340,7 +341,7 @@ public static void createAnimationTracks(Rig.RigAnimation.Builder animBuilder,
double spf = 1.0 / sampleRate;

Rig.AnimationTrack.Builder animTrackBuilder = Rig.AnimationTrack.newBuilder();
animTrackBuilder.setBoneId(MurmurHash.hash64(bone.getSourceId()));
animTrackBuilder.setBoneId(MurmurHash.hash64(boneName));

samplePosTrack(animBuilder, animTrackBuilder, posTrack, duration, startTime, sampleRate, spf, true);
sampleRotTrack(animBuilder, animTrackBuilder, rotTrack, duration, startTime, sampleRate, spf, true);
Expand Down Expand Up @@ -422,6 +423,12 @@ private static void boneAnimToDDF(XMLCOLLADA collada, Rig.RigAnimation.Builder a
if (refIndex == null)
continue;

String boneName = bone.getSourceId();
if (bi == 0 || refIndex == 0)
{
boneName = "root";
}

Matrix4d localToParent = getBoneLocalToParent(bone);
AssetSpace assetSpace = getAssetSpace(collada.asset);
if (bi != 0) {
Expand Down Expand Up @@ -449,7 +456,7 @@ private static void boneAnimToDDF(XMLCOLLADA collada, Rig.RigAnimation.Builder a

ExtractKeys(bone, localToParent, assetSpace, animation, posTrack, rotTrack, sclTrack);

createAnimationTracks(animBuilder, bone, posTrack, rotTrack, sclTrack, refIndex, (float)duration, sceneStartTime, sceneFrameRate);
createAnimationTracks(animBuilder, boneName, bone, posTrack, rotTrack, sclTrack, refIndex, (float)duration, sceneStartTime, sceneFrameRate);

break; // we only support one animation per file/bone
}
Expand Down Expand Up @@ -529,8 +536,6 @@ public static void loadAnimations(XMLCOLLADA collada, Rig.AnimationSet.Builder a
Long boneRef = MurmurHash.hash64(boneId);
boneRefMap.put(boneRef, boneIndex);
++boneIndex;

animationSetBuilder.addBoneList(boneRef);
}

// Animation clips
Expand All @@ -555,7 +560,7 @@ public static void loadAnimations(XMLCOLLADA collada, Rig.AnimationSet.Builder a
}

String boneTarget = animation.getTargetBone();
if (!boneToAnimations.containsKey(animation.getTargetBone())) {
if (!boneToAnimations.containsKey(boneTarget)) {
boneToAnimations.put(boneTarget, new ArrayList<XMLAnimation>());
}
boneToAnimations.get(boneTarget).add(animation);
Expand Down Expand Up @@ -984,10 +989,10 @@ public final int hashCode() { // fnv 32 bit hash
meshSetBuilder.addModels(modelBuilder);
meshSetBuilder.setMaxBoneCount(max_bone_count);

List<String> boneRefArray = createBoneReferenceList(collada);
if (boneRefArray != null && !boneRefArray.isEmpty()) {
for (int i = 0; i < boneRefArray.size(); i++) {
meshSetBuilder.addBoneList(MurmurHash.hash64(boneRefArray.get(i)));
ArrayList<ModelImporter.Bone> bones = loadSkeleton(collada);
if (bones != null) {
for (ModelImporter.Bone bone : bones) {
meshSetBuilder.addBoneList(MurmurHash.hash64(bone.name));
}
}
}
Expand Down Expand Up @@ -1286,14 +1291,20 @@ private static boolean validateMatrix4d(Matrix4d m) {
private static void toDDF(ArrayList<com.dynamo.rig.proto.Rig.Bone> ddfBones, Bone bone, int parentIndex, Matrix4d parentWorldTransform) {
com.dynamo.rig.proto.Rig.Bone.Builder b = com.dynamo.rig.proto.Rig.Bone.newBuilder();

b.setName(bone.getName());
// We'd like to do it outside, but since the Bone instances are read-only, we cannot do it.
if (parentIndex == BONE_NO_PARENT) {
b.setName("root");
b.setId(MurmurHash.hash64("root"));
} else {
b.setName(bone.getName());
b.setId(MurmurHash.hash64(bone.getSourceId()));
}

b.setParent(parentIndex);
b.setId(MurmurHash.hash64(bone.getSourceId()));

Matrix4d localMatrix = getBoneLocalToParent(bone);
if (!validateMatrix4d(localMatrix)) {
logger.severe(String.format("Found invalid local matrix in bone '%s', replacing with identity matrix", bone.getName()));
System.out.printf("'%s' local matrix:\n", bone.getName());
printMatrix4d(localMatrix);
localMatrix.setIdentity();
}
Expand All @@ -1303,7 +1314,6 @@ private static void toDDF(ArrayList<com.dynamo.rig.proto.Rig.Bone> ddfBones, Bon

if (!validateMatrix4d(worldMatrix)) {
logger.severe(String.format("Found invalid world matrix in bone '%s', replacing with identity matrix", bone.getName()));
System.out.printf("'%s' world matrix:\n", bone.getName());
printMatrix4d(worldMatrix);
worldMatrix.setIdentity();
}
Expand Down Expand Up @@ -1458,6 +1468,27 @@ public static XMLCOLLADA loadScene(InputStream is) throws IOException, XMLStream
return loadDAE(is);
}

private static void flattenBoneTree(Bone colladaBone, ModelImporter.Bone parent, ArrayList<ModelImporter.Bone> out) {
String originalName = colladaBone.getSourceId();
String newName = originalName;
if (parent == null)
newName = "root";

ModelImporter.Bone bone = new ModelImporter.Bone();
bone.name = newName;
bone.parent = parent;
bone.node = null;
bone.index = out.size();
bone.invBindPose = null;

out.add(bone);

for (int i = 0 ; i < colladaBone.numChildren(); ++i) {
Bone child = colladaBone.getChild(i);
flattenBoneTree(child, bone, out);
}
}

public static ArrayList<ModelImporter.Bone> loadSkeleton(XMLCOLLADA scene) throws IOException, XMLStreamException, LoaderException {
ArrayList<String> boneIds = new ArrayList<>();
ArrayList<Bone> colladaBones = loadSkeleton(scene, boneIds);
Expand All @@ -1466,17 +1497,7 @@ public static ArrayList<ModelImporter.Bone> loadSkeleton(XMLCOLLADA scene) throw
return null;

ArrayList<ModelImporter.Bone> bones = new ArrayList<>();
for (Bone colladaBone : colladaBones)
{
ModelImporter.Bone bone = new ModelImporter.Bone();
bone.name = colladaBone.getSourceId();
bone.parent = null; // Do we need it in the editor?
bone.node = null;
bone.index = bones.size();
bone.invBindPose = null;

bones.add(bone);
}
flattenBoneTree(colladaBones.get(0), null, bones);
return bones;
}

Expand Down
Expand Up @@ -187,11 +187,9 @@ public void build(Task<Void> task) throws CompileExceptionError, IOException {

// Animationset
{
ArrayList<ModelImporter.Bone> skeleton = ModelUtil.loadSkeleton(scene);

AnimationSet.Builder animationSetBuilder = AnimationSet.newBuilder();
if (skeleton.size() > 0) {
ModelUtil.loadAnimations(scene, skeleton, animationSetBuilder, FilenameUtils.getBaseName(task.input(0).getPath()), new ArrayList<String>());
if (ModelUtil.getNumAnimations(scene) > 0) {
ModelUtil.loadAnimations(scene, animationSetBuilder, FilenameUtils.getBaseName(task.input(0).getPath()), new ArrayList<String>());
}

ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024);
Expand Down

0 comments on commit 3509aa9

Please sign in to comment.