예제 #1
0
        public BoneExporter(int resource_index, ZMD zmd)
        {
            sbone = new StringBuilder();
            sbone.Append("; skelton node - mesh nodes parent\n");
            sbone.AppendLine("[node name=\"Armature\" type=\"Skeleton\" parent=\".:\"]");
            sbone.AppendLine("bones_in_world_transform = true");
            sbone.AppendLine("transform = Transform(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)");

            int idx = 0;

            GodotTransform IdentityTransform = new GodotTransform(GodotQuat.Identity, translator.ToVector3(Vector3.ZERO));

            foreach (RoseBone bone in zmd.Bone)
            {
                GodotTransform transform = new GodotTransform(translator.ToQuat(bone.Rotation), translator.ToVector3(bone.Position));
                GodotTransform rest      = new GodotTransform(translator.ToQuat(bone.TransformMatrix.ExtractQuaternion()), translator.ToVector3(bone.TransformMatrix.Translation));
                bone.Rotation.ToAngleAxis(out Radian radian, out Vector3 axis);

                sbone.AppendFormat("bones/{0}/name = \"{1}\"\n", idx, bone.Name);
                sbone.AppendFormat("bones/{0}/parent = {1}\n", idx, idx == 0 ? -1 : bone.ParentID);
                //sbone.AppendFormat("; Position: {0}\n; Rotation: {1}\n; Angle(rad): {2:G4} Angle(deg): {3:G4} Axis: {4}\n", bone.Position, bone.Rotation, radian.ValueRadians, radian.ValueDegrees, axis);
                sbone.AppendFormat("bones/{0}/rest = {1}\n", idx, translator.Transform2String(transform));
                sbone.AppendFormat("bones/{0}/pose = {1}\n", idx, translator.Transform2String(rest));
                //sbone.AppendFormat("bones/{0}/pose = {1}\n", idx, "Transform(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)");
                sbone.AppendFormat("bones/{0}/enabled = true\n", idx);
                sbone.AppendFormat("bones/{0}/bound_children = [ ]\n", idx);
                idx++;
            }

            if (zmd.DummiesCount > 0)
            {
                sbone.Append("; dummy bones\n");
                foreach (RoseBone dummy in zmd.Dummy)
                {
                    GodotTransform transform = new GodotTransform(translator.ToQuat(dummy.Rotation), translator.ToVector3(dummy.Position));

                    sbone.AppendFormat("bones/{0}/name = \"{1}\"\n", idx, dummy.Name);
                    sbone.AppendFormat("bones/{0}/parent = {1}\n", idx, idx == 0 ? -1 : dummy.ParentID);
                    //sbone.AppendFormat("; Position: {0}\n; Rotation: {1}\n", dummy.Position, dummy.Rotation);
                    sbone.AppendFormat("bones/{0}/rest = {1}\n", idx, translator.Transform2String(transform));
                    //sbone.AppendFormat("bones/{0}/pose = {1}\n", idx, translator.Transform2String(IdentityTransform));
                    sbone.AppendFormat("bones/{0}/enabled = true\n", idx);
                    sbone.AppendFormat("bones/{0}/bound_children = []\n", idx);
                    idx++;
                }
            }

            LastResourceIndex += resource_index;
        }
예제 #2
0
    public static RoseSkeletonData ImportSkeleton(string path)
    {
        var fullPath = Utils.CombinePath(dataPath, path);

        if (!File.Exists(fullPath))
        {
            Debug.LogWarning("Could not find referenced skeleton: " + fullPath);
            return(null);
        }

        var skelPath = GenerateAssetPath(path, ".skel.asset");

        if (!File.Exists(skelPath))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(skelPath));

            var zmd  = new ZMD(fullPath);
            var skel = ScriptableObject.CreateInstance <RoseSkeletonData>();

            for (var i = 0; i < zmd.bones.Count; ++i)
            {
                var zmdBone = zmd.bones[i];
                var bone    = new RoseSkeletonData.Bone();
                bone.name        = zmdBone.Name;
                bone.parent      = zmdBone.ParentID;
                bone.translation = zmdBone.Position;
                bone.rotation    = zmdBone.Rotation;
                skel.bones.Add(bone);
            }

            for (var i = 0; i < zmd.dummies.Count; ++i)
            {
                var zmdBone = zmd.dummies[i];
                var bone    = new RoseSkeletonData.Bone();
                bone.name        = zmdBone.Name;
                bone.parent      = zmdBone.ParentID;
                bone.translation = zmdBone.Position;
                bone.rotation    = zmdBone.Rotation;
                skel.dummies.Add(bone);
            }

            AssetDatabase.CreateAsset(skel, skelPath);
            EditorUtility.SetDirty(skel);
            AssetDatabase.SaveAssets();
        }
        return(AssetDatabase.LoadAssetAtPath <RoseSkeletonData>(skelPath));
    }
예제 #3
0
        /// <summary>
        /// Loads all animations for equiped weapon type. Used only in editor to generate prefabs
        /// </summary>
        /// <param name="WeaponType"></param>
        public void LoadAnimations(GameObject player, ZMD skeleton, WeaponType weapon, GenderType gender)
        {
            List <AnimationClip> clips = new List <AnimationClip>();

            foreach (ActionType action in Enum.GetValues(typeof(ActionType)))
            {
                // Attempt to find animation asset, and if not found, load from ZMO
                string        zmoPath = Utils.FixPath(ResourceManager.Instance.GetZMOPath(weapon, action, gender));
                AnimationClip clip    = R2U.GetClip(zmoPath, skeleton, action.ToString());
                clip.legacy = true;
                clips.Add(clip);
            }

            Animation animation = player.GetComponent <Animation>();

            AnimationUtility.SetAnimationClips(animation, clips.ToArray());
        }
예제 #4
0
        public void LoadClips(GameObject skeleton, ZMD zmd, GenderType gender, RigType rig, Dictionary <String, String> zmoPaths)
        {
            List <AnimationClip> clips = new List <AnimationClip>();

            foreach (KeyValuePair <String, String> motion in zmoPaths)
            {
                string unityPath = "Assets/Resources/Animation/" + gender.ToString() + "/" + rig.ToString() + "/clips/" + motion.Key + ".anim";

                AnimationClip clip = new ZMO("Assets/" + motion.Value).buildAnimationClip(zmd);
                clip.name   = motion.Key;
                clip.legacy = true;
                clip        = (AnimationClip)Utils.SaveReloadAsset(clip, unityPath, ".anim");
                clips.Add(clip);
            }

            Animation animation = skeleton.AddComponent <Animation>();

            AnimationUtility.SetAnimationClips(animation, clips.ToArray());
        }
예제 #5
0
        /// <summary>
        /// Loads all animations for given weapon type and gender. The clips are saved to Animation/{gender}/{weapon}/clips/{action}.anim
        /// Used only in editor to generate prefabs
        /// </summary>
        /// <param name="skeleton"></param>
        /// <param name="weapon"></param>
        /// <param name="gender"></param>
        /// <returns></returns>
        public void LoadClips(GameObject skeleton, ZMD zmd, WeaponType weapon, GenderType gender)
        {
            List <AnimationClip> clips = new List <AnimationClip>();

            foreach (ActionType action in Enum.GetValues(typeof(ActionType)))
            {
                string zmoPath   = Utils.FixPath(ResourceManager.Instance.GetZMOPath(weapon, action, gender)); // Assets/3ddata path
                string unityPath = "Assets/Resources/Animation/" + gender.ToString() + "/" + weapon.ToString() + "/clips/" + action.ToString() + ".anim";

                AnimationClip clip = new ZMO("Assets/" + zmoPath).buildAnimationClip(zmd);
                clip.name   = action.ToString();
                clip.legacy = true;
                clip        = (AnimationClip)Utils.SaveReloadAsset(clip, unityPath, ".anim");
                clips.Add(clip);
            }

            Animation animation = skeleton.AddComponent <Animation> ();

            AnimationUtility.SetAnimationClips(animation, clips.ToArray());
        }
예제 #6
0
        public void GenerateAnimationAsset(GenderType gender, RigType rig, Dictionary <String, String> zmoPaths)
        {
            GameObject skeleton = new GameObject("skeleton");
            bool       male     = (gender == GenderType.MALE);
            ZMD        zmd      = new ZMD(male ? "Assets/3DData/Avatar/MALE.ZMD" : "Assets/3DData/Avatar/FEMALE.ZMD");

            zmd.buildSkeleton(skeleton);

            BindPoses poses = ScriptableObject.CreateInstance <BindPoses>();

            poses.bindPoses      = zmd.bindposes;
            poses.boneNames      = getBoneNames(zmd.boneTransforms);
            poses.boneTransforms = zmd.boneTransforms;
            LoadClips(skeleton, zmd, gender, rig, zmoPaths);
            string path = "Assets/Resources/Animation/" + gender.ToString() + "/" + rig.ToString() + "/skeleton.prefab";

            AssetDatabase.CreateAsset(poses, path.Replace("skeleton.prefab", "bindPoses.asset"));
            AssetDatabase.SaveAssets();
            PrefabUtility.CreatePrefab(path, skeleton);
        }
예제 #7
0
        public SceneExporter(
            string objectName,
            List <ZMS> mesh,
            ZMD skeleton,
            List <ZMO> animation)
        {
            scene   = new StringBuilder();
            zms     = mesh;
            zmd     = skeleton;
            zmo     = animation;
            objName = objectName;

            nodes     = new List <string>();
            resources = new List <string>();

            // should include num of external objects
            //num_resources = (uint)(zms.Count + zms.Count + zmo.Count);
            num_resources = zms.Count;
            scene.AppendFormat("[gd_scene load_steps={0} format=2]\n", num_resources);
        }
예제 #8
0
 public void SetSkeleton(string path)
 {
     this.Skeleton = new ZMD();
     this.Skeleton.Load(path, ClientType.IROSE);
     for (int i = 0; i < this.listZMS.Count; i++)
     {
         if (this.listZMS[i].GetBindBoneIndex().HasValue)
         {
             for (int j = 0; j < (int)this.listZMS[i].vertCount; j++)
             {
                 this.listZMS[i].vertex[j].Position = Vector3.Transform(this.listZMS[i].vertex[j].Position, this.Skeleton.listBone[this.listZMS[i].GetBindBoneIndex().Value].matrix);
             }
         }
         if (this.listZMS[i].GetBindDummyIndex().HasValue)
         {
             for (int j = 0; j < (int)this.listZMS[i].vertCount; j++)
             {
                 this.listZMS[i].vertex[j].Position = Vector3.Transform(this.listZMS[i].vertex[j].Position, this.Skeleton.listBone[this.listZMS[i].GetBindDummyIndex().Value + this.Skeleton.DummyOffset].matrix);
             }
         }
     }
 }
예제 #9
0
 public void SetSkeleton(string path)
 {
     this.Skeleton = new ZMD();
     this.Skeleton.Load(path, ClientType.IROSE);
     for (int i = 0; i < this.listZMS.Count; i++)
     {
         if (this.listZMS[i].GetBindBoneIndex().HasValue)
         {
             for (int j = 0; j < (int)this.listZMS[i].vertCount; j++)
             {
                 this.listZMS[i].vertex[j].Position = Vector3.Transform(this.listZMS[i].vertex[j].Position, this.Skeleton.listBone[this.listZMS[i].GetBindBoneIndex().Value].matrix);
             }
         }
         if (this.listZMS[i].GetBindDummyIndex().HasValue)
         {
             for (int j = 0; j < (int)this.listZMS[i].vertCount; j++)
             {
                 this.listZMS[i].vertex[j].Position = Vector3.Transform(this.listZMS[i].vertex[j].Position, this.Skeleton.listBone[this.listZMS[i].GetBindDummyIndex().Value + this.Skeleton.DummyOffset].matrix);
             }
         }
     }
 }
예제 #10
0
        public OgreAnimation(ZMD zmd, ZMO zmo, XmlDocument XMLDoc, string AnimationName)
        {
            XmlNode animations;

            // Get/Create <animations> tag
            XmlNodeList animationslist = XMLDoc.GetElementsByTagName("animations");

            if (animationslist.Count == 0)
            {
                animations = XMLDoc.CreateNode(XmlNodeType.Element, "animations", null);
                XMLDoc.DocumentElement.AppendChild(animations);
            }
            else
            {
                // Get the first node
                animations = animationslist[0];
            }

            XmlNode animation = XMLDoc.CreateNode(XmlNodeType.Element, "animation", null);

            animation.Attributes.Append(SetAttr(XMLDoc, "name", AnimationName));
            animation.Attributes.Append(SetAttr(XMLDoc, "length", string.Format("{0:0.000000}", zmo.Length * 1.5f)));

            XmlNode tracks = XMLDoc.CreateNode(XmlNodeType.Element, "tracks", null);

            for (int boneidx = 0; boneidx < zmd.Bone.Count; boneidx++)
            {
                XmlNode  track = XMLDoc.CreateNode(XmlNodeType.Element, "track", null);
                RoseBone bone  = zmd.Bone[boneidx];

                track.Attributes.Append(SetAttr(XMLDoc, "bone", bone.Name));
                XmlNode keyframes = XMLDoc.CreateNode(XmlNodeType.Element, "keyframes", null);

                for (int frameidx = 0; frameidx < zmo.Frames; frameidx++)
                {
                    XmlNode keyframe = XMLDoc.CreateNode(XmlNodeType.Element, "keyframe", null);
                    keyframe.Attributes.Append(SetAttr(XMLDoc, "time", string.Format("{0:0.000000}", zmo.FrameTime(frameidx) * 1.5f)));

                    XmlNode translate = XMLDoc.CreateNode(XmlNodeType.Element, "translate", null);

                    Vector3 translateVector = bone.Frame[frameidx].Position;
                    if (boneidx == 0)
                    {
                        translateVector  = VertexTransformMatrix * translateVector;
                        translateVector *= fscale;
                    }

                    translate.Attributes.Append(SetAttr(XMLDoc, "x", string.Format("{0:0.000000}", translateVector.x)));
                    translate.Attributes.Append(SetAttr(XMLDoc, "y", string.Format("{0:0.000000}", translateVector.y)));
                    translate.Attributes.Append(SetAttr(XMLDoc, "z", string.Format("{0:0.000000}", translateVector.z)));
                    keyframe.AppendChild(translate);

                    // Rotations

                    Quaternion qRot = bone.Rotation.UnitInverse() * bone.Frame[frameidx].Rotation;

                    Radian  RotAngle;
                    Vector3 RotAxis;
                    qRot.ToAngleAxis(out RotAngle, out RotAxis);

                    XmlNode rotate = XMLDoc.CreateNode(XmlNodeType.Element, "rotate", null);
                    rotate.Attributes.Append(SetAttr(XMLDoc, "angle", string.Format("{0:0.00000000}", RotAngle.ValueRadians)));

                    XmlNode axis = XMLDoc.CreateNode(XmlNodeType.Element, "axis", null);
                    axis.Attributes.Append(SetAttr(XMLDoc, "x", string.Format("{0:0.000000000}", RotAxis.x)));
                    axis.Attributes.Append(SetAttr(XMLDoc, "y", string.Format("{0:0.000000000}", RotAxis.y)));
                    axis.Attributes.Append(SetAttr(XMLDoc, "z", string.Format("{0:0.000000000}", RotAxis.z)));

                    rotate.AppendChild(axis);
                    keyframe.AppendChild(rotate);

                    XmlNode scale = XMLDoc.CreateNode(XmlNodeType.Element, "scale", null);
                    scale.Attributes.Append(SetAttr(XMLDoc, "x", string.Format("{0:0.000000000}", bone.Frame[frameidx].Scale.x)));
                    scale.Attributes.Append(SetAttr(XMLDoc, "y", string.Format("{0:0.000000000}", bone.Frame[frameidx].Scale.y)));
                    scale.Attributes.Append(SetAttr(XMLDoc, "z", string.Format("{0:0.000000000}", bone.Frame[frameidx].Scale.z)));

                    keyframe.AppendChild(scale);

                    keyframes.AppendChild(keyframe);
                }

                track.AppendChild(keyframes);
                tracks.AppendChild(track);
            }

            animation.AppendChild(tracks);
            animations.AppendChild(animation);
        }
예제 #11
0
        public OgreSkeleton(ZMD zmd)
        {
            XMLDoc = new XmlDocument();

            XmlNode skeleton      = XMLDoc.CreateNode(XmlNodeType.Element, "skeleton", null);
            XmlNode bones         = XMLDoc.CreateNode(XmlNodeType.Element, "bones", null);
            XmlNode bonehierarchy = XMLDoc.CreateNode(XmlNodeType.Element, "bonehierarchy", null);

            for (int boneIdx = 0; boneIdx < zmd.Bone.Count; boneIdx++)
            {
                XmlNode bone = XMLDoc.CreateNode(XmlNodeType.Element, "bone", null);
                bone.Attributes.Append(SetAttr("id", zmd.Bone[boneIdx].ID.ToString()));
                bone.Attributes.Append(SetAttr("name", zmd.Bone[boneIdx].Name));

                Vector3    pos = zmd.Bone[boneIdx].Position;
                Quaternion rot = zmd.Bone[boneIdx].Rotation;

                if (boneIdx == 0)
                {
                    Matrix4 transformMatrix = new Matrix4(rot);
                    transformMatrix.SetTrans(VertexTransformMatrix * pos);
                    transformMatrix *= RotationTransformMatrix;
                    pos              = transformMatrix.GetTrans();
                    rot              = transformMatrix.ExtractQuaternion();
                }

                pos *= fscale;

                rot.ToAngleAxis(out Radian Angle, out Vector3 Axis);

                XmlNode position = XMLDoc.CreateNode(XmlNodeType.Element, "position", null);
                position.Attributes.Append(SetAttr("x", string.Format("{0:0.000000000}", pos.x)));
                position.Attributes.Append(SetAttr("y", string.Format("{0:0.000000000}", pos.y)));
                position.Attributes.Append(SetAttr("z", string.Format("{0:0.000000000}", pos.z)));
                bone.AppendChild(position);

                XmlNode rotation = XMLDoc.CreateNode(XmlNodeType.Element, "rotation", null);
                rotation.Attributes.Append(SetAttr("angle", string.Format("{0:0.00000000}", Angle.ValueRadians)));

                XmlNode axis = XMLDoc.CreateNode(XmlNodeType.Element, "axis", null);
                axis.Attributes.Append(SetAttr("x", string.Format("{0:0.000000000}", Axis.x)));
                axis.Attributes.Append(SetAttr("y", string.Format("{0:0.000000000}", Axis.y)));
                axis.Attributes.Append(SetAttr("z", string.Format("{0:0.000000000}", Axis.z)));

                rotation.AppendChild(axis);
                bone.AppendChild(rotation);

                XmlNode scale = XMLDoc.CreateNode(XmlNodeType.Element, "scale", null);
                scale.Attributes.Append(SetAttr("x", "1"));
                scale.Attributes.Append(SetAttr("y", "1"));
                scale.Attributes.Append(SetAttr("z", "1"));

                bone.AppendChild(scale);

                bones.AppendChild(bone);

                if (boneIdx > 0)
                {
                    XmlNode      boneparent = XMLDoc.CreateNode(XmlNodeType.Element, "boneparent", null);
                    XmlAttribute boneHName  = XMLDoc.CreateAttribute("bone");
                    boneHName.Value = zmd.Bone[boneIdx].Name;
                    XmlAttribute boneHParent = XMLDoc.CreateAttribute("parent");
                    boneHParent.Value = zmd.Bone[zmd.Bone[boneIdx].ParentID].Name;
                    boneparent.Attributes.Append(boneHName);
                    boneparent.Attributes.Append(boneHParent);
                    bonehierarchy.AppendChild(boneparent);
                }
            } // for

            for (int dummyIdx = 0; dummyIdx < zmd.Dummy.Count; dummyIdx++)
            {
                XmlNode bone = XMLDoc.CreateNode(XmlNodeType.Element, "bone", null);
                bone.Attributes.Append(SetAttr("id", zmd.Dummy[dummyIdx].ID.ToString()));

                string dummy_name = string.Format("dummy_{0}", dummyIdx);
                //bone.Attributes.Append(SetAttr("name", zmd.Dummy[dummyIdx].Name));
                bone.Attributes.Append(SetAttr("name", dummy_name));

                Vector3    dummyPos = zmd.Dummy[dummyIdx].Position * fscale;
                Quaternion rot      = zmd.Dummy[dummyIdx].Rotation;
                rot.ToAngleAxis(out Radian dummyAngle, out Vector3 dummyAxis);

                XmlNode position = XMLDoc.CreateNode(XmlNodeType.Element, "position", null);

                position.Attributes.Append(SetAttr("x", string.Format("{0:0.000000}", dummyPos.x)));
                position.Attributes.Append(SetAttr("y", string.Format("{0:0.000000}", dummyPos.y)));
                position.Attributes.Append(SetAttr("z", string.Format("{0:0.000000}", dummyPos.z)));

                bone.AppendChild(position);

                XmlNode rotation = XMLDoc.CreateNode(XmlNodeType.Element, "rotation", null);

                rotation.Attributes.Append(SetAttr("angle", string.Format("{0:0.00000}", dummyAngle.ValueRadians)));

                XmlNode axis = XMLDoc.CreateNode(XmlNodeType.Element, "axis", null);
                axis.Attributes.Append(SetAttr("x", string.Format("{0:0.000000}", dummyAxis.x)));
                axis.Attributes.Append(SetAttr("y", string.Format("{0:0.000000}", dummyAxis.y)));
                axis.Attributes.Append(SetAttr("z", string.Format("{0:0.000000}", dummyAxis.z)));

                rotation.AppendChild(axis);
                bone.AppendChild(rotation);

                XmlNode scale = XMLDoc.CreateNode(XmlNodeType.Element, "scale", null);
                scale.Attributes.Append(SetAttr("x", "1"));
                scale.Attributes.Append(SetAttr("y", "1"));
                scale.Attributes.Append(SetAttr("z", "1"));

                bone.AppendChild(scale);

                bones.AppendChild(bone);

                XmlNode boneparent = XMLDoc.CreateNode(XmlNodeType.Element, "boneparent", null);
                //boneparent.Attributes.Append(SetAttr("bone", zmd.Dummy[dummyIdx].Name));
                boneparent.Attributes.Append(SetAttr("bone", dummy_name));
                boneparent.Attributes.Append(SetAttr("parent", zmd.Bone[zmd.Dummy[dummyIdx].ParentID].Name));
                bonehierarchy.AppendChild(boneparent);
            } // for

            skeleton.AppendChild(bones);
            skeleton.AppendChild(bonehierarchy);
            XMLDoc.AppendChild(skeleton);
        } // constructor
예제 #12
0
        public AnimationExporter(
            int resource_index,
            List <ZMO> zmo,
            ZMD zmd)
        {
            int animation_resource_idx = resource_index;

            System.Console.WriteLine("[Animation export] Start from idx: {0}", animation_resource_idx);

            nodes             = new StringBuilder();
            resource          = new StringBuilder();
            LastResourceIndex = resource_index;

            nodes.AppendLine("[node name=\"AnimationPlayer\" type=\"AnimationPlayer\" parent=\"Armature\"]");
            //nodes.AppendLine("root_node = NodePath(\"Armature\")");
            nodes.AppendLine("autoplay = \"\"");
            nodes.AppendLine("playback_process_mode = 1");
            nodes.AppendLine("playback_default_blend_time = 0.0");
            nodes.AppendLine("playback_speed = 1.0");
            nodes.AppendLine("blend_times = [  ]");

            List <Animation> animation = new List <Animation>();


            foreach (ZMO mo in zmo)
            {
                animation.Add(new Animation(mo.AnimationName, mo.Frames, mo.FPS));
            }

            // bones
            foreach (RoseBone bone in zmd.Bone)
            {
                foreach (BoneAnimation boneAnimation in bone.BoneAnimations)
                {
                    Animation anim = animation.Find(a => a.Name.Equals(boneAnimation.Name));
                    if (anim != null)
                    {
                        int fidx = 0;
                        foreach (BoneFrame frame in boneAnimation.Frames)
                        {
                            Quaternion r = frame.Rotation;
                            Vector3    p = new Matrix4(new Quaternion(new Radian(-1.57079633f), new Vector3(1.0f, 0.0f, 0.0f))) * frame.Position;

                            anim.Tracks.Add(new AnimationTrack(fidx++ / anim.FPS, 1f, p, r.Normalized(), frame.Scale, bone.ID, bone.Name));
                        }
                    }
                }
            }

            //dummies
            foreach (RoseBone bone in zmd.Dummy)
            {
                Quaternion bakedQ = bone.TransformMatrix.ExtractQuaternion().Normalized();
                Vector3    bakedP = bone.TransformMatrix.Translation;

                foreach (BoneAnimation boneAnimation in bone.BoneAnimations)
                {
                    Animation anim = animation.Find(a => a.Name.Equals(boneAnimation.Name));
                    if (anim != null)
                    {
                        int fidx = 0;
                        foreach (BoneFrame frame in boneAnimation.Frames)
                        {
                            anim.Tracks.Add(new AnimationTrack(fidx++ / anim.FPS, 1f, frame.Position, frame.Rotation.Normalized(), frame.Scale, bone.ID, bone.Name));
                        }
                    }
                }
            }

            foreach (Animation anim in animation)
            {
                nodes.AppendFormat("anims/{0} = SubResource({1})\n", anim.Name, animation_resource_idx);

                resource.AppendFormat("[sub_resource id={0} type=\"Animation\"]\n", animation_resource_idx);

                // info

                resource.AppendFormat("; FPS: {0} Frames: {1} Length: {2:G} sec\n", anim.FPS, anim.FramesCount, (float)anim.FramesCount / anim.FPS);
                resource.AppendFormat("resource_name = \"{0}\"\n", anim.Name);
                resource.AppendFormat("length = {0:G5}\n", (float)(anim.FramesCount) / anim.FPS);
                resource.AppendLine("step = 0.05");
                resource.AppendLine("loop = true");


                for (int bone_id = 0; bone_id < zmd.BonesCount; bone_id++)
                {
                    resource.AppendFormat("tracks/{0}/type = \"transform\"\n", bone_id);
                    resource.AppendFormat("tracks/{0}/path = NodePath(\".:{1}\")\n", bone_id, zmd.Bone[bone_id].Name);
                    resource.AppendFormat("tracks/{0}/interp = 1\n", bone_id);
                    resource.AppendFormat("tracks/{0}/loop_wrap = true\n", bone_id);
                    resource.AppendFormat("tracks/{0}/imported = true\n", bone_id);
                    resource.AppendFormat("tracks/{0}/enabled = true\n", bone_id);

                    List <string> transforms = new List <string>();
                    foreach (AnimationTrack track in anim.GetTracksForBoneId(bone_id))
                    {
                        transforms.Add(track.ToString());
                    }
                    resource.AppendFormat("tracks/{0}/keys = PoolRealArray({1})\n", bone_id, string.Join(", ", transforms.ToArray()));
                    //resource.AppendFormat("tracks/{0}/keys = [{1}]\n", bone_id, string.Join(", ", transforms.ToArray()));
                }
                animation_resource_idx++;
                resource.AppendLine();
            }

            LastResourceIndex = animation_resource_idx++;
        }