123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- using BabylonExport.Entities;
- using GLTFExport.Entities;
- using System;
- using System.Collections.Generic;
- namespace Max2Babylon
- {
- partial class BabylonExporter
- {
- private static float FPS_FACTOR = 30.0f; // TODO - Which FPS factor ?
- private GLTFAnimation ExportNodeAnimation(BabylonNode babylonNode, GLTF gltf, GLTFNode gltfNode, BabylonScene babylonScene = null)
- {
- var channelList = new List<GLTFChannel>();
- var samplerList = new List<GLTFAnimationSampler>();
- if (babylonNode.animations != null && babylonNode.animations.Length > 0)
- {
- RaiseMessage("GLTFExporter.Animation | Export animation of node named: " + babylonNode.name, 2);
- foreach (BabylonAnimation babylonAnimation in babylonNode.animations)
- {
- // Target
- var gltfTarget = new GLTFChannelTarget
- {
- node = gltfNode.index
- };
- gltfTarget.path = _getTargetPath(babylonAnimation.property);
- if (gltfTarget.path == null)
- {
- // Unkown babylon animation property
- RaiseWarning("GLTFExporter.Animation | Unkown animation property '" + babylonAnimation.property + "'", 3);
- // Ignore this babylon animation
- continue;
- }
- // --- Input ---
- var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);
- // --- Output ---
- GLTFAccessor accessorOutput = _createAccessorOfPath(gltfTarget.path, gltf);
- // Populate accessor
- foreach (var babylonAnimationKey in babylonAnimation.keys)
- {
- var outputValues = babylonAnimationKey.values;
- // Store values as bytes
- foreach (var outputValue in outputValues)
- {
- accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
- }
- };
- accessorOutput.count = babylonAnimation.keys.Length;
- // 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);
- }
- }
- if (babylonNode.GetType() == typeof(BabylonMesh))
- {
- var babylonMesh = babylonNode as BabylonMesh;
- // Morph targets
- var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
- if (babylonMorphTargetManager != null)
- {
- ExportMorphTargetWeightAnimation(babylonMorphTargetManager, gltf, gltfNode, channelList, samplerList);
- }
- }
- // Do not export empty arrays
- if (channelList.Count > 0)
- {
- // Animation
- var gltfAnimation = new GLTFAnimation
- {
- channels = channelList.ToArray(),
- samplers = samplerList.ToArray()
- };
- gltf.AnimationsList.Add(gltfAnimation);
- return gltfAnimation;
- }
- else
- {
- return null;
- }
- }
- private GLTFAnimation ExportBoneAnimation(BabylonBone babylonBone, GLTF gltf, GLTFNode gltfNode)
- {
- var channelList = new List<GLTFChannel>();
- var samplerList = new List<GLTFAnimationSampler>();
- if (babylonBone.animation != null && babylonBone.animation.property == "_matrix")
- {
- RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2);
- var babylonAnimation = babylonBone.animation;
- // --- Input ---
- var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);
- // --- Output ---
- var paths = new string[] { "translation", "rotation", "scale" };
- var accessorOutputByPath = new Dictionary<string, GLTFAccessor>();
- foreach (string path in paths)
- {
- GLTFAccessor accessorOutput = _createAccessorOfPath(path, gltf);
- accessorOutputByPath.Add(path, accessorOutput);
- }
- // Populate accessors
- foreach (var babylonAnimationKey in babylonAnimation.keys)
- {
- var matrix = new BabylonMatrix();
- matrix.m = babylonAnimationKey.values;
- var translationBabylon = new BabylonVector3();
- var rotationQuatBabylon = new BabylonQuaternion();
- var scaleBabylon = new BabylonVector3();
- matrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);
- var outputValuesByPath = new Dictionary<string, float[]>();
- outputValuesByPath.Add("translation", translationBabylon.ToArray());
- outputValuesByPath.Add("rotation", rotationQuatBabylon.ToArray());
- outputValuesByPath.Add("scale", scaleBabylon.ToArray());
- // Store values as bytes
- foreach (string path in paths)
- {
- var accessorOutput = accessorOutputByPath[path];
- var outputValues = outputValuesByPath[path];
- foreach (var outputValue in outputValues)
- {
- accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
- }
- accessorOutput.count++;
- }
- };
- foreach (string path in paths)
- {
- var accessorOutput = accessorOutputByPath[path];
- // Animation sampler
- var gltfAnimationSampler = new GLTFAnimationSampler
- {
- input = accessorInput.index,
- output = accessorOutput.index
- };
- gltfAnimationSampler.index = samplerList.Count;
- samplerList.Add(gltfAnimationSampler);
- // Target
- var gltfTarget = new GLTFChannelTarget
- {
- node = gltfNode.index
- };
- gltfTarget.path = path;
- // Channel
- var gltfChannel = new GLTFChannel
- {
- sampler = gltfAnimationSampler.index,
- target = gltfTarget
- };
- channelList.Add(gltfChannel);
- }
- }
- // Do not export empty arrays
- if (channelList.Count > 0)
- {
- // Animation
- var gltfAnimation = new GLTFAnimation
- {
- channels = channelList.ToArray(),
- samplers = samplerList.ToArray()
- };
- gltf.AnimationsList.Add(gltfAnimation);
- return gltfAnimation;
- }
- else
- {
- return null;
- }
- }
- private GLTFAccessor _createAndPopulateInput(GLTF gltf, BabylonAnimation babylonAnimation)
- {
- 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 };
- foreach (var babylonAnimationKey in babylonAnimation.keys)
- {
- var inputValue = babylonAnimationKey.frame / FPS_FACTOR;
- // Store values as bytes
- accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
- // Update min and max values
- GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
- };
- accessorInput.count = babylonAnimation.keys.Length;
- return accessorInput;
- }
- private GLTFAccessor _createAccessorOfPath(string path, GLTF gltf)
- {
- var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
- GLTFAccessor accessorOutput = null;
- switch (path)
- {
- case "translation":
- accessorOutput = GLTFBufferService.Instance.CreateAccessor(
- gltf,
- GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
- "accessorAnimationPositions",
- GLTFAccessor.ComponentType.FLOAT,
- GLTFAccessor.TypeEnum.VEC3
- );
- break;
- case "rotation":
- accessorOutput = GLTFBufferService.Instance.CreateAccessor(
- gltf,
- GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
- "accessorAnimationRotations",
- GLTFAccessor.ComponentType.FLOAT,
- GLTFAccessor.TypeEnum.VEC4
- );
- break;
- case "scale":
- accessorOutput = GLTFBufferService.Instance.CreateAccessor(
- gltf,
- GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
- "accessorAnimationScales",
- GLTFAccessor.ComponentType.FLOAT,
- GLTFAccessor.TypeEnum.VEC3
- );
- break;
- }
- return accessorOutput;
- }
- private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List<GLTFChannel> channelList, List<GLTFAnimationSampler> samplerList)
- {
- if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))
- {
- return false;
- }
- RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2);
- var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager);
- var frames = new List<int>(influencesPerFrame.Keys);
- frames.Sort(); // Mandatory otherwise gltf loader of babylon doesn't understand
- // 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 };
- foreach (var frame in frames)
- {
- var inputValue = frame / FPS_FACTOR;
- // Store values as bytes
- accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
- // Update min and max values
- GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
- }
- accessorInput.count = influencesPerFrame.Count;
- // --- 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)
- {
- 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 bool _isBabylonMorphTargetManagerAnimationValid(BabylonMorphTargetManager babylonMorphTargetManager)
- {
- bool hasAnimation = false;
- bool areAnimationsValid = true;
- foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
- {
- if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
- {
- hasAnimation = true;
- // Ensure target has only one animation
- if (babylonMorphTarget.animations.Length > 1)
- {
- areAnimationsValid = false;
- RaiseWarning("GLTFExporter.Animation | Only one animation is supported for morph targets", 3);
- continue;
- }
- // Ensure the target animation property is 'influence'
- bool targetHasInfluence = false;
- foreach (BabylonAnimation babylonAnimation in babylonMorphTarget.animations)
- {
- if (babylonAnimation.property == "influence")
- {
- targetHasInfluence = true;
- }
- }
- if (targetHasInfluence == false)
- {
- areAnimationsValid = false;
- RaiseWarning("GLTFExporter.Animation | Only 'influence' animation is supported for morph targets", 3);
- continue;
- }
- }
- }
- return hasAnimation && areAnimationsValid;
- }
- /// <summary>
- /// The keys of each BabylonMorphTarget animation ARE NOT assumed to be identical.
- /// This function merges together all keys and binds to each an influence value for all targets.
- /// A target influence value is automatically computed when necessary.
- /// Computation rules are:
- /// - linear interpolation between target key range
- /// - constant value outside target key range
- /// </summary>
- /// <example>
- /// When:
- /// animation1.keys = {0, 25, 50, 100}
- /// animation2.keys = {50, 75, 100}
- ///
- /// Gives:
- /// mergedKeys = {0, 25, 50, 100, 75}
- /// range1=[0, 100]
- /// range2=[50, 100]
- /// for animation1, the value associated to key=75 is the interpolation of its values between 50 and 100
- /// for animation2, the value associated to key=0 is equal to the one at key=50 since 0 is out of range [50, 100] (same for key=25)</example>
- /// <param name="babylonMorphTargetManager"></param>
- /// <returns>A map which for each frame, gives the influence value of all targets</returns>
- private Dictionary<int, List<float>> _getTargetManagerAnimationsData(BabylonMorphTargetManager babylonMorphTargetManager)
- {
- // Merge all keys into a single set (no duplicated frame)
- var mergedFrames = new HashSet<int>();
- foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
- {
- if (babylonMorphTarget.animations != null)
- {
- var animation = babylonMorphTarget.animations[0];
- foreach (BabylonAnimationKey animationKey in animation.keys)
- {
- mergedFrames.Add(animationKey.frame);
- }
- }
- }
- // For each frame, gives the influence value of all targets (gltf structure)
- var influencesPerFrame = new Dictionary<int, List<float>>();
- foreach (var frame in mergedFrames)
- {
- influencesPerFrame.Add(frame, new List<float>());
- }
- foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
- {
- // For a given target, for each frame, gives the influence value of the target (babylon structure)
- var influencePerFrameForTarget = new Dictionary<int, float>();
- if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
- {
- var animation = babylonMorphTarget.animations[0];
- if (animation.keys.Length == 1)
- {
- // Same influence for all frames
- var influence = animation.keys[0].values[0];
- foreach (var frame in mergedFrames)
- {
- influencePerFrameForTarget.Add(frame, influence);
- }
- }
- else
- {
- // Retreive target animation key range [min, max]
- var babylonAnimationKeys = new List<BabylonAnimationKey>(animation.keys);
- babylonAnimationKeys.Sort();
- var minAnimationKey = babylonAnimationKeys[0];
- var maxAnimationKey = babylonAnimationKeys[babylonAnimationKeys.Count - 1];
-
- foreach (var frame in mergedFrames)
- {
- // Surround the current frame with closest keys available for the target
- BabylonAnimationKey lowerAnimationKey = minAnimationKey;
- BabylonAnimationKey upperAnimationKey = maxAnimationKey;
- foreach (BabylonAnimationKey animationKey in animation.keys)
- {
- if (lowerAnimationKey.frame < animationKey.frame && animationKey.frame <= frame)
- {
- lowerAnimationKey = animationKey;
- }
- if (frame <= animationKey.frame && animationKey.frame < upperAnimationKey.frame)
- {
- upperAnimationKey = animationKey;
- }
- }
- // In case the target has a key for this frame
- // or the current frame is out of target animation key range
- if (lowerAnimationKey.frame == upperAnimationKey.frame)
- {
- influencePerFrameForTarget.Add(frame, lowerAnimationKey.values[0]);
- }
- else
- {
- // Interpolate influence values
- var t = 1.0f * (frame - lowerAnimationKey.frame) / (upperAnimationKey.frame - lowerAnimationKey.frame);
- var influence = Tools.Lerp(lowerAnimationKey.values[0], upperAnimationKey.values[0], t);
- influencePerFrameForTarget.Add(frame, influence);
- }
- }
- }
- }
- else
- {
- // Target is not animated
- // Fill all frames with 0
- foreach (var frame in mergedFrames)
- {
- influencePerFrameForTarget.Add(frame, 0);
- }
- }
- // Switch from babylon to gltf storage representation
- foreach (var frame in mergedFrames)
- {
- List<float> influences = influencesPerFrame[frame];
- influences.Add(influencePerFrameForTarget[frame]);
- }
- }
- return influencesPerFrame;
- }
- private string _getTargetPath(string babylonProperty)
- {
- switch (babylonProperty)
- {
- case "position":
- return "translation";
- case "rotationQuaternion":
- return "rotation";
- case "scaling":
- return "scale";
- default:
- return null;
- }
- }
- }
- }
|