private BabylonAnimation CreateMatrixAnimation(float from, float to, float[] matrix) { BabylonAnimation animation = new BabylonAnimation { name = "_matrix animation", property = "_matrix", dataType = 3, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, framePerSecond = Loader.GetFPS(), keysFull = new List <BabylonAnimationKey>() }; animation.keysFull.Add(new BabylonAnimationKey { frame = (int)from, values = matrix }); animation.keysFull.Add(new BabylonAnimationKey { frame = (int)from, values = matrix }); animation.keys = animation.keysFull.ToArray(); return(animation); }
private GLTFAccessor _createAndPopulateInput(GLTF gltf, BabylonAnimation babylonAnimation, int startFrame, int endFrame, bool offsetToStartAtFrameZero = true) { var buffer = GLTFBufferService.Instance.GetBuffer(gltf); var accessorInput = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer), "accessorAnimationInput", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.SCALAR ); // Populate accessor accessorInput.min = new float[] { float.MaxValue }; accessorInput.max = new float[] { float.MinValue }; int numKeys = 0; foreach (var babylonAnimationKey in babylonAnimation.keys) { if (babylonAnimationKey.frame < startFrame) { continue; } if (babylonAnimationKey.frame > endFrame) { continue; } numKeys++; float inputValue = babylonAnimationKey.frame; if (offsetToStartAtFrameZero) { inputValue -= startFrame; } inputValue /= Loader.GetFPS(); // Store values as bytes accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue)); // Update min and max values GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue); } ; accessorInput.count = numKeys; // bail out if there are no keys // todo [KeyInterpolation]: bail out only when there are no keyframes at all (?) and otherwise add the appropriate (interpolated) keyframes if (numKeys == 0) { return(null); } return(accessorInput); }
/// <summary> /// Export the morph target influence animation. /// </summary> /// <param name="blendShapeDeformerName"></param> /// <param name="weightIndex"></param> /// <returns>A list containing all animations</returns> private IList <BabylonAnimation> GetAnimationsInfluence(string blendShapeDeformerName, int weightIndex) { IList <BabylonAnimation> animations = new List <BabylonAnimation>(); BabylonAnimation animation = null; IDictionary <double, IList <double> > morphWeights = GetMorphWeightsByFrame(blendShapeDeformerName); // get keys List <BabylonAnimationKey> keys = new List <BabylonAnimationKey>(); for (int index = 0; index < morphWeights.Count; index++) { KeyValuePair <double, IList <double> > keyValue = morphWeights.ElementAt(index); // Set the animation key BabylonAnimationKey key = new BabylonAnimationKey() { frame = (int)keyValue.Key, values = new float[] { (float)keyValue.Value[weightIndex] } }; keys.Add(key); } List <BabylonAnimationKey> keysFull = new List <BabylonAnimationKey>(keys); // Optimization OptimizeAnimations(keys, false); // Ensure animation has at least 2 frames if (IsAnimationKeysRelevant(keys)) { // Animations animation = new BabylonAnimation() { name = "influence animation", // override default animation name dataType = (int)BabylonAnimation.DataType.Float, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, framePerSecond = Loader.GetFPS(), keys = keys.ToArray(), keysFull = keysFull, property = "influence" }; animations.Add(animation); } return(animations); }
/// <summary> /// Using MEL commands, it return the babylon animation /// </summary> /// <param name="objectName">The name of the Maya object</param> /// <param name="mayaProperty">The attribut in Maya</param> /// <param name="babylonProperty">The attribut in Babylon</param> /// <returns>A Babylon animation that represents the Maya animation</returns> public BabylonAnimation GetAnimationFloat(string objectName, string mayaProperty, string babylonProperty) { // Get keyframes IList <double> keyframes = GetKeyframes(objectName); BabylonAnimation animation = null; // set the key for each keyframe List <BabylonAnimationKey> keys = new List <BabylonAnimationKey>(); for (int index = 0; index < keyframes.Count; index++) { double keyframe = keyframes[index]; MGlobal.executeCommand($"getAttr -t {keyframe} {objectName}.{mayaProperty}", out double value); // Set the animation key BabylonAnimationKey key = new BabylonAnimationKey() { frame = (int)keyframe, values = new float[] { (float)value } }; keys.Add(key); } List <BabylonAnimationKey> keysFull = new List <BabylonAnimationKey>(keys); // Optimization OptimizeAnimations(keys, false); // Ensure animation has at least 2 frames if (IsAnimationKeysRelevant(keys)) { // Animations animation = new BabylonAnimation() { name = $"{babylonProperty} animation", // override default animation name dataType = (int)BabylonAnimation.DataType.Float, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, framePerSecond = Loader.GetFPS(), keys = keys.ToArray(), keysFull = keysFull, property = babylonProperty }; } return(animation); }
private BabylonAnimation GetAnimationsFrameByFrameMatrix(MFnTransform mFnTransform) { int start = Loader.GetMinTime(); int end = Loader.GetMaxTime(); BabylonAnimation animation = null; // get keys List <BabylonAnimationKey> keys = new List <BabylonAnimationKey>(); for (int currentFrame = start; currentFrame <= end; currentFrame++) { // Set the animation key BabylonAnimationKey key = new BabylonAnimationKey() { frame = currentFrame, values = GetBabylonMatrix(mFnTransform, currentFrame).m.ToArray() }; keys.Add(key); } var keysFull = new List <BabylonAnimationKey>(keys); // Optimization OptimizeAnimations(keys, false); // Do not remove linear animation keys for bones // Ensure animation has at least 2 frames if (IsAnimationKeysRelevant(keys)) { // Animations animation = new BabylonAnimation() { name = mFnTransform.name + "Animation", // override default animation name dataType = (int)BabylonAnimation.DataType.Matrix, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, framePerSecond = Loader.GetFPS(), keys = keys.ToArray(), keysFull = keysFull, property = "_matrix" }; } return(animation); }
private BabylonAnimation GetDummyAnimation(GLTFNode gltfNode, int startFrame, int endFrame) { BabylonAnimation dummyAnimation = new BabylonAnimation(); dummyAnimation.name = "Dummy"; dummyAnimation.property = "scaling"; dummyAnimation.framePerSecond = Loader.GetFPS(); dummyAnimation.dataType = (int)BabylonAnimation.DataType.Vector3; BabylonAnimationKey startKey = new BabylonAnimationKey(); startKey.frame = startFrame; startKey.values = gltfNode.scale; BabylonAnimationKey endKey = new BabylonAnimationKey(); endKey.frame = endFrame; endKey.values = gltfNode.scale; dummyAnimation.keys = new BabylonAnimationKey[] { startKey, endKey }; return(dummyAnimation); }
/// <summary> /// Export to file /// </summary> /// <param name="outputDirectory">The directory to store the generated files</param> /// <param name="outputFileName">The filename to use for the generated files</param> /// <param name="outputFormat">The format to use for the generated files</param> /// <param name="generateManifest">Specifies if a manifest file should be generated</param> /// <param name="onlySelected">Specifies if only the selected objects should be exported</param> /// <param name="autoSaveMayaFile">Specifies if the Maya scene should be auto-saved</param> /// <param name="exportHiddenObjects">Specifies if hidden objects should be exported</param> /// <param name="copyTexturesToOutput">Specifies if textures should be copied to the output directory</param> /// <param name="optimizeVertices">Specifies if vertices should be optimized on export</param> /// <param name="exportTangents">Specifies if tangents should be exported</param> /// <param name="scaleFactor">Scales the scene by this factor</param> /// <param name="exportSkin">Specifies if skins should be exported</param> /// <param name="quality">The texture quality</param> /// <param name="dracoCompression">Specifies if draco compression should be used</param> /// <param name="exportMorphNormal">Specifies if normals should be exported for morph targets</param> /// <param name="exportMorphTangent">Specifies if tangents should be exported for morph targets</param> /// <param name="exportKHRLightsPunctual">Specifies if the KHR_lights_punctual extension should be enabled</param> /// <param name="exportKHRTextureTransform">Specifies if the KHR_texture_transform extension should be enabled</param> /// <param name="bakeAnimationFrames">Specifies if animations should be exporting keyframes directly or should manually bake out animations frame by frame</param> public void Export(ExportParameters exportParameters) { this.exportParameters = exportParameters; // Check if the animation is running MGlobal.executeCommand("play -q - state", out int isPlayed); if (isPlayed == 1) { RaiseError("Stop the animation before exporting."); return; } RaiseMessage("Export started", Color.Blue); var progression = 0.0f; ReportProgressChanged(progression); // Store export options this.isBabylonExported = exportParameters.outputFormat == "babylon" || exportParameters.outputFormat == "binary babylon"; var outputBabylonDirectory = Path.GetDirectoryName(exportParameters.outputPath); // Check directory exists if (!Directory.Exists(outputBabylonDirectory)) { RaiseError("Export stopped: Output folder does not exist"); ReportProgressChanged(100); return; } var watch = new Stopwatch(); watch.Start(); var babylonScene = new BabylonScene(outputBabylonDirectory); // Save scene if (exportParameters.autoSaveSceneFile) { RaiseMessage("Saving Maya file"); // Query expand file name string fileName = MGlobal.executeCommandStringResult($@"file -q -exn;"); // If scene has already been saved previously if (fileName.EndsWith(".ma") || fileName.EndsWith(".mb")) { // Name is already specified and this line will not fail MFileIO.save(); } else { // Open SaveAs dialog window MGlobal.executeCommand($@"fileDialog2;"); } } // Force output file extension to be babylon var outputFileName = Path.ChangeExtension(Path.GetFileName(exportParameters.outputPath), "babylon"); // Store selected nodes MSelectionList selectedNodes = new MSelectionList(); MGlobal.getActiveSelectionList(selectedNodes); selectedNodeFullPaths = new List <string>(); MItSelectionList mItSelectionList = new MItSelectionList(selectedNodes); while (!mItSelectionList.isDone) { MDagPath mDagPath = new MDagPath(); try { mItSelectionList.getDagPath(mDagPath); selectedNodeFullPaths.Add(mDagPath.fullPathName); } catch { // selected object is not a DAG object // fail silently } mItSelectionList.next(); } if (selectedNodeFullPaths.Count > 0) { RaiseMessage("Selected nodes full path"); foreach (string selectedNodeFullPath in selectedNodeFullPaths) { RaiseMessage(selectedNodeFullPath, 1); } } // Producer babylonScene.producer = new BabylonProducer { name = "Maya", version = "2018", exporter_version = exporterVersion, file = outputFileName }; // Global babylonScene.autoClear = true; // TODO - Retreive colors from Maya //babylonScene.clearColor = Loader.Core.GetBackGround(0, Tools.Forever).ToArray(); //babylonScene.ambientColor = Loader.Core.GetAmbient(0, Tools.Forever).ToArray(); babylonScene.TimelineStartFrame = Loader.GetMinTime(); babylonScene.TimelineEndFrame = Loader.GetMaxTime(); babylonScene.TimelineFramesPerSecond = Loader.GetFPS(); // TODO - Add custom properties _exportQuaternionsInsteadOfEulers = true; PrintDAG(true); PrintDAG(false); // Store the current frame. It can be change to find a proper one for the node/bone export double currentTime = Loader.GetCurrentTime(); // -------------------- // ------ Nodes ------- // -------------------- RaiseMessage("Exporting nodes"); // It makes each morph target manager export starts from id = 0. BabylonMorphTargetManager.Reset(); // Clear materials referencedMaterials.Clear(); multiMaterials.Clear(); // Get all nodes var dagIterator = new MItDag(MItDag.TraversalType.kDepthFirst, MFn.Type.kTransform); List <MDagPath> nodes = new List <MDagPath>(); while (!dagIterator.isDone) { MDagPath mDagPath = new MDagPath(); dagIterator.getPath(mDagPath); // Check if one of its descendant (direct or not) is a mesh/camera/light/locator if (isNodeRelevantToExportRec(mDagPath) // Ensure it's not one of the default cameras used as viewports in Maya && defaultCameraNames.Contains(mDagPath.partialPathName) == false) { nodes.Add(mDagPath); } else { // Skip descendants dagIterator.prune(); } dagIterator.next(); } // Export all nodes var progressionStep = 100.0f / nodes.Count; foreach (MDagPath mDagPath in nodes) { BabylonNode babylonNode = null; try { switch (getApiTypeOfDirectDescendants(mDagPath)) { case MFn.Type.kMesh: babylonNode = ExportMesh(mDagPath, babylonScene); break; case MFn.Type.kCamera: babylonNode = ExportCamera(mDagPath, babylonScene); break; case MFn.Type.kLight: // Lights api type are actually kPointLight, kSpotLight... babylonNode = ExportLight(mDagPath, babylonScene); break; case MFn.Type.kLocator: // Camera target babylonNode = ExportDummy(mDagPath, babylonScene); break; } } catch (Exception e) { this.RaiseWarning(String.Format("Exception raised during export. Node will be exported as dummy node. \r\nMessage: \r\n{0} \r\n{1}", e.Message, e.InnerException), 2); } // If node is not exported successfully if (babylonNode == null) { // Create a dummy (empty mesh) babylonNode = ExportDummy(mDagPath, babylonScene); } ; // Update progress bar progression += progressionStep; ReportProgressChanged(progression); CheckCancelled(); } RaiseMessage(string.Format("Total meshes: {0}", babylonScene.MeshesList.Count), Color.Gray, 1); // if nothing is enlightened, exclude all meshes foreach (BabylonLight light in babylonScene.LightsList) { if (light.includedOnlyMeshesIds.Length == 0) { light.excludedMeshesIds = babylonScene.MeshesList.Select(m => m.id).ToArray(); } } /* * Switch coordinate system at global level * * Add a root node with negative scaling * Pros - It's safer to use a root node * Cons - It's cleaner to switch at object level (as it is done now) * Use root node method when you want to be 100% sure of the output * Don't forget to also inverse winding order of mesh indices */ //// Switch from right to left handed coordinate system //MUuid mUuid = new MUuid(); //mUuid.generate(); //var rootNode = new BabylonMesh //{ // name = "root", // id = mUuid.asString(), // scaling = new float[] { 1, 1, -1 } //}; //foreach(var babylonMesh in babylonScene.MeshesList) //{ // // Add root meshes as child to root node // if (babylonMesh.parentId == null) // { // babylonMesh.parentId = rootNode.id; // } //} //babylonScene.MeshesList.Add(rootNode); // Main camera BabylonCamera babylonMainCamera = null; if (babylonScene.CamerasList.Count > 0) { // Set first camera as main one babylonMainCamera = babylonScene.CamerasList[0]; babylonScene.activeCameraID = babylonMainCamera.id; RaiseMessage("Active camera set to " + babylonMainCamera.name, Color.Green, 1, true); } if (babylonMainCamera == null) { RaiseWarning("No camera defined", 1); } else { RaiseMessage(string.Format("Total cameras: {0}", babylonScene.CamerasList.Count), Color.Gray, 1); } // Default light if (!exportParameters.pbrNoLight && babylonScene.LightsList.Count == 0) { RaiseWarning("No light defined", 1); RaiseWarning("A default ambient light was added for your convenience", 1); ExportDefaultLight(babylonScene); } else { RaiseMessage(string.Format("Total lights: {0}", babylonScene.LightsList.Count), Color.Gray, 1); } var sceneScaleFactor = exportParameters.scaleFactor; if (exportParameters.scaleFactor != 1.0f) { RaiseMessage(String.Format("A root node is added to globally scale the scene by {0}", sceneScaleFactor), 1); // Create root node for scaling BabylonMesh rootNode = new BabylonMesh { name = "root", id = Tools.GenerateUUID() }; rootNode.isDummy = true; float rootNodeScale = sceneScaleFactor; rootNode.scaling = new float[3] { rootNodeScale, rootNodeScale, rootNodeScale }; if (ExportQuaternionsInsteadOfEulers) { rootNode.rotationQuaternion = new float[] { 0, 0, 0, 1 }; } else { rootNode.rotation = new float[] { 0, 0, 0 }; } // Update all top nodes var babylonNodes = new List <BabylonNode>(); babylonNodes.AddRange(babylonScene.MeshesList); babylonNodes.AddRange(babylonScene.CamerasList); babylonNodes.AddRange(babylonScene.LightsList); foreach (BabylonNode babylonNode in babylonNodes) { if (babylonNode.parentId == null) { babylonNode.parentId = rootNode.id; } } // Store root node babylonScene.MeshesList.Add(rootNode); } // -------------------- // ----- Materials ---- // -------------------- RaiseMessage("Exporting materials"); GenerateMaterialDuplicationDatas(babylonScene); foreach (var mat in referencedMaterials) { ExportMaterial(mat, babylonScene, exportParameters.pbrFull); CheckCancelled(); } foreach (var mat in multiMaterials) { ExportMultiMaterial(mat.Key, mat.Value, babylonScene, exportParameters.pbrFull); CheckCancelled(); } UpdateMeshesMaterialId(babylonScene); RaiseMessage(string.Format("Total: {0}", babylonScene.MaterialsList.Count + babylonScene.MultiMaterialsList.Count), Color.Gray, 1); // Export skeletons if (exportParameters.exportSkins && skins.Count > 0) { progressSkin = 0; progressSkinStep = 100 / skins.Count; ReportProgressChanged(progressSkin); RaiseMessage("Exporting skeletons"); foreach (var skin in skins) { ExportSkin(skin, babylonScene); } } // set back the frame Loader.SetCurrentTime(currentTime); // ---------------------------- // ----- Animation groups ----- // ---------------------------- RaiseMessage("Export animation groups"); // add animation groups to the scene babylonScene.animationGroups = ExportAnimationGroups(babylonScene); if (isBabylonExported) { // if we are exporting to .Babylon then remove then remove animations from nodes if there are animation groups. if (babylonScene.animationGroups.Count > 0) { // add animations of each nodes in the animGroup List <BabylonNode> babylonNodes = new List <BabylonNode>(); babylonNodes.AddRange(babylonScene.MeshesList); babylonNodes.AddRange(babylonScene.CamerasList); babylonNodes.AddRange(babylonScene.LightsList); foreach (BabylonNode node in babylonNodes) { node.animations = null; } foreach (BabylonSkeleton skel in babylonScene.SkeletonsList) { foreach (BabylonBone bone in skel.bones) { bone.animation = null; } } } // setup a default skybox for the scene for .Babylon export. var sourcePath = exportParameters.pbrEnvironment; if (!string.IsNullOrEmpty(sourcePath)) { babylonScene.createDefaultSkybox = exportParameters.createDefaultSkybox; var fileName = Path.GetFileName(sourcePath); // Allow only dds file format if (!fileName.EndsWith(".dds")) { RaiseWarning("Failed to export defauenvironment texture: only .dds format is supported."); } else { RaiseMessage($"texture id = Max_Babylon_Default_Environment"); babylonScene.environmentTexture = fileName; if (exportParameters.writeTextures) { try { var destPath = Path.Combine(babylonScene.OutputPath, fileName); if (File.Exists(sourcePath) && sourcePath != destPath) { File.Copy(sourcePath, destPath, true); } } catch { // silently fails RaiseMessage($"Fail to export the default env texture", 3); } } } } } // Output babylonScene.Prepare(false, false); if (isBabylonExported) { Write(babylonScene, outputBabylonDirectory, outputFileName, exportParameters.outputFormat, exportParameters.generateManifest); } ReportProgressChanged(100); // Export glTF if (exportParameters.outputFormat == "gltf" || exportParameters.outputFormat == "glb") { bool generateBinary = exportParameters.outputFormat == "glb"; GLTFExporter gltfExporter = new GLTFExporter(); gltfExporter.ExportGltf(this.exportParameters, babylonScene, outputBabylonDirectory, outputFileName, generateBinary, this); } watch.Stop(); RaiseMessage(string.Format("Export done in {0:0.00}s", watch.ElapsedMilliseconds / 1000.0), Color.Blue); }
private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List <GLTFChannel> channelList, List <GLTFAnimationSampler> samplerList, int startFrame, int endFrame) { if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager)) { return(false); } RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2); // Target var gltfTarget = new GLTFChannelTarget { node = gltfNode.index }; gltfTarget.path = "weights"; // Buffer var buffer = GLTFBufferService.Instance.GetBuffer(gltf); // --- Input --- var accessorInput = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer), "accessorAnimationInput", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.SCALAR ); // Populate accessor accessorInput.min = new float[] { float.MaxValue }; accessorInput.max = new float[] { float.MinValue }; var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager); var frames = new List <int>(influencesPerFrame.Keys); frames.Sort(); // Mandatory otherwise gltf loader of babylon doesn't understand int numKeys = 0; foreach (var frame in frames) { if (frame < startFrame) { continue; } if (frame > endFrame) { continue; } numKeys++; var inputValue = frame / (float)Loader.GetFPS(); // Store values as bytes accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue)); // Update min and max values GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue); } accessorInput.count = numKeys; // bail out if we have no keys to export (?) // todo [KeyInterpolation]: bail out only when there are no keyframes at all (?) and otherwise add the appropriate (interpolated) keyframes if (numKeys == 0) { return(false); } // --- Output --- GLTFAccessor accessorOutput = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer), "accessorAnimationWeights", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.SCALAR ); // Populate accessor foreach (var frame in frames) { if (frame < startFrame) { continue; } if (frame > endFrame) { continue; } var outputValues = influencesPerFrame[frame]; // Store values as bytes foreach (var outputValue in outputValues) { accessorOutput.count++; accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue)); } } // Animation sampler var gltfAnimationSampler = new GLTFAnimationSampler { input = accessorInput.index, output = accessorOutput.index }; gltfAnimationSampler.index = samplerList.Count; samplerList.Add(gltfAnimationSampler); // Channel var gltfChannel = new GLTFChannel { sampler = gltfAnimationSampler.index, target = gltfTarget }; channelList.Add(gltfChannel); return(true); }
private List <BabylonAnimation> GetAnimationsFrameByFrame(MFnTransform mFnTransform) { int start = Loader.GetMinTime(); int end = Loader.GetMaxTime(); // Animations List <BabylonAnimation> animations = new List <BabylonAnimation>(); string[] babylonAnimationProperties = new string[] { "scaling", "rotationQuaternion", "position", "visibility" }; Dictionary <string, List <BabylonAnimationKey> > keysPerProperty = new Dictionary <string, List <BabylonAnimationKey> >(); keysPerProperty.Add("scaling", new List <BabylonAnimationKey>()); keysPerProperty.Add("rotationQuaternion", new List <BabylonAnimationKey>()); keysPerProperty.Add("position", new List <BabylonAnimationKey>()); keysPerProperty.Add("visibility", new List <BabylonAnimationKey>()); // get keys for (int currentFrame = start; currentFrame <= end; currentFrame++) { // get transformation matrix at this frame MDoubleArray mDoubleMatrix = new MDoubleArray(); MGlobal.executeCommand($"getAttr -t {currentFrame} {mFnTransform.fullPathName}.matrix", mDoubleMatrix); mDoubleMatrix.get(out float[] localMatrix); MMatrix matrix = new MMatrix(localMatrix); var transformationMatrix = new MTransformationMatrix(matrix); // Retreive TRS vectors from matrix var position = transformationMatrix.getTranslation(); var rotationQuaternion = transformationMatrix.getRotationQuaternion(); var scaling = transformationMatrix.getScale(); // Switch coordinate system at object level position[2] *= -1; rotationQuaternion[0] *= -1; rotationQuaternion[1] *= -1; // create animation key for each property for (int indexAnimation = 0; indexAnimation < babylonAnimationProperties.Length; indexAnimation++) { string babylonAnimationProperty = babylonAnimationProperties[indexAnimation]; BabylonAnimationKey key = new BabylonAnimationKey(); key.frame = currentFrame; switch (indexAnimation) { case 0: // scaling key.values = scaling.ToArray(); break; case 1: // rotationQuaternion key.values = rotationQuaternion.ToArray(); break; case 2: // position key.values = position.ToArray(); break; case 3: // visibility key.values = new float[] { Loader.GetVisibility(mFnTransform.fullPathName, currentFrame) }; break; } keysPerProperty[babylonAnimationProperty].Add(key); } } // create animation for each property for (int indexAnimation = 0; indexAnimation < babylonAnimationProperties.Length; indexAnimation++) { string babylonAnimationProperty = babylonAnimationProperties[indexAnimation]; List <BabylonAnimationKey> keys = keysPerProperty[babylonAnimationProperty]; var keysFull = new List <BabylonAnimationKey>(keys); // Optimization OptimizeAnimations(keys, true); // Ensure animation has at least 2 frames if (IsAnimationKeysRelevant(keys)) { int dataType = 0; // "scaling", "rotationQuaternion", "position", "visibility" if (indexAnimation == 0 || indexAnimation == 2) // scaling and position { dataType = (int)BabylonAnimation.DataType.Vector3; } else if (indexAnimation == 1) // rotationQuaternion { dataType = (int)BabylonAnimation.DataType.Quaternion; } else // visibility { dataType = (int)BabylonAnimation.DataType.Float; } // Create BabylonAnimation animations.Add(new BabylonAnimation() { dataType = dataType, name = babylonAnimationProperty + " animation", framePerSecond = Loader.GetFPS(), loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, property = babylonAnimationProperty, keys = keys.ToArray(), keysFull = keysFull }); } } return(animations); }
/// <summary> /// Get TRS and visiblity animations of the transform /// </summary> /// <param name="transform">Transform above mesh/camera/light</param> /// <returns></returns> private List <BabylonAnimation> GetAnimation(MFnTransform transform) { // Animations MPlugArray connections = new MPlugArray(); MStringArray animCurvList = new MStringArray(); MIntArray keysTime = new MIntArray(); MDoubleArray keysValue = new MDoubleArray(); MFloatArray translateValues = new MFloatArray(); MFloatArray rotateValues = new MFloatArray(); MFloatArray scaleValues = new MFloatArray(); MFloatArray visibilityValues = new MFloatArray(); MFloatArray keyTimes = new MFloatArray(); List <BabylonAnimation> animationsObject = new List <BabylonAnimation>(); //Get the animCurve MGlobal.executeCommand("listConnections -type \"animCurve\" " + transform.fullPathName + ";", animCurvList); List <AnimCurvData> animCurvesData = new List <AnimCurvData>(); foreach (String animCurv in animCurvList) { AnimCurvData animCurvData = new AnimCurvData(); animCurvesData.Add(animCurvData); animCurvData.animCurv = animCurv; //Get the key time for each curves MGlobal.executeCommand("keyframe -q " + animCurv + ";", keysTime); //Get the value for each curves MGlobal.executeCommand("keyframe - q -vc -absolute " + animCurv + ";", keysValue); if (animCurv.EndsWith("translateZ") || animCurv.EndsWith("rotateX") || animCurv.EndsWith("rotateY")) { for (int index = 0; index < keysTime.Count; index++) { // Switch coordinate system at object level int key = keysTime[index]; if (animCurvData.valuePerFrame.ContainsKey(key) == false) { animCurvData.valuePerFrame.Add(key, (float)keysValue[index] * -1.0f); } } } else { for (int index = 0; index < keysTime.Count; index++) { int key = keysTime[index]; if (animCurvData.valuePerFrame.ContainsKey(key) == false) { animCurvData.valuePerFrame.Add(key, (float)keysValue[index]); } } } } string[] mayaAnimationProperties = new string[] { "translate", "rotate", "scale" }; string[] babylonAnimationProperties = new string[] { "position", "rotationQuaternion", "scaling" }; string[] axis = new string[] { "X", "Y", "Z" }; // Init TRS default values Dictionary <string, float> defaultValues = new Dictionary <string, float>(); float[] position = null; float[] rotationQuaternion = null; BabylonVector3.EulerRotationOrder rotationOrder = BabylonVector3.EulerRotationOrder.XYZ; float[] rotation = null; float[] scaling = null; GetTransform(transform, ref position, ref rotationQuaternion, ref rotation, ref rotationOrder, ref scaling); // coordinate system already switched defaultValues.Add("translateX", position[0]); defaultValues.Add("translateY", position[1]); defaultValues.Add("translateZ", position[2]); defaultValues.Add("rotateX", rotation[0]); defaultValues.Add("rotateY", rotation[1]); defaultValues.Add("rotateZ", rotation[2]); defaultValues.Add("scaleX", scaling[0]); defaultValues.Add("scaleY", scaling[1]); defaultValues.Add("scaleZ", scaling[2]); for (int indexAnimationProperty = 0; indexAnimationProperty < mayaAnimationProperties.Length; indexAnimationProperty++) { string mayaAnimationProperty = mayaAnimationProperties[indexAnimationProperty]; // Retreive animation curves data for current animation property // Ex: all "translate" data are "translateX", "translateY", "translateZ" List <AnimCurvData> animDataProperty = animCurvesData.Where(data => data.animCurv.Contains(mayaAnimationProperty)).ToList(); if (animDataProperty.Count == 0) { // Property is not animated continue; } // Get all frames for this property List <int> framesProperty = new List <int>(); foreach (var animData in animDataProperty) { framesProperty.AddRange(animData.valuePerFrame.Keys); } framesProperty = framesProperty.Distinct().ToList(); framesProperty.Sort(); // Get default values for this property BabylonAnimationKey lastBabylonAnimationKey = new BabylonAnimationKey(); lastBabylonAnimationKey.frame = 0; lastBabylonAnimationKey.values = new float[] { defaultValues[mayaAnimationProperty + "X"], defaultValues[mayaAnimationProperty + "Y"], defaultValues[mayaAnimationProperty + "Z"] }; // Compute all values for this property List <BabylonAnimationKey> babylonAnimationKeys = new List <BabylonAnimationKey>(); foreach (var frameProperty in framesProperty) { BabylonAnimationKey babylonAnimationKey = new BabylonAnimationKey(); babylonAnimationKeys.Add(babylonAnimationKey); // Frame babylonAnimationKey.frame = frameProperty; // Values float[] valuesProperty = new float[3]; for (int indexAxis = 0; indexAxis < axis.Length; indexAxis++) { AnimCurvData animCurvDataAxis = animDataProperty.Find(data => data.animCurv.EndsWith(axis[indexAxis])); float value; if (animCurvDataAxis != null && animCurvDataAxis.valuePerFrame.ContainsKey(frameProperty)) { value = animCurvDataAxis.valuePerFrame[frameProperty]; } else { value = lastBabylonAnimationKey.values[indexAxis]; } valuesProperty[indexAxis] = value; } babylonAnimationKey.values = valuesProperty.ToArray(); // Update last known values lastBabylonAnimationKey = babylonAnimationKey; } // Convert euler to quaternion angles if (indexAnimationProperty == 1) // Rotation { foreach (var babylonAnimationKey in babylonAnimationKeys) { BabylonVector3 eulerAngles = BabylonVector3.FromArray(babylonAnimationKey.values); BabylonVector3 eulerAnglesRadians = eulerAngles * (float)(Math.PI / 180); BabylonQuaternion quaternionAngles = eulerAnglesRadians.toQuaternion(rotationOrder); babylonAnimationKey.values = quaternionAngles.ToArray(); } } var keysFull = new List <BabylonAnimationKey>(babylonAnimationKeys); // Optimization OptimizeAnimations(babylonAnimationKeys, true); // Ensure animation has at least 2 frames string babylonAnimationProperty = babylonAnimationProperties[indexAnimationProperty]; if (IsAnimationKeysRelevant(babylonAnimationKeys, babylonAnimationProperty)) { // Create BabylonAnimation animationsObject.Add(new BabylonAnimation() { dataType = indexAnimationProperty == 1 ? (int)BabylonAnimation.DataType.Quaternion : (int)BabylonAnimation.DataType.Vector3, name = babylonAnimationProperty + " animation", framePerSecond = Loader.GetFPS(), loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, property = babylonAnimationProperty, keys = babylonAnimationKeys.ToArray(), keysFull = keysFull }); } } return(animationsObject); }