// only register when click the "start edit" button
        void OnUpdate()
        {
            if (EditorApplication.isCompiling)
            {
                if (ms_Instance != null)
                {
                    ms_Instance.Close();
                }
                return;
            }

            // check target object is still existed, or shutdown
            if (!m_InEditMF)
            {
                _EnsureStopEditing();
            }
            else
            {
                GameObject curGO = Selection.activeGameObject;
                if (curGO != m_InEditMF.gameObject)
                {
                    Selection.activeGameObject = m_InEditMF.gameObject;
                    EUtil.ShowNotification("GO re-selection is forbidden");
                }
            }
        }
Beispiel #2
0
        public override void OnInspectorGUI()
        {
            ShrinkWrap cp = (ShrinkWrap)target;

            EditorGUI.BeginChangeCheck();

            EConUtil.DrawActiveLine(cp);

            //constraint target
            var newTarget = (Transform)EditorGUILayout.ObjectField("Target Obj", cp.Target, typeof(Transform), true);

            if (newTarget != null &&
                (newTarget.GetComponent <MeshFilter>() == null || newTarget.GetComponent <Collider>() == null)
                )
            {
                EUtil.ShowNotification("Target must have MeshFilter & Collider");
            }
            else
            {
                cp.Target = newTarget;
            }

            EUtil.DrawSplitter();

            EUtil.PushGUIEnable(cp.IsActiveConstraint && cp.Target);
            {
                cp.Method = (ShrinkWrap.EShrinkWrapMethod)EditorGUILayout.EnumPopup(new GUIContent("ShrinkWrap Method", "select the algorithm for the action"), cp.Method);
                if (cp.Method == ShrinkWrap.EShrinkWrapMethod.NearestVertex)
                {
                    EUtil.GetSceneView().renderMode = DrawCameraMode.TexturedWire;
                }
                else
                {
                    EUtil.GetSceneView().renderMode = DrawCameraMode.Textured;
                }

                cp.Distance = EditorGUILayout.FloatField(new GUIContent("Distance", "keep distance to the projected point"), cp.Distance);

                if (cp.Method == ShrinkWrap.EShrinkWrapMethod.Project)
                {
                    cp.ProjectDir         = (EAxisD)EConUtil.DrawEnumBtns(EConUtil.AxisDs, EConUtil.AxisDStrs, cp.ProjectDir, "ProjectDir", "the direction or project ray, from origin of owner");
                    cp.OwnerSpace         = (ESpace)EditorGUILayout.EnumPopup(new GUIContent("OwnerSpace", "the space used for project dir"), cp.OwnerSpace);
                    cp.MaxProjectDistance = Mathf.Max(0, EditorGUILayout.FloatField(new GUIContent("Max Project Dist", "only execute wrap when the projected point is within the dist; 0 means infinite distance"), cp.MaxProjectDistance));
                }
                else if (cp.Method == ShrinkWrap.EShrinkWrapMethod.NearestVertex)
                {
                    //nothing here
                }

                //cp.ModifyInitInfo = EditorGUILayout.Toggle(new GUIContent("Modify InitInfo", "the constraint result will be written back to the initInfo"), cp.ModifyInitInfo);

                cp.Influence = EUtil.ProgressBar(cp.Influence, 0, 1f, "Influence: {0:F2}");
            }
            EUtil.PopGUIEnable();

            if (EditorGUI.EndChangeCheck())
            {
                EditorUtility.SetDirty(cp); //so ConstraintStack.Update can be called in edit-mode
            }
        }
Beispiel #3
0
    void OnGUI()
    {
        m_Animator = (Animator)EditorGUILayout.ObjectField("Animator", m_Animator, typeof(Animator), true);
        m_AnimType = (ModelImporterAnimationType)EditorGUILayout.EnumPopup("AnimType", m_AnimType);
        if (m_AnimType != ModelImporterAnimationType.Legacy)
        {
            m_AnimType = ModelImporterAnimationType.Generic;
        }

        bool bSet = (m_Animator != null);

        EUtil.PushGUIEnable(bSet);
        if (EUtil.Button("Convert Animation!", bSet ? Color.green : Color.red))
        {
            if (!m_Animator.isHuman)
            {
                EUtil.ShowNotification("The model is not in Humanoid rig!");
                return;
            }

            // save xforms recursively
            m_SavedPose = EUtil.CacheXformData(m_Animator.transform);

            m_Animator.Update(0);
#if !U5
            var ainfos = m_Animator.GetCurrentAnimationClipState(0); //only effect after Update is called
#else
            var ainfos = m_Animator.GetCurrentAnimatorClipInfo(0);   //only effect after Update is called
#endif
            if (ainfos.Length == 0)
            {
                EUtil.ShowNotification("No clip in AnimationController!");
            }
            else
            {
                AnimationClip clip             = ainfos[0].clip;
                string        oldClipAssetPath = AssetDatabase.GetAssetPath(clip);
                string        newClipAssetPath = PathUtil.StripExtension(oldClipAssetPath) + NEW_CLIP_POSTFIX + m_AnimType + ".anim";

                string filePath = EditorUtility.SaveFilePanel("Select export file path",
                                                              Path.GetDirectoryName(newClipAssetPath),
                                                              Path.GetFileNameWithoutExtension(newClipAssetPath),
                                                              "anim");
                if (filePath.Length > 0)
                {
                    filePath = PathUtil.FullPath2ProjectPath(filePath);
                    _ConvertAnim(filePath);
                }
                else
                {
                    EUtil.ShowNotification("Conversion Cancelled...");
                }
            }

            //apply the original pose back
            EUtil.ApplyXformData(m_Animator.transform, m_SavedPose);
        }
        EUtil.PopGUIEnable();
    }
Beispiel #4
0
        private void _ToggleCycle(CatmullRomCentripetal spline)
        {
            if (!spline.Cycle && spline.PointCount < 3)
            {
                EUtil.ShowNotification("Need at least 3 points to make cycle");
                return;
            }

            spline.Cycle = !spline.Cycle;

            if (m_curPtIdx >= spline.PointCount)
            {
                m_curPtIdx = spline.PointCount - 1;
            }
        }
Beispiel #5
0
            public override void OnInspectorGUI()
            {
                serializedObject.Update();

                MorphProc proc = (MorphProc)serializedObject.targetObject;

                EditorGUILayout.ObjectField("Mesh", proc.GetMesh().mesh, typeof(Mesh), true);

                m_propIndependentMesh.boolValue = EditorGUILayout.Toggle(new GUIContent("Indie Animation",
                                                                                        "by default, all instances of one animated prefab will be animated in sync;\nIf you need to make this object animated independently, check this option"),
                                                                         m_propIndependentMesh.boolValue);

                m_propUseMeshCache.boolValue = EditorGUILayout.Toggle(
                    new GUIContent("Use MeshCache",
                                   "This is a very important performance optimization, check this option as long as you don't manipulate this mesh yourself"),
                    m_propUseMeshCache.boolValue);

                if (proc.MorphCount > 0)
                {
                    _DrawDeformEntry(proc, 0, true);

                    for (int deformIdx = 1; deformIdx < m_propDeforms.arraySize; ++deformIdx)
                    {
                        _DrawDeformEntry(proc, deformIdx, false);
                    }

                    GUILayout.BeginHorizontal();
                    {
                        GUILayout.Space(30f);
                        if (GUILayout.Button("New Deform"))
                        {
                            if (proc.MorphCount >= MorphProc.MAX_SHAPEKEY_CNT)
                            {
                                EUtil.ShowNotification("Reached Max Deform Count");
                            }
                            else
                            {
                                proc.AddCurrentMeshAsNewShapeKeyMorph();
                            }
                        }
                        GUILayout.Space(30f);
                    }
                    GUILayout.EndHorizontal();
                }

                serializedObject.ApplyModifiedProperties();
            }
Beispiel #6
0
        void OnGUI()
        {
            if (m_solver == null)
            {
                return;
            }

            EditorGUI.BeginChangeCheck();
            m_endJoint = (Transform)EditorGUILayout.ObjectField("endJoint", m_endJoint, typeof(Transform), true);
            m_boneLen  = EditorGUILayout.IntField("boneLen", m_boneLen);
            if (EditorGUI.EndChangeCheck())
            {
                if (m_endJoint == null || m_boneLen <= 0)
                {
                    return;
                }

                m_solver.SetBones(m_endJoint, m_boneLen);
                m_solver.Target = m_endJoint.position;
                EUtil.ShowNotification("Set Bones: endJoint: " + m_endJoint.name + ", bonelen: " + m_boneLen);
            }

            if (GUILayout.Button("Return To endJoint"))
            {
                m_solver.Target = m_endJoint.position;
                EUtil.RepaintSceneView();
            }

            if (GUILayout.Button("Reset all rotation"))
            {
                var joints = m_solver.GetJoints();
                foreach (var j in joints)
                {
                    j.localRotation = Quaternion.identity;
                }
            }

            if (GUILayout.Button("GO"))
            {
                m_solver.Execute();
            }
        }
        private bool _CheckManualSetJointList(CCDSolverMB cp)
        {
            var lst = cp.jointList;

            for (int i = 0; i < lst.Count; ++i)
            {
                if (lst[i] == null)
                {
                    EUtil.ShowNotification("Joint List has null entry!");
                    return(false);
                }
            }

            if (lst.Count > 0 && lst[lst.Count - 1] != cp.transform)
            {
                EUtil.ShowNotification("Joint List's last entry must be 'this' transform");
                return(false);
            }

            return(true);
        }
Beispiel #8
0
        private void _DelPoint(CatmullRomCentripetal spline)
        {
            if (spline.PointCount <= 2)
            {
                EUtil.ShowNotification("The spline needs at least 2 points");
                return;
            }
            if (spline.Cycle && spline.PointCount <= 4)
            {
                EUtil.ShowNotification("Cannot delete any points to maintain cycle");
                return;
            }
            if (m_curPtIdx < 0)
            {
                return;
            }

            MUndo.RecordObject(target, "del point");
            MUndo.RecordObject(this, "del point");
            spline.RemovePoint(m_curPtIdx);
            m_curPtIdx = Mathf.Min(spline.PointCount - 1, m_curPtIdx);
        }
Beispiel #9
0
    // public method

    #endregion "public method"

    #region "private method"
    // private method

    private void _ConvertAnim(string newClipAssetPath)
    {
        // 0. prepare
        if (!m_Animator.isHuman)
        {
            Dbg.LogWarn("MuscleClipConverterEditor._ConvertAnim: Need to change to Humanoid rig first!");
            return;
        }

        m_SMR = m_Animator.GetComponentInChildren <SkinnedMeshRenderer>();
        if (m_SMR == null)
        {
            Dbg.LogWarn("MuscleClipConverterEditor._ConvertAnim: failed to find SMR under {0}", m_Animator.name);
            return;
        }

        m_Animator.Update(0);
#if !U5
        var ainfos = m_Animator.GetCurrentAnimationClipState(0); //only effect after Update is called
#else
        var ainfos = m_Animator.GetCurrentAnimatorClipInfo(0);   //only effect after Update is called
#endif
        AnimationClip         clip        = ainfos[0].clip;
        AnimationClipSettings clipSetting = AnimationUtility.GetAnimationClipSettings(clip);

        //{//debug
        //    var bindings = AnimationUtility.GetCurveBindings(clip);
        //    foreach( var b in bindings)
        //    {
        //        Dbg.Log("path: {0}, prop: {1}", b.path, b.propertyName);
        //    }
        //}

        Transform animatorTr = m_Animator.transform;
        Transform hipsBone   = null;
        CurveDict curveDict  = new CurveDict();

        float SAMPLE_RATE = clip.frameRate;
        float clipLen     = clip.length;

        Matrix4x4 animatorInitW2LMat = animatorTr.worldToLocalMatrix;
        Matrix4x4 hipsInitW2LMat     = Matrix4x4.identity;

        List <Transform> boneLst = new List <Transform>();
        for (HumanBodyBones boneIdx = 0; boneIdx < HumanBodyBones.LastBone; ++boneIdx)
        {
            Transform tr = m_Animator.GetBoneTransform(boneIdx);
            //Dbg.Log("Map: {0}->{1}", boneIdx, tr);
            if (tr != null)
            {
                boneLst.Add(tr);
                if (boneIdx == HumanBodyBones.Hips)
                {
                    hipsBone       = tr;
                    hipsInitW2LMat = hipsBone.parent.worldToLocalMatrix;
                    //clipSetting.level = -hipsBone.localPosition.y; // set Y offset
                    clipSetting.keepOriginalPositionY = false; //use RootNode position
                }
            }
        }
        Transform[] bones = boneLst.ToArray();

        // init curves for each bone
        for (int idx = 0; idx < bones.Length; ++idx)
        {
            Transform oneBone = bones[idx];
            string    trPath  = AnimationUtility.CalculateTransformPath(oneBone, animatorTr);

            var curves = new _Curves();
            curves.relPath = trPath;

            curveDict.Add(oneBone, curves);
        }

        // init rootmotion curve
        {
            var curves = new _Curves();
            curveDict.Add(animatorTr, curves);
        }

        AnimatorStateInfo curStateInfo = m_Animator.GetCurrentAnimatorStateInfo(0);
        float             nt           = curStateInfo.normalizedTime;
        m_Animator.Update(-nt * clipLen); //revert to 0 time

        {                                 // 1. bake animation info into curve on all bones transform
            float time      = 0f;
            float deltaTime = 1f / (SAMPLE_RATE);
            for (;
                 time <= clipLen || Mathf.Approximately(time, clipLen);
                 )
            {
                //Dbg.Log("nt = {0}", m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime);

                // bone
                for (int idx = 0; idx < bones.Length; ++idx)
                {
                    Transform oneBone = bones[idx];
                    _Curves   curves  = curveDict[oneBone];

                    if (oneBone == hipsBone)
                    {
                        continue; //skip the HipsBone. This is to add rootMotion matrix on hips, so Legacy is right
                    }
                    Vector3    pos = oneBone.localPosition;
                    Quaternion rot = oneBone.localRotation;

                    curves.X.AddKey(time, rot.x);
                    curves.Y.AddKey(time, rot.y);
                    curves.Z.AddKey(time, rot.z);
                    curves.W.AddKey(time, rot.w);

                    curves.PX.AddKey(time, pos.x);
                    curves.PY.AddKey(time, pos.y);
                    curves.PZ.AddKey(time, pos.z);
                }

                // root motion process
                {
                    { //on Animator transform
                        Vector3 pos = /*animatorTr.localPosition*/ animatorTr.position;
                        Vector3 fwd = animatorTr.forward;
                        Vector3 up  = animatorTr.up;

                        _Curves rootMotionCurves = curveDict[animatorTr];
                        Vector3 lpos             = animatorInitW2LMat.MultiplyPoint(pos);
                        Vector3 lfwd             = animatorInitW2LMat.MultiplyVector(fwd);
                        Vector3 lup = animatorInitW2LMat.MultiplyVector(up);

                        Quaternion rot = Quaternion.LookRotation(lfwd, lup);

                        rootMotionCurves.X.AddKey(time, rot.x);
                        rootMotionCurves.Y.AddKey(time, rot.y);
                        rootMotionCurves.Z.AddKey(time, rot.z);
                        rootMotionCurves.W.AddKey(time, rot.w);

                        rootMotionCurves.PX.AddKey(time, lpos.x);
                        rootMotionCurves.PY.AddKey(time, lpos.y);
                        rootMotionCurves.PZ.AddKey(time, lpos.z);
                    }

                    { //on hips transform
                        if (hipsBone != null)
                        {
                            Vector3 pos = hipsBone.position;
                            Vector3 fwd = hipsBone.forward;
                            Vector3 up  = hipsBone.up;

                            _Curves hipsCurves = curveDict[hipsBone];
                            Vector3 lpos       = hipsInitW2LMat.MultiplyPoint(pos);
                            Vector3 lfwd       = hipsInitW2LMat.MultiplyVector(fwd);
                            Vector3 lup        = hipsInitW2LMat.MultiplyVector(up);

                            //Dbg.Log("time: {0}, lpos: {1}", time, lpos.ToString("F2"));

                            Quaternion rot = Quaternion.LookRotation(lfwd, lup);

                            hipsCurves.X.AddKey(time, rot.x);
                            hipsCurves.Y.AddKey(time, rot.y);
                            hipsCurves.Z.AddKey(time, rot.z);
                            hipsCurves.W.AddKey(time, rot.w);

                            hipsCurves.PX.AddKey(time, lpos.x);
                            hipsCurves.PY.AddKey(time, lpos.y);
                            hipsCurves.PZ.AddKey(time, lpos.z);
                        }
                    }
                }

                if (!Mathf.Approximately(time + deltaTime, clipLen))
                {
                    m_Animator.Update(deltaTime);
                    time += deltaTime;
                }
                else
                {
                    m_Animator.Update(deltaTime - 0.005f); //keep it in the range, if go beyond, something bad could happen
                    time += deltaTime - 0.005f;
                }
            }
        } //end of 1.


        { // 2. set animation clip and store in AssetDatabase
            AnimationClip newClip = new AnimationClip();
            newClip.frameRate   = SAMPLE_RATE;
            newClip.localBounds = clip.localBounds;

            // set bone curves
            for (var ie = curveDict.GetEnumerator(); ie.MoveNext();)
            {
                var curves = ie.Current.Value;
                if (ie.Current.Key == animatorTr)
                { //root motion
                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionT.x", curves.PX);
                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionT.y", curves.PY);
                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionT.z", curves.PZ);

                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionQ.x", curves.X);
                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionQ.y", curves.Y);
                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionQ.z", curves.Z);
                    newClip.SetCurve(curves.relPath, typeof(Animator), "MotionQ.w", curves.W);
                }
                else
                {
                    newClip.SetCurve(curves.relPath, typeof(Transform), "localRotation.x", curves.X);
                    newClip.SetCurve(curves.relPath, typeof(Transform), "localRotation.y", curves.Y);
                    newClip.SetCurve(curves.relPath, typeof(Transform), "localRotation.z", curves.Z);
                    newClip.SetCurve(curves.relPath, typeof(Transform), "localRotation.w", curves.W);

                    newClip.SetCurve(curves.relPath, typeof(Transform), "localPosition.x", curves.PX);
                    newClip.SetCurve(curves.relPath, typeof(Transform), "localPosition.y", curves.PY);
                    newClip.SetCurve(curves.relPath, typeof(Transform), "localPosition.z", curves.PZ);
                }
            }

            // 2.1 copy the unmapped curves to new clip( not mapped by Muscle clip )
            _CopyOtherCurves(newClip, clip);

            // some setting work
            newClip.EnsureQuaternionContinuity();

#if !U5
            AnimationUtility.SetAnimationType(newClip, m_AnimType);
            RCall.CallMtd("UnityEditor.AnimationUtility", "SetAnimationClipSettings", null, newClip, clipSetting);
#else
            if (m_AnimType == ModelImporterAnimationType.Legacy)
            {
                newClip.legacy = true;
            }
            AnimationUtility.SetAnimationClipSettings(newClip, clipSetting);
#endif

            EUtil.SaveAnimClip(newClip, newClipAssetPath);

            EUtil.ShowNotification("Converted to: " + m_AnimType +
                                   (hipsBone != null ? ("\nroot=" + AnimationUtility.CalculateTransformPath(hipsBone, animatorTr)) : ""),
                                   3f
                                   );
        } //end of 2.

        // 3. clean job
        curveDict = null;
        AssetDatabase.SaveAssets();

        Dbg.Log("Converted: {0}", newClipAssetPath);
    }
    private void _OnGUI_SMR()
    {
        m_RootBone = EditorGUILayout.ObjectField(new GUIContent("Top GO", "fill the topmost GameObject of model"), m_RootBone, typeof(Transform), true) as Transform;
        GUIUtil.PushGUIEnable(m_RootBone != null);
        {
            GUILayout.BeginHorizontal();
            GUILayout.Space(60f);
            if (EUtil.Button("AutoFind", "Automatically collect all SMR & MF on this model", Color.blue))
            {
                _AutoFindRenderers();
            }
            GUILayout.Space(60f);
            GUILayout.EndHorizontal();
        }
        GUIUtil.PopGUIEnable();

        EUtil.DrawSplitter();

        //SMR
        for (int idx = 0; idx < m_SMRs.Count; ++idx)
        {
            GUILayout.BeginHorizontal();

            if (EUtil.Button("X", "delete", Color.red, GUILayout.Width(30f)))
            {
                m_SMRs.RemoveAt(idx);
                --idx;
                continue;
            }

            Color oc = GUI.backgroundColor;
            GUI.backgroundColor = Color.green;
            m_SMRs[idx]         = EditorGUILayout.ObjectField(m_SMRs[idx], typeof(SkinnedMeshRenderer), true) as SkinnedMeshRenderer;
            GUI.backgroundColor = oc;

            GUILayout.EndHorizontal();
        }

        GUILayout.BeginHorizontal();
        GUILayout.Space(60f);
        if (GUILayout.Button(new GUIContent("Add SMR Entry", "manually add \"Skinned-Mesh Renderer\"")))
        {
            m_SMRs.Add(null);
        }
        GUILayout.Space(60f);
        GUILayout.EndHorizontal();

        EUtil.DrawSplitter();

        //MF
        for (int idx = 0; idx < m_MFs.Count; ++idx)
        {
            GUILayout.BeginHorizontal();
            if (EUtil.Button("X", "delete", Color.red, GUILayout.Width(30f)))
            {
                m_MFs.RemoveAt(idx);
                --idx;
                continue;
            }

            Color oc = GUI.backgroundColor;
            GUI.backgroundColor = Color.yellow;
            m_MFs[idx]          = EditorGUILayout.ObjectField(m_MFs[idx], typeof(MeshFilter), true) as MeshFilter;
            GUI.backgroundColor = oc;

            GUILayout.EndHorizontal();
        }

        GUILayout.BeginHorizontal();
        GUILayout.Space(60f);
        if (GUILayout.Button(new GUIContent("Add MF Entry", "manually add \"Mesh Filter\"")))
        {
            m_MFs.Add(null);
        }
        GUILayout.Space(60f);
        GUILayout.EndHorizontal();

        EUtil.DrawSplitter();

        //export clips
        for (int idx = 0; idx < m_Clips.Count; ++idx)
        {
            GUILayout.BeginHorizontal();
            if (EUtil.Button("X", "delete", Color.red, GUILayout.Width(30f)))
            {
                m_Clips.RemoveAt(idx);
                --idx;
                continue;
            }
            m_Clips[idx] = EditorGUILayout.ObjectField(m_Clips[idx], typeof(AnimationClip), true) as AnimationClip;
            GUILayout.EndHorizontal();
        }

        GUILayout.BeginHorizontal();
        GUILayout.Space(60f);
        if (GUILayout.Button(new GUIContent("Add Clip", "add another animation clip to export")))
        {
            m_Clips.Add(null);
        }
        GUILayout.Space(60f);
        GUILayout.EndHorizontal();

        EUtil.DrawSplitter();

        bool  bHasValidEntry = _HasValidEntry();
        Color c = (bHasValidEntry) ? Color.green : Color.red;

        EUtil.PushBackgroundColor(c);
        GUIUtil.PushGUIEnable(bHasValidEntry);
        GUILayout.BeginHorizontal();
        GUILayout.Space(60f);
        if (GUILayout.Button("Export!"))
        {
            string saveDir  = _GetSaveDirectory();
            string filePath = EditorUtility.SaveFilePanel("Select export file path", saveDir, "anim", "dae");
            if (filePath.Length > 0)
            {
                string recDir = System.IO.Path.GetDirectoryName(filePath);
                _RecordSaveDirectory(recDir);

                SkinnedMeshRenderer[] smrArr = m_SMRs.TakeWhile(x => x != null).ToArray();
                MeshFilter[]          mfArr  = m_MFs.TakeWhile(x => x != null).ToArray();
                m_Clips.RemoveAll(x => x == null);

                DaeExporter exp = new DaeExporter(smrArr, mfArr, m_RootBone);
                exp.Export(m_Clips, filePath);

                AssetDatabase.Refresh();
            }
            else
            {
                EUtil.ShowNotification("Export Cancelled...");
            }
        }
        GUILayout.Space(60f);
        GUILayout.EndHorizontal();
        GUIUtil.PopGUIEnable();
        EUtil.PopBackgroundColor();
    }
    // unity event handlers

    #endregion "unity event handlers"

    #region "public method"
    // public method

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        // Draw label
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;

        EditorGUI.indentLevel = 0;

        // Calculate rects
        var btnRect    = new Rect(position.x, position.y, 25, LINEHEIGHT);
        var trPathRect = new Rect(position.x + 27, position.y, position.width - 27, LINEHEIGHT);

        // Draw fields - pass GUIContent.none to each so they are drawn without labels

        if (GUI.Button(btnRect, "C"))
        {
            m_bSelectingTransform = !m_bSelectingTransform;
        }

        if (property.FindPropertyRelative(F_VALID).boolValue)
        {
            EditorGUI.PropertyField(trPathRect, property.FindPropertyRelative(F_TRPATH), GUIContent.none);
        }
        else
        {
            EditorGUI.LabelField(trPathRect, "NULL");
        }

        if (m_bSelectingTransform)
        {
            var       objSelectRect = new Rect(position.x, position.y + LINEHEIGHT, position.width, LINEHEIGHT);
            Transform selfTr        = ((MonoBehaviour)(property.serializedObject.targetObject)).transform;
            Transform tr            = EditorGUI.ObjectField(objSelectRect, selfTr, typeof(Transform), true) as Transform;
            if (tr != selfTr)
            {
                if (tr == null)
                {
                    _SetTrPath(property, null);
                }
                else
                {
                    Transform ccroot = _FindCCRoot(tr);

                    if (ccroot == null)
                    {
                        string trPath = CCTrPath.SceneRoot + AnimationUtility.CalculateTransformPath(tr, null);
                        _SetTrPath(property, trPath);
                        EUtil.ShowNotification("TrPath: " + trPath, 4.0f);
                    }
                    else
                    {
                        string trPath = AnimationUtility.CalculateTransformPath(tr, ccroot);
                        _SetTrPath(property, trPath);
                        EUtil.ShowNotification("TrPath: " + trPath, 4.0f);
                    }
                }

                m_bSelectingTransform = false;
            }
        }

        // Set indent back to what it was
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }