Example #1
0
        private void ProcessModel(GltfFile modelFile, GltfFile[] animFiles)
        {
            var modelPath = modelFile.Path;
            var modelData = modelFile.Data;

            var modelName = Path.GetFileNameWithoutExtension(modelPath);

            var outputModelFolder = Path.Combine(OutputFolder, modelName);

            Directory.CreateDirectory(outputModelFolder);

            Log.WriteLine($"Copying images...");

            if (modelData.Images != null)
            {
                using (Log.Indented())
                {
                    CopyImages(modelData, modelPath, outputModelFolder);
                }
            }

            Log.WriteLine($"Fetching model buffers...");

            using (Log.Indented())
            {
                FetchBuffers(modelData, modelPath);
            }

            var modelNodeIndexByName = modelData.Nodes.Select((n, i) => (n, i))
                                       .ToDictionary(pair => pair.Item1.Name, pair => pair.Item2);

            foreach (GltfFile animFile in animFiles)
            {
                Log.WriteLine($"Processing animations in {animFile.Path}...");
                using (Log.Indented())
                {
                    ProcessAnimations(modelFile, animFile, modelNodeIndexByName, outputModelFolder);
                }

                // The anim-file-data was modified, reload next time.
                animFile.Invalidate();
            }

            var outputBufferPath = Path.Combine(outputModelFolder, OutputBufferFilename);

            using (var newBufferStream = File.Create(outputBufferPath))
            {
                Log.WriteLine($"Packing all accessors in a single buffer...");
                PackAccessors(modelFile, newBufferStream, OutputBufferFilename);

                Log.WriteLine($"Writing model glTF file...");
                var outputModelPath = Path.Combine(outputModelFolder, OutputModelFilename);
                modelData.SaveModel(outputModelPath);
            }
        }
Example #2
0
        private void ProcessAnimations(GltfFile modelFile, GltfFile animFile, Dictionary <string, int> modelNodeIndexByName, string outputModelFolder)
        {
            foreach (Animation animation in animFile.Data.Animations)
            {
                using (Log.Indented())
                {
                    Log.WriteLine($"Processing animation clip {animation.Name} in {animFile.Path}...");

                    using (Log.Indented())
                    {
                        ProcessAnimation(modelFile, animFile, animation, modelNodeIndexByName, outputModelFolder);
                    }
                }
            }
        }
Example #3
0
        private void ProcessAnimation(GltfFile modelFile, GltfFile animFile, Animation animation, Dictionary <string, int> modelNodeIndexByName, string outputModelFolder)
        {
            var animData  = animFile.Data;
            var modelData = modelFile.Data;

            // Load all animation buffers into memory
            // TODO: This could be done lazily!
            FetchBuffers(animData, animFile.Path);

            foreach (var channel in animation.Channels)
            {
                var nodeIndex = channel.Target?.Node;
                if (nodeIndex.HasValue)
                {
                    var nodeName = animData.Nodes[nodeIndex.Value].Name;
                    if (!_allModelNodeNames.Contains(nodeName))
                    {
                        Log.WriteLine($"WARNING: Clip node '{nodeName}' not found in any model. Similar node names are:");
                        using (Log.Indented())
                        {
                            Fastenshtein.Levenshtein lev = new Fastenshtein.Levenshtein(nodeName);
                            Log.WriteLine(string.Join(", ", _allModelNodeNames.OrderBy(lev.DistanceFrom).Take(3)));
                        }
                    }
                }
            }

            int tc = 0;
            int rc = 0;
            int sc = 0;
            int wc = 0;

            // Keep only those animation channels that animate the current model,
            // and are different from the initial scene
            AnimationChannel[] clipChannels = animation
                                              .Channels
                                              .Where(channel =>
            {
                var nodeIndex = channel?.Target?.Node;
                if (!nodeIndex.HasValue)
                {
                    return(false);
                }
                var node = animData.Nodes[nodeIndex.Value];
                if (!modelNodeIndexByName.ContainsKey(node.Name))
                {
                    return(false);
                }

                var sampler        = animation.Samplers[channel.Sampler];
                var outputAccessor = animData.Accessors[sampler.Output];

                // We assume the exporter already reduced constant curves to a single key.
                if (outputAccessor.Count != 1)
                {
                    return(true);
                }

                // We don't handle sparse data yet, keep it
                if (!outputAccessor.BufferView.HasValue)
                {
                    return(true);
                }

                var mesh = node.Mesh.HasValue ? animData.Meshes[node.Mesh.Value] : null;

                var outputValues = GetComponentAtIndex(animData, outputAccessor, 0);

                switch (channel.Target.Path)
                {
                case AnimationChannelTarget.PathEnum.translation:
                    return(AreDifferent(ref tc, outputValues, node.Translation ?? DefaultTranslation, 1e-3f));

                case AnimationChannelTarget.PathEnum.rotation:
                    return(AreDifferent(ref rc, outputValues, node.Rotation ?? DefaultRotation, 1e-4f));

                case AnimationChannelTarget.PathEnum.scale:
                    return(AreDifferent(ref sc, outputValues, node.Scale ?? DefaultScale, 1e-4f));

                case AnimationChannelTarget.PathEnum.weights:
                    return(AreDifferent(ref wc, outputValues, node.Weights ?? mesh?.Weights, 1e-3f));

                default:
                    return(true);
                }
            })
                                              .ToArray();

            if (clipChannels.Length == 0)
            {
                Log.WriteLine($"NOTE: Animation '{animation.Name}' is not used by model '{modelFile.Path}'");
                return;
            }

            if (tc + rc + sc + wc > 0)
            {
                Log.WriteLine($"Skipped {tc} translation, {rc} rotation, {sc} scaling and {wc} weight redundant animation channels");
            }

            // Keep only those samplers used by any channel
            AnimationSampler[] clipSamplers = clipChannels
                                              .Select(channel => animation.Samplers[channel.Sampler])
                                              .Distinct()
                                              .ToArray();

            AnimationChannelTarget[] clipTargets = clipChannels
                                                   .Select(channel => channel.Target)
                                                   .ToArray();

            Accessor[] clipSamplerInputs  = clipSamplers.Select(sampler => animData.Accessors[sampler.Input]).ToArray();
            Accessor[] clipSamplerOutputs = clipSamplers.Select(sampler => animData.Accessors[sampler.Output]).ToArray();

            Accessor[] clipAccessors = clipSamplerInputs.Concat(clipSamplerOutputs).Distinct().ToArray();

            Remapper <Accessor> newAccessors = modelData.Accessors.Concat(clipAccessors).RemapFrom(animData.Accessors);

            Remapper <AnimationSampler> newSamplers = clipSamplers.RemapFrom(animation.Samplers);
            Remapper <AnimationChannel> newChannels = clipChannels.RemapFrom(animation.Channels);


            // Remap sampler indices
            foreach (var sampler in clipSamplers)
            {
                sampler.Input  = newAccessors.Remap(sampler.Input);
                sampler.Output = newAccessors.Remap(sampler.Output);
            }

            // Remap channel indices
            foreach (var channel in clipChannels)
            {
                channel.Sampler = newSamplers.Remap(channel.Sampler);
            }

            // Remap animation channel targets
            foreach (var target in clipTargets)
            {
                // ReSharper disable once PossibleInvalidOperationException
                var clipNode  = animData.Nodes[(int)target.Node];
                var nodeIndex = modelNodeIndexByName[clipNode.Name];
                target.Node = nodeIndex;
            }

            animation.Channels = newChannels.OutputItems;
            animation.Samplers = newSamplers.OutputItems;

            var clipBufferViews = clipAccessors
                                  .Select(a => a.BufferView)
                                  .Where(bv => bv.HasValue)
                                  .Select(bv => bv.Value)
                                  .Distinct()
                                  .Select(i => animData.BufferViews[i])
                                  .ToArray();

            var newBufferViews = modelData.BufferViews.Concat(clipBufferViews)
                                 .RemapFrom(animData.BufferViews);

            // Remap clip accessor indices
            foreach (var accessor in clipAccessors)
            {
                if (accessor.BufferView.HasValue)
                {
                    accessor.BufferView = newBufferViews.Remap(accessor.BufferView.Value);
                }
            }

            var clipBuffers = clipBufferViews
                              .Select(bv => bv.Buffer)
                              .Distinct()
                              .Select(i => animData.Buffers[i])
                              .ToArray();

            var newBuffers = modelData.Buffers.Concat(clipBuffers).RemapFrom(animData.Buffers);

            // Remap buffer view indices
            foreach (var bufferView in clipBufferViews)
            {
                bufferView.Buffer = newBuffers.Remap(bufferView.Buffer);
            }

            // TODO: Animation indices don't need to be remapped?
            modelData.Animations  = (modelData.Animations ?? Array.Empty <Animation>()).Append(animation).ToArray();
            modelData.Accessors   = newAccessors.OutputItems;
            modelData.BufferViews = newBufferViews.OutputItems;
            modelData.Buffers     = newBuffers.OutputItems;
        }
Example #4
0
        /// <summary>
        /// Creates a single buffer and a minimum number of buffer-views to pack all the accessors.
        /// </summary>
        private void PackAccessors(GltfFile modelFile, Stream newBufferStream, string outputBufferUri)
        {
            var fileData = modelFile.Data;

            var buffers     = fileData.Buffers;
            var bufferViews = fileData.BufferViews;

            // TODO: Check sparse accessors
            // Group accessor by optional target, component-stride and component byte-length
            // For each group, we need to create a single buffer-view.
            var accessorGroups = fileData
                                 .Accessors
                                 .Where(a => a.BufferView.HasValue)
                                 .GroupBy(a => (target: bufferViews[a.BufferView.Value].Target, stride: a.GetByteStride(bufferViews) /*, componentByteLength: a.GetComponentByteLength()*/))
                                 .ToDictionary(g => g.Key, g => g.ToArray());

            // NOTE: The following select sequence has a side-effect
            var newBufferViews = accessorGroups
                                 .Select((pair, newBufferViewIndex) =>
            {
                //var ((target, stride, componentByteLength), accessors) = pair;
                var((target, stride), accessors) = pair;

                var newBufferViewOffset = (int)newBufferStream.Position;

                //Debug.Assert(newBufferStream.GetComponentPadding(componentByteLength) == 0);

                foreach (var accessor in accessors)
                {
                    var componentByteLength = accessor.GetComponentByteLength();

                    var accessorPadding = newBufferStream.GetComponentPadding(componentByteLength);
                    newBufferStream.WriteByte(0, accessorPadding);

                    var newAccessorByteOffset = (int)(newBufferStream.Position - newBufferViewOffset);

                    Debug.Assert(accessor.BufferView.HasValue);
                    var bufferView = bufferViews[accessor.BufferView.Value];
                    var buffer     = buffers[bufferView.Buffer];
                    var data       = _bufferFileData[buffer];

                    var bufferOffset = bufferView.ByteOffset + accessor.ByteOffset;

                    var rowLength = accessor.GetComponentDimension() * componentByteLength;

                    for (int i = 0; i < accessor.Count; ++i)
                    {
                        Debug.Assert(newBufferStream.GetComponentPadding(componentByteLength) == 0);
                        newBufferStream.Write(data, bufferOffset + i * stride, rowLength);
                        Debug.Assert(newBufferStream.GetComponentPadding(componentByteLength) == 0);
                    }

                    // Patch the accessor.
                    accessor.ByteOffset = newAccessorByteOffset;
                    accessor.BufferView = newBufferViewIndex;
                }

                var newBufferView = new BufferView
                {
                    Target     = target,
                    ByteOffset = newBufferViewOffset,
                    ByteStride = target == BufferView.TargetEnum.ARRAY_BUFFER ? stride : (int?)null,
                    ByteLength = (int)(newBufferStream.Position - newBufferViewOffset)
                };

                return(newBufferView);
            })
                                 .ToArray();

            var newBuffer = new Buffer
            {
                ByteLength = (int)newBufferStream.Length,
                Uri        = outputBufferUri
            };

            fileData.BufferViews = newBufferViews;
            fileData.Buffers     = new[] { newBuffer };
        }