public void TestBasics() { // Test get euler index var eulerCurve = new EulerCurve(); Assert.That(EulerCurve.GetEulerIndex("localEulerAnglesRaw.y"), Is.EqualTo(1)); Assert.That(EulerCurve.GetEulerIndex("localEulerAnglesRaw."), Is.EqualTo(-1)); Assert.That(EulerCurve.GetEulerIndex("m_LocalRotation.x"), Is.EqualTo(-1)); // Test get quaternion index var quaternionCurve = new QuaternionCurve(); Assert.That(QuaternionCurve.GetQuaternionIndex("m_LocalRotation.w"), Is.EqualTo(3)); Assert.That(QuaternionCurve.GetQuaternionIndex("m_LocalRotation"), Is.EqualTo(-1)); Assert.That(QuaternionCurve.GetQuaternionIndex("localEulerAnglesRaw.y"), Is.EqualTo(-1)); // Test SetCurve var animCurve = new AnimationCurve(); eulerCurve.SetCurve(2, animCurve); Assert.That(eulerCurve.GetCurves()[2], Is.EqualTo(animCurve)); Assert.That(() => eulerCurve.SetCurve(-1, animCurve), Throws.Exception.TypeOf <System.IndexOutOfRangeException>()); Assert.That(() => eulerCurve.SetCurve(3, animCurve), Throws.Exception.TypeOf <System.IndexOutOfRangeException>()); quaternionCurve.SetCurve(3, animCurve); Assert.That(quaternionCurve.GetCurves()[3], Is.EqualTo(animCurve)); Assert.That(() => quaternionCurve.SetCurve(-5, animCurve), Throws.Exception.TypeOf <System.IndexOutOfRangeException>()); Assert.That(() => quaternionCurve.SetCurve(4, animCurve), Throws.Exception.TypeOf <System.IndexOutOfRangeException>()); }
public void ExportGenericData(IExportContainer container, YAMLMappingNode node, IReadOnlyDictionary <uint, string> tos) { StreamedClip streamedClip = MuscleClip.Clip.StreamedClip; DenseClip denseClip = MuscleClip.Clip.DenseClip; ConstantClip constantClip = MuscleClip.Clip.ConstantClip; IReadOnlyList <StreamedFrame> streamedFrames = streamedClip.GenerateFrames(container); Dictionary <uint, Vector3Curve> translations = new Dictionary <uint, Vector3Curve>(); Dictionary <uint, QuaternionCurve> rotations = new Dictionary <uint, QuaternionCurve>(); Dictionary <uint, Vector3Curve> scales = new Dictionary <uint, Vector3Curve>(); Dictionary <uint, Vector3Curve> eulers = new Dictionary <uint, Vector3Curve>(); Dictionary <uint, FloatCurve> floats = new Dictionary <uint, FloatCurve>(); int frameCount = Math.Max(denseClip.FrameCount - 1, streamedFrames.Count - 2); float[] frameCurvesValue = new float[streamedClip.CurveCount]; for (int frame = 0, streamFrame = 1; frame < frameCount; frame++, streamFrame++) { bool isAdd = true; float time; StreamedFrame streamedFrame = new StreamedFrame(); if (streamFrame < streamedFrames.Count) { streamedFrame = streamedFrames[streamFrame]; time = streamedFrame.Time; } else { time = (float)frame / SampleRate; } bool isStreamFrame = streamFrame < (streamedFrames.Count - 1); bool isDenseFrame = frame < (denseClip.FrameCount - 1); // number of stream curves which has key in current frame int streamFrameCurveCount = isStreamFrame ? streamedFrame.Curves.Count : 0; int denseFrameCurveCount = (int)denseClip.CurveCount; // total amount of curves which has key in current frame int frameCurveCount = streamFrameCurveCount + denseFrameCurveCount + constantClip.Constants.Count; int streamOffset = (int)streamedClip.CurveCount - streamFrameCurveCount; for (int curve = 0; curve < frameCurveCount;) { int curveIndex; IReadOnlyList <float> curvesValue; int offset; if (isStreamFrame && curve < streamedFrame.Curves.Count) { #warning TODO: read TCB and convert to in/out slope for (int key = curve; key < Math.Min(curve + 5, streamedFrame.Curves.Count); key++) { frameCurvesValue[key] = streamedFrame.Curves[key].Value; } curveIndex = streamedFrame.Curves[curve].Index; curvesValue = frameCurvesValue; offset = 0; } else if (isDenseFrame && curve < streamFrameCurveCount + denseFrameCurveCount) { curveIndex = curve + streamOffset; curvesValue = denseClip.SampleArray; offset = streamFrameCurveCount - frame * denseFrameCurveCount; } else if (!isDenseFrame && curve < streamFrameCurveCount + denseFrameCurveCount) { curve += denseFrameCurveCount; curveIndex = curve + streamOffset; curvesValue = constantClip.Constants; offset = streamFrameCurveCount + denseFrameCurveCount; isAdd = frame == 0 || frame == frameCount - 1; } else { curveIndex = curve + streamOffset; curvesValue = constantClip.Constants; offset = streamFrameCurveCount + denseFrameCurveCount; isAdd = frame == 0 || frame == frameCount - 1; } GenericBinding binding = ClipBindingConstant.FindBinding(curveIndex); uint pathHash = binding.Path; if (pathHash == 0) { curve++; continue; } if (!tos.TryGetValue(pathHash, out string path)) { path = "dummy" + pathHash; //Logger.Log(LogType.Debug, LogCategory.Export, $"Can't find path '{binding.Path}' in TOS for {ToLogString()}"); } switch (binding.BindingType) { case BindingType.Translation: // HACK: TEMP: if (curve + 3 > curvesValue.Count) { curve += 3; break; } float x = curvesValue[curve++ - offset]; float y = curvesValue[curve++ - offset]; float z = curvesValue[curve++ - offset]; float w = 0; if (isAdd) { Vector3f trans = new Vector3f(x, y, z); if (!translations.TryGetValue(pathHash, out Vector3Curve transCurve)) { transCurve = new Vector3Curve(path); translations[pathHash] = transCurve; } Vector3f defWeight = new Vector3f(1.0f / 3.0f); KeyframeTpl <Vector3f> transKey = new KeyframeTpl <Vector3f>(time, trans, defWeight); transCurve.Curve.Curve.Add(transKey); } break; case BindingType.Rotation: // HACK: TEMP: if (curve + 4 > curvesValue.Count) { curve += 4; break; } x = curvesValue[curve++ - offset]; y = curvesValue[curve++ - offset]; z = curvesValue[curve++ - offset]; w = curvesValue[curve++ - offset]; if (isAdd) { Quaternionf rot = new Quaternionf(x, y, z, w); if (!rotations.TryGetValue(pathHash, out QuaternionCurve rotCurve)) { rotCurve = new QuaternionCurve(path); rotations[pathHash] = rotCurve; } Quaternionf defWeight = new Quaternionf(1.0f / 3.0f); KeyframeTpl <Quaternionf> rotKey = new KeyframeTpl <Quaternionf>(time, rot, defWeight); rotCurve.Curve.Curve.Add(rotKey); } break; case BindingType.Scaling: // HACK: TEMP: if (curve + 3 > curvesValue.Count) { curve += 3; break; } x = curvesValue[curve++ - offset]; y = curvesValue[curve++ - offset]; z = curvesValue[curve++ - offset]; if (isAdd) { Vector3f scale = new Vector3f(x, y, z); if (!scales.TryGetValue(pathHash, out Vector3Curve scaleCurve)) { scaleCurve = new Vector3Curve(path); scales[pathHash] = scaleCurve; } Vector3f defWeight = new Vector3f(1.0f / 3.0f); KeyframeTpl <Vector3f> scaleKey = new KeyframeTpl <Vector3f>(time, scale, defWeight); scaleCurve.Curve.Curve.Add(scaleKey); } break; case BindingType.EulerRotation: // HACK: TEMP: if (curve + 3 > curvesValue.Count) { curve += 3; break; } x = curvesValue[curve++ - offset]; y = curvesValue[curve++ - offset]; z = curvesValue[curve++ - offset]; if (isAdd) { Vector3f euler = new Vector3f(x, y, z); if (!eulers.TryGetValue(pathHash, out Vector3Curve eulerCurve)) { eulerCurve = new Vector3Curve(path); eulers[pathHash] = eulerCurve; } Vector3f defWeight = new Vector3f(1.0f / 3.0f); KeyframeTpl <Vector3f> eulerKey = new KeyframeTpl <Vector3f>(time, euler, defWeight); eulerCurve.Curve.Curve.Add(eulerKey); } break; case BindingType.Floats: float value = curvesValue[curve++ - offset]; if (isAdd) { Float @float = new Float(value); if (!floats.TryGetValue(pathHash, out FloatCurve floatCurve)) { floatCurve = new FloatCurve(path); floats[pathHash] = floatCurve; } Float defWeight = new Float(1.0f / 3.0f); KeyframeTpl <Float> floatKey = new KeyframeTpl <Float>(time, @float, defWeight); floatCurve.Curve.Curve.Add(floatKey); } break; default: #warning TODO: ??? curve++; //throw new NotImplementedException(binding.BindingType.ToString()); break; } } } node.Add("m_RotationCurves", rotations.Values.ExportYAML(container)); node.Add("m_CompressedRotationCurves", YAMLSequenceNode.Empty); node.Add("m_EulerCurves", eulers.Values.ExportYAML(container)); node.Add("m_PositionCurves", translations.Values.ExportYAML(container)); node.Add("m_ScaleCurves", scales.Values.ExportYAML(container)); node.Add("m_FloatCurves", floats.Values.ExportYAML(container)); }
/// <summary>Constructs a new named animation curve.</summary> /// <param name="name">Name of the curve.</param> /// <param name="flags">Flags that describe the animation curve.</param> /// <param name="curve">Curve containing the animation data.</param> public NamedQuaternionCurve(string name, AnimationCurveFlags flags, QuaternionCurve curve) { this.name = name; this.flags = (AnimationCurveFlags)0; this.curve = curve; }
private static extern Vector3Curve Internal_quaternionToEulerCurve(QuaternionCurve quatCurve);
/// <summary>Converts a curve in quaternions into a curve using euler angles (in degrees).</summary> public static Vector3Curve QuaternionToEulerCurve(QuaternionCurve quatCurve) { return(Internal_quaternionToEulerCurve(quatCurve)); }
/// <summary> /// Export an AnimationClip as a single take /// </summary> protected void ExportAnimationClip(AnimationClip unityAnimClip, GameObject unityRoot, FbxScene fbxScene) { if (Verbose) { Debug.Log(string.Format("exporting clip {1} for {0}", unityRoot.name, unityAnimClip.name)); } // setup anim stack FbxAnimStack fbxAnimStack = FbxAnimStack.Create(fbxScene, unityAnimClip.name); fbxAnimStack.Description.Set("Animation Take: " + unityAnimClip.name); // add one mandatory animation layer FbxAnimLayer fbxAnimLayer = FbxAnimLayer.Create(fbxScene, "Animation Base Layer"); fbxAnimStack.AddMember(fbxAnimLayer); // Set up the FPS so our frame-relative math later works out // Custom frame rate isn't really supported in FBX SDK (there's // a bug), so try hard to find the nearest time mode. FbxTime.EMode timeMode = FbxTime.EMode.eCustom; double precision = 1e-6; while (timeMode == FbxTime.EMode.eCustom && precision < 1000) { timeMode = FbxTime.ConvertFrameRateToTimeMode(unityAnimClip.frameRate, precision); precision *= 10; } if (timeMode == FbxTime.EMode.eCustom) { timeMode = FbxTime.EMode.eFrames30; } FbxTime.SetGlobalTimeMode(timeMode); // set time correctly var fbxStartTime = FbxTime.FromSecondDouble(0); var fbxStopTime = FbxTime.FromSecondDouble(unityAnimClip.length); fbxAnimStack.SetLocalTimeSpan(new FbxTimeSpan(fbxStartTime, fbxStopTime)); /* The major difficulty: Unity uses quaternions for rotation * (which is how it should be) but FBX uses euler angles. So we * need to gather up the list of transform curves per object. */ var quaternions = new Dictionary <UnityEngine.GameObject, QuaternionCurve> (); foreach (EditorCurveBinding unityCurveBinding in AnimationUtility.GetCurveBindings(unityAnimClip)) { Object unityObj = AnimationUtility.GetAnimatedObject(unityRoot, unityCurveBinding); if (!unityObj) { continue; } AnimationCurve unityAnimCurve = AnimationUtility.GetEditorCurve(unityAnimClip, unityCurveBinding); if (unityAnimCurve == null) { continue; } int index = QuaternionCurve.GetQuaternionIndex(unityCurveBinding.propertyName); if (index == -1) { if (Verbose) { Debug.Log(string.Format("export binding {1} for {0}", unityCurveBinding.propertyName, unityObj.ToString())); } /* Some normal property (e.g. translation), export right away */ ExportAnimCurve(unityObj, unityAnimCurve, unityCurveBinding.propertyName, fbxScene, fbxAnimLayer); } else { /* Rotation property; save it to convert quaternion -> euler later. */ var unityGo = GetGameObject(unityObj); if (!unityGo) { continue; } QuaternionCurve quat; if (!quaternions.TryGetValue(unityGo, out quat)) { quat = new QuaternionCurve(); quaternions.Add(unityGo, quat); } quat.SetCurve(index, unityAnimCurve); } } /* now export all the quaternion curves */ foreach (var kvp in quaternions) { var unityGo = kvp.Key; var quat = kvp.Value; FbxNode fbxNode; if (!MapUnityObjectToFbxNode.TryGetValue(unityGo, out fbxNode)) { Debug.LogError(string.Format("no fbxnode found for '0'", unityGo.name)); continue; } quat.Animate(unityGo.transform, fbxNode, fbxAnimLayer, Verbose); } }
private void AddTransformCurve(float time, TransformType transType, IReadOnlyList <float> curveValues, IReadOnlyList <float> inSlopeValues, IReadOnlyList <float> outSlopeValues, int offset, string path) { switch (transType) { case TransformType.Translation: { Vector3Curve curve = new Vector3Curve(path); if (!m_translations.TryGetValue(curve, out List <KeyframeTpl <Vector3f> > transCurve)) { transCurve = new List <KeyframeTpl <Vector3f> >(); m_translations.Add(curve, transCurve); } float x = curveValues[offset + 0]; float y = curveValues[offset + 1]; float z = curveValues[offset + 2]; float inX = inSlopeValues[0]; float inY = inSlopeValues[1]; float inZ = inSlopeValues[2]; float outX = outSlopeValues[0]; float outY = outSlopeValues[1]; float outZ = outSlopeValues[2]; Vector3f value = new Vector3f(x, y, z); Vector3f inSlope = new Vector3f(inX, inY, inZ); Vector3f outSlope = new Vector3f(outX, outY, outZ); KeyframeTpl <Vector3f> transKey = new KeyframeTpl <Vector3f>(time, value, inSlope, outSlope, KeyframeTpl <Vector3f> .DefaultVector3Weight); transCurve.Add(transKey); } break; case TransformType.Rotation: { QuaternionCurve curve = new QuaternionCurve(path); if (!m_rotations.TryGetValue(curve, out List <KeyframeTpl <Quaternionf> > rotCurve)) { rotCurve = new List <KeyframeTpl <Quaternionf> >(); m_rotations.Add(curve, rotCurve); } float x = curveValues[offset + 0]; float y = curveValues[offset + 1]; float z = curveValues[offset + 2]; float w = curveValues[offset + 3]; float inX = inSlopeValues[0]; float inY = inSlopeValues[1]; float inZ = inSlopeValues[2]; float inW = inSlopeValues[3]; float outX = outSlopeValues[0]; float outY = outSlopeValues[1]; float outZ = outSlopeValues[2]; float outW = outSlopeValues[3]; Quaternionf value = new Quaternionf(x, y, z, w); Quaternionf inSlope = new Quaternionf(inX, inY, inZ, inW); Quaternionf outSlope = new Quaternionf(outX, outY, outZ, outW); KeyframeTpl <Quaternionf> rotKey = new KeyframeTpl <Quaternionf>(time, value, inSlope, outSlope, KeyframeTpl <Quaternionf> .DefaultQuaternionWeight); rotCurve.Add(rotKey); } break; case TransformType.Scaling: { Vector3Curve curve = new Vector3Curve(path); if (!m_scales.TryGetValue(curve, out List <KeyframeTpl <Vector3f> > scaleCurve)) { scaleCurve = new List <KeyframeTpl <Vector3f> >(); m_scales.Add(curve, scaleCurve); } float x = curveValues[offset + 0]; float y = curveValues[offset + 1]; float z = curveValues[offset + 2]; float inX = inSlopeValues[0]; float inY = inSlopeValues[1]; float inZ = inSlopeValues[2]; float outX = outSlopeValues[0]; float outY = outSlopeValues[1]; float outZ = outSlopeValues[2]; Vector3f value = new Vector3f(x, y, z); Vector3f inSlope = new Vector3f(inX, inY, inZ); Vector3f outSlope = new Vector3f(outX, outY, outZ); KeyframeTpl <Vector3f> scaleKey = new KeyframeTpl <Vector3f>(time, value, inSlope, outSlope, KeyframeTpl <Vector3f> .DefaultVector3Weight); scaleCurve.Add(scaleKey); } break; case TransformType.EulerRotation: { Vector3Curve curve = new Vector3Curve(path); if (!m_eulers.TryGetValue(curve, out List <KeyframeTpl <Vector3f> > eulerCurve)) { eulerCurve = new List <KeyframeTpl <Vector3f> >(); m_eulers.Add(curve, eulerCurve); } float x = curveValues[offset + 0]; float y = curveValues[offset + 1]; float z = curveValues[offset + 2]; float inX = inSlopeValues[0]; float inY = inSlopeValues[1]; float inZ = inSlopeValues[2]; float outX = outSlopeValues[0]; float outY = outSlopeValues[1]; float outZ = outSlopeValues[2]; Vector3f value = new Vector3f(x, y, z); Vector3f inSlope = new Vector3f(inX, inY, inZ); Vector3f outSlope = new Vector3f(outX, outY, outZ); KeyframeTpl <Vector3f> eulerKey = new KeyframeTpl <Vector3f>(time, value, inSlope, outSlope, KeyframeTpl <Vector3f> .DefaultVector3Weight); eulerCurve.Add(eulerKey); } break; default: throw new NotImplementedException(transType.ToString()); } }
private static extern void Internal_TAnimationCurve(QuaternionCurve managedInstance, KeyFrameQuat[] keyframes);
/// <summary>Registers a new curve used for animating rotation.</summary> /// <param name="name"> /// Unique name of the curve. This name will be used mapping the curve to the relevant bone in a skeleton, if any. /// </param> /// <param name="curve">Curve to add to the clip.</param> public void AddRotationCurve(string name, QuaternionCurve curve) { Internal_addRotationCurve(mCachedPtr, name, curve); }
private static extern void Internal_addRotationCurve(IntPtr thisPtr, string name, QuaternionCurve curve);