void StartSampleClip(short clipIdx)
        {
            _isSampling = true;

            if (_sampleParam != null)
            {
                _sampleParam.aniState.enabled = false;
            }

            AnimationClip clip = _clips[clipIdx];

            _sampleParam         = new SampleParam();
            _sampleParam.clipIdx = clipIdx;
            _sampleParam.clip    = clip;

            AnimationState aniState = _animation[clip.name];

            aniState.enabled        = true;
            aniState.weight         = 1;
            aniState.normalizedTime = 0f;
            aniState.speed          = 1f;
            _sampleParam.aniState   = aniState;

            _sampleParam.frameRate    = SAMPLER_FRAME_RATE;
            _sampleParam.frameCount   = (short)(clip.length * SAMPLER_FRAME_RATE);
            _sampleParam.currFrameIdx = 0;

            GetAnimationCurves();

            transform.parent     = null;
            transform.position   = Vector3.zero;
            transform.rotation   = Quaternion.identity;
            transform.localScale = Vector3.one;

            foreach (var boneData in _allBoneDatas)
            {
                boneData.matrixes[_sampleParam.clipIdx] = new Matrix4x4[_sampleParam.frameCount];
            }

            foreach (var jointData in _allJointDatas)
            {
                jointData.matrixes[_sampleParam.clipIdx] = new PosRot[_sampleParam.frameCount];
            }

            _rootMotionData.matrixes[_sampleParam.clipIdx] = new PosRot[_sampleParam.frameCount];

            _sampleParams.Add(_sampleParam);

            Debug.LogFormat("<color=yellow>Start sample clip <{0}>[{1}],length:{2},frameRate:{3},frameCount:{4}</color>", clip.name, clipIdx, clip.length, _sampleParam.frameRate, _sampleParam.frameCount);
        }
        private void WriteToFile()
        {
            string savePath = string.Format("{0}{1}.asset", SKINNING_DATA_SAVE_DIR, transform.name);

            SkinningData skinningData = AssetDatabase.LoadAssetAtPath <SkinningData>(savePath);

            if (skinningData == null)
            {
                skinningData      = ScriptableObject.CreateInstance <SkinningData>();
                skinningData.name = transform.name;
                AssetDatabase.CreateAsset(skinningData, savePath);
            }

            skinningData.frameRate = SAMPLER_FRAME_RATE;
            skinningData.boneInfos = GetBoneInfos();
            skinningData.clipInfos = new BakedClipInfo[_sampleParams.Count];

            Vector2   texSize = CalcTextureSize();
            Texture2D tex     = new Texture2D((int)texSize.x, (int)texSize.y, TextureFormat.RGBAHalf, false, true);

            Color[] pixels = tex.GetPixels();

            int pixelIdx = 0;

            /*
             *  layout: Clip -> FrameIdx -> Bone,存储完某一帧内所有骨头再存下一帧,原因是同一帧内数据相互靠近可以提高显存 cache 命中率
             *  简化存储可以使用 Dual Quaternion 或者只存储位移和旋转,然后在 shader 里还原(可能会耗费一定性能)
             */
            for (int i = 0; i < _sampleParams.Count; i++)
            {
                SampleParam   sampleParam = _sampleParams[i];
                BakedClipInfo clipData    = new BakedClipInfo();

                int frameCnt = sampleParam.frameCount;

                clipData.name             = sampleParam.clip.name;
                clipData.frameCount       = sampleParam.frameCount;
                clipData.duration         = sampleParam.clip.length;
                clipData.wrapMode         = sampleParam.clip.wrapMode;
                clipData.localBounds      = sampleParam.clip.localBounds;
                clipData.clipPixelOffset  = pixelIdx;
                clipData.frameRate        = skinningData.frameRate; // TODO 先临时复制一下全局帧率
                clipData.curveDatas       = _curveDatas[i];
                skinningData.clipInfos[i] = clipData;

                for (int j = 0; j < frameCnt; j++)
                {
                    foreach (var boneData in _allBoneDatas)
                    {
                        if (boneData.isJoint)
                        {
                            continue;
                        }

                        Matrix4x4 matrix = boneData.matrixes[i][j];
                        // 去掉第四行(0,0,0,1)
                        //matrix.GetRow(0);
                        pixels[pixelIdx++] = new Color(matrix.m00, matrix.m01, matrix.m02, matrix.m03);
                        pixels[pixelIdx++] = new Color(matrix.m10, matrix.m11, matrix.m12, matrix.m13);
                        pixels[pixelIdx++] = new Color(matrix.m20, matrix.m21, matrix.m22, matrix.m23);
                    }
                }
            }

            tex.SetPixels(pixels);
            tex.Apply();

            skinningData.width          = (short)texSize.x;
            skinningData.height         = (short)texSize.y;
            skinningData.bakedBoneDatas = tex.GetRawTextureData();

            /*
             *  save joints and rootMotion
             */
            //skinningData.jointNames = _allJointDatas.Select(jointData => jointData.transform.name).ToArray();
            skinningData.bakedJointDatas = new List <JointClipData>();      //new PosRot[_clips.Length][][];
            skinningData.rootMotions     = new List <RootMotionClipData>(); //new PosRot[_clips.Length][];

            int jointCount = _allJointDatas.Count;

            for (int i = 0; i < _sampleParams.Count; i++)
            {
                SampleParam sampleParam = _sampleParams[i];
                int         frameCnt    = sampleParam.frameCount;
                skinningData.bakedJointDatas.Add(new JointClipData());
                skinningData.bakedJointDatas[i].clipName = sampleParam.clip.name;
                skinningData.rootMotions.Add(new RootMotionClipData());

                for (int j = 0; j < frameCnt; j++)
                {
                    skinningData.bakedJointDatas[i].frameData.Add(new JointFrameData());
                    for (int k = 0; k < jointCount; k++)
                    {
                        skinningData.bakedJointDatas[i].frameData[j].jointData.Add(_allJointDatas[k].matrixes[i][j]);
                    }

                    skinningData.rootMotions[i].data.Add(_rootMotionData.matrixes[i][j]);
                }
            }

            /*
             *  save animation events
             */
            for (int i = 0; i < _sampleParams.Count; i++)
            {
                SampleParam      sampleParam = _sampleParams[i];
                AnimationEvent[] aniEvents   = sampleParam.clip.events;
                EventInfo[]      events      = new EventInfo[aniEvents.Length];
                for (int j = 0; j < aniEvents.Length; j++)
                {
                    events[j] = new EventInfo(aniEvents[j]);
                }

                skinningData.clipInfos[i].events = events;
            }

            EditorUtility.SetDirty(skinningData);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }