|
|
@@ -11,221 +11,11 @@ using MESHBUILDER = SharpGLTF.Geometry.IMeshBuilder<SharpGLTF.Materials.Material
|
|
|
namespace SharpGLTF.Scenes
|
|
|
{
|
|
|
/// <summary>
|
|
|
- /// Helper class to create a Schema2.Scene from one or multiple <see cref="SceneBuilder"/> instances.
|
|
|
+ /// Defines configurable options for converting <see cref="SceneBuilder"/> to <see cref="ModelRoot"/>
|
|
|
/// </summary>
|
|
|
- class Schema2SceneBuilder
|
|
|
- {
|
|
|
- #region data
|
|
|
-
|
|
|
- private readonly Dictionary<Materials.MaterialBuilder, Material> _Materials = new Dictionary<Materials.MaterialBuilder, Material>();
|
|
|
-
|
|
|
- private readonly Dictionary<MESHBUILDER, Mesh> _Meshes = new Dictionary<MESHBUILDER, Mesh>();
|
|
|
-
|
|
|
- private readonly Dictionary<NodeBuilder, Node> _Nodes = new Dictionary<NodeBuilder, Node>();
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region settings
|
|
|
- public int GpuMeshInstancingMinCount { get; set; }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region API
|
|
|
-
|
|
|
- public Mesh GetMesh(MESHBUILDER key) { return key == null ? null : _Meshes.TryGetValue(key, out Mesh val) ? val : null; }
|
|
|
-
|
|
|
- public Node GetNode(NodeBuilder key) { return key == null ? null : _Nodes.TryGetValue(key, out Node val) ? val : null; }
|
|
|
-
|
|
|
- public static bool HasContent(Node node, bool checkTransform = true)
|
|
|
- {
|
|
|
- if (checkTransform && node.LocalMatrix != Matrix4x4.Identity) return true;
|
|
|
-
|
|
|
- if (node.VisualChildren.Any()) return true;
|
|
|
-
|
|
|
- if (node.Mesh != null) return true;
|
|
|
- if (node.Skin != null) return true;
|
|
|
- if (node.Camera != null) return true;
|
|
|
- if (node.PunctualLight != null) return true;
|
|
|
- if (node.GetGpuInstancing() != null) return true;
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- public void AddGeometryResources(ModelRoot root, IEnumerable<SceneBuilder> srcScenes, SceneBuilderSchema2Settings settings)
|
|
|
- {
|
|
|
- // gather all unique MeshBuilders
|
|
|
-
|
|
|
- var srcMeshes = srcScenes
|
|
|
- .SelectMany(item => item.Instances)
|
|
|
- .Select(item => item.Content?.GetGeometryAsset())
|
|
|
- .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
|
|
|
- .Distinct()
|
|
|
- .ToArray();
|
|
|
-
|
|
|
- // gather all unique MaterialBuilders
|
|
|
-
|
|
|
- var materialGroups = srcMeshes
|
|
|
- .SelectMany(item => item.Primitives)
|
|
|
- .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
|
|
|
- .Select(item => item.Material)
|
|
|
- .Distinct()
|
|
|
- .ToList()
|
|
|
- // group by equal content, to reduce material splitting whenever possible.
|
|
|
- .GroupBy(item => item, Materials.MaterialBuilder.ContentComparer);
|
|
|
-
|
|
|
- // create a Schema2.Material for every MaterialBuilder.
|
|
|
-
|
|
|
- foreach (var mg in materialGroups)
|
|
|
- {
|
|
|
- var val = root.CreateMaterial(mg.Key);
|
|
|
- foreach (var key in mg) _Materials[key] = val;
|
|
|
- }
|
|
|
-
|
|
|
- // create a Schema2.Mesh for every MeshBuilder.
|
|
|
-
|
|
|
- var dstMeshes = root.CreateMeshes(mat => _Materials[mat], settings, srcMeshes);
|
|
|
-
|
|
|
- for (int i = 0; i < srcMeshes.Length; ++i)
|
|
|
- {
|
|
|
- _Meshes[srcMeshes[i]] = dstMeshes[i];
|
|
|
- }
|
|
|
-
|
|
|
- // TODO: here we could check that every dstMesh has been correctly created.
|
|
|
- }
|
|
|
-
|
|
|
- private void AddArmatureResources(IEnumerable<SceneBuilder> srcScenes, Func<Node> nodeFactory)
|
|
|
- {
|
|
|
- // ALIGNMENT ISSUE:
|
|
|
- // the toolkit builder is designed in a way that every instance can reuse the same node many times, even from different scenes.
|
|
|
- // so the only way to handle this is to forcefully recreate all the nodes on every scene.
|
|
|
-
|
|
|
- // gather all NodeBuilder unique armatures
|
|
|
-
|
|
|
- var armatures = srcScenes
|
|
|
- .SelectMany(item => item.Instances)
|
|
|
- .Select(item => item.Content?.GetArmatureRoot())
|
|
|
- .Where(item => item != null)
|
|
|
- .Select(item => item.Root)
|
|
|
- .Distinct()
|
|
|
- .ToList();
|
|
|
-
|
|
|
- // create Schema2.Node trees for every armature
|
|
|
-
|
|
|
- foreach (var armature in armatures)
|
|
|
- {
|
|
|
- CreateArmature(armature, nodeFactory);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void CreateArmature(NodeBuilder srcNode, Func<Node> nodeFactory)
|
|
|
- {
|
|
|
- var dstNode = nodeFactory();
|
|
|
-
|
|
|
- srcNode.TryCopyNameAndExtrasTo(dstNode);
|
|
|
-
|
|
|
- _Nodes[srcNode] = dstNode;
|
|
|
-
|
|
|
- if (srcNode.HasAnimations)
|
|
|
- {
|
|
|
- dstNode.LocalTransform = srcNode.LocalTransform.GetDecomposed();
|
|
|
-
|
|
|
- // Copies all the animations to the target node.
|
|
|
- if (srcNode.Scale != null) foreach (var t in srcNode.Scale.Tracks) dstNode.WithScaleAnimation(t.Key, t.Value);
|
|
|
- if (srcNode.Rotation != null) foreach (var t in srcNode.Rotation.Tracks) dstNode.WithRotationAnimation(t.Key, t.Value);
|
|
|
- if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- dstNode.LocalTransform = srcNode.LocalTransform;
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var c in srcNode.VisualChildren) CreateArmature(c, () => dstNode.CreateNode());
|
|
|
- }
|
|
|
-
|
|
|
- public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<Transforms.SparseWeight8> animation)
|
|
|
- {
|
|
|
- Guard.NotNull(dstNode, nameof(dstNode));
|
|
|
- Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
|
|
|
-
|
|
|
- if (animation == null) return;
|
|
|
-
|
|
|
- var dstMesh = dstNode.Mesh;
|
|
|
-
|
|
|
- dstMesh.SetMorphWeights(animation.Value);
|
|
|
-
|
|
|
- foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
|
|
|
- }
|
|
|
-
|
|
|
- public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<ArraySegment<float>> animation)
|
|
|
- {
|
|
|
- Guard.NotNull(dstNode, nameof(dstNode));
|
|
|
- Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
|
|
|
-
|
|
|
- if (animation == null) return;
|
|
|
-
|
|
|
- var dstMesh = dstNode.Mesh;
|
|
|
-
|
|
|
- dstMesh.SetMorphWeights(animation.Value);
|
|
|
-
|
|
|
- foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
|
|
|
- }
|
|
|
-
|
|
|
- public void AddScene(Scene dstScene, SceneBuilder srcScene)
|
|
|
- {
|
|
|
- _Nodes.Clear();
|
|
|
- AddArmatureResources(new[] { srcScene }, () => dstScene.CreateNode());
|
|
|
-
|
|
|
- // gather single operators (RigidTransformer and SkinnedTransformer)
|
|
|
-
|
|
|
- var srcSingleOperators = srcScene
|
|
|
- .Instances
|
|
|
- .Select(item => item.Content)
|
|
|
- .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
|
|
|
- .OfType<IOperator<Scene>>();
|
|
|
-
|
|
|
- // gather multi operators (Fixed Transformer)
|
|
|
-
|
|
|
- var srcChildren = srcScene
|
|
|
- .Instances
|
|
|
- .Select(item => item.Content)
|
|
|
- .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
|
|
|
- .OfType<FixedTransformer>();
|
|
|
-
|
|
|
- var srcMultiOperators = _MeshInstancing.CreateFrom(srcChildren, this.GpuMeshInstancingMinCount);
|
|
|
-
|
|
|
- // apply operators
|
|
|
-
|
|
|
- var srcOperators = srcSingleOperators.Concat(srcMultiOperators);
|
|
|
-
|
|
|
- foreach (var op in srcOperators)
|
|
|
- {
|
|
|
- op.ApplyTo(dstScene, this);
|
|
|
- }
|
|
|
-
|
|
|
- #if DEBUG
|
|
|
- srcScene._VerifyConversion(dstScene);
|
|
|
- #endif
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region nested types
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Represents an object that can operate on a target object.
|
|
|
- /// </summary>
|
|
|
- /// <typeparam name="T">
|
|
|
- /// The target type.
|
|
|
- /// This is usually <see cref="Scene"/> or <see cref="Node"/>.
|
|
|
- /// </typeparam>
|
|
|
- public interface IOperator<T>
|
|
|
- {
|
|
|
- void ApplyTo(T target, Schema2SceneBuilder context);
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
- }
|
|
|
-
|
|
|
+ /// <remarks>
|
|
|
+ /// Used by <see cref="SceneBuilder.ToGltf2(SceneBuilderSchema2Settings)"/>
|
|
|
+ /// </remarks>
|
|
|
public struct SceneBuilderSchema2Settings
|
|
|
{
|
|
|
public static SceneBuilderSchema2Settings Default => new SceneBuilderSchema2Settings
|
|
|
@@ -242,11 +32,34 @@ namespace SharpGLTF.Scenes
|
|
|
GpuMeshInstancingMinCount = 3
|
|
|
};
|
|
|
|
|
|
- public bool UseStridedBuffers;
|
|
|
+ /// <summary>
|
|
|
+ /// When true, meshes will be created using strided vertices when possible.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// this option is not taken into account by meshes with morph targets.
|
|
|
+ /// </remarks>
|
|
|
+ public bool UseStridedBuffers { get; set; }
|
|
|
|
|
|
- public bool CompactVertexWeights;
|
|
|
+ /// <summary>
|
|
|
+ /// if meshes have Skin Weights, defines the output vertex element format:<br/>
|
|
|
+ /// - True: Short<br/>
|
|
|
+ /// - False: Float<br/>
|
|
|
+ /// </summary>
|
|
|
+ public bool CompactVertexWeights { get; set; }
|
|
|
|
|
|
- public int GpuMeshInstancingMinCount;
|
|
|
+ /// <summary>
|
|
|
+ /// determines the mínimum number mesh instances required to enable Gpu mesh instancing.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// Set to <see cref="int.MaxValue"/> to disable gpu instancing.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// If set to a small value like 10, any mesh instance collection smaller than this will be instantiated
|
|
|
+ /// using individual nodes, otherwise it will use Gpu Instancing extension.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ public int GpuMeshInstancingMinCount { get; set; }
|
|
|
}
|
|
|
|
|
|
public partial class SceneBuilder : IConvertibleToGltf2
|
|
|
@@ -305,10 +118,42 @@ namespace SharpGLTF.Scenes
|
|
|
|
|
|
#region from Schema2 to SceneBuilder
|
|
|
|
|
|
+ public static SceneBuilder[] CreateFrom(ModelRoot model)
|
|
|
+ {
|
|
|
+ return model == null
|
|
|
+ ? Array.Empty<SceneBuilder>()
|
|
|
+ : CreateFrom(model.LogicalScenes).ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
public static SceneBuilder CreateFrom(Scene srcScene)
|
|
|
{
|
|
|
if (srcScene == null) return null;
|
|
|
|
|
|
+ // gather shared mesh instances
|
|
|
+
|
|
|
+ var dstMeshIntances = _GatherMeshInstances(Node.Flatten(srcScene));
|
|
|
+
|
|
|
+ return _CreateFrom(srcScene, dstMeshIntances);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IEnumerable<SceneBuilder> CreateFrom(IEnumerable<Scene> srcScenes)
|
|
|
+ {
|
|
|
+ if (srcScenes == null) yield break;
|
|
|
+
|
|
|
+ // gather shared mesh instances
|
|
|
+
|
|
|
+ var dstMeshIntances = _GatherMeshInstances(srcScenes.Distinct().SelectMany(s => Node.Flatten(s)));
|
|
|
+
|
|
|
+ // process each scene
|
|
|
+
|
|
|
+ foreach (var srcScene in srcScenes)
|
|
|
+ {
|
|
|
+ yield return _CreateFrom(srcScene, dstMeshIntances);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static SceneBuilder _CreateFrom(Scene srcScene, IReadOnlyDictionary<Node, MESHBUILDER> dstInstances)
|
|
|
+ {
|
|
|
// Process armatures
|
|
|
|
|
|
var dstNodes = new Dictionary<Node, NodeBuilder>();
|
|
|
@@ -325,12 +170,7 @@ namespace SharpGLTF.Scenes
|
|
|
|
|
|
dstScene.SetNameAndExtrasFrom(srcScene);
|
|
|
|
|
|
- // process mesh instances
|
|
|
- var srcMeshInstances = Node.Flatten(srcScene)
|
|
|
- .Where(item => item.Mesh != null)
|
|
|
- .ToList();
|
|
|
-
|
|
|
- _AddMeshInstances(dstScene, dstNodes, srcMeshInstances);
|
|
|
+ _AddMeshInstances(dstScene, Node.Flatten(srcScene), dstNodes, dstInstances);
|
|
|
|
|
|
// process cameras
|
|
|
var srcCameraInstances = Node.Flatten(srcScene)
|
|
|
@@ -352,53 +192,74 @@ namespace SharpGLTF.Scenes
|
|
|
return dstScene;
|
|
|
}
|
|
|
|
|
|
- private static void _AddMeshInstances(SceneBuilder dstScene, IReadOnlyDictionary<Node, NodeBuilder> dstNodes, IReadOnlyList<Node> srcInstances)
|
|
|
+ private static IReadOnlyDictionary<Node, MESHBUILDER> _GatherMeshInstances(IEnumerable<Node> srcNodes)
|
|
|
{
|
|
|
- var dstMeshes = srcInstances
|
|
|
- .Select(item => item.Mesh)
|
|
|
- .Distinct()
|
|
|
- .ToDictionary(item => item, item => item.ToMeshBuilder());
|
|
|
+ // filter all the nodes with meshes
|
|
|
|
|
|
- foreach (var srcInstance in srcInstances)
|
|
|
+ var srcInstances = srcNodes
|
|
|
+ .Where(item => item.Mesh != null);
|
|
|
+
|
|
|
+ // create a dictionary of shared Mesh => MeshBuilder pairs.
|
|
|
+
|
|
|
+ var srcMeshes = srcInstances
|
|
|
+ .Select(item => item.Mesh)
|
|
|
+ .Distinct()
|
|
|
+ .ToDictionary(item => item, item => item.ToMeshBuilder());
|
|
|
+
|
|
|
+ // return a Node => MeshBuilder dictionary.
|
|
|
+
|
|
|
+ return srcInstances
|
|
|
+ .ToDictionary(item => item, item => srcMeshes[item.Mesh]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void _AddMeshInstances(SceneBuilder dstScene, IEnumerable<Node> srcNodes, IReadOnlyDictionary<Node, NodeBuilder> nodesDict, IReadOnlyDictionary<Node, MESHBUILDER> meshesDict)
|
|
|
+ {
|
|
|
+ foreach (var srcNode in srcNodes)
|
|
|
{
|
|
|
- var dstMesh = dstMeshes[srcInstance.Mesh];
|
|
|
+ if (!meshesDict.TryGetValue(srcNode, out var dstMesh)) continue; // nothing to do.
|
|
|
|
|
|
- if (srcInstance.Skin == null)
|
|
|
+ if (srcNode.Skin == null)
|
|
|
{
|
|
|
- var dstNode = dstNodes[srcInstance];
|
|
|
+ // rigid mesh instance
|
|
|
+
|
|
|
+ var dstNode = nodesDict[srcNode];
|
|
|
|
|
|
- var gpuInstancing = srcInstance.GetGpuInstancing();
|
|
|
+ var gpuInstancing = srcNode.GetGpuInstancing();
|
|
|
|
|
|
- if (gpuInstancing != null)
|
|
|
+ if (gpuInstancing == null)
|
|
|
{
|
|
|
- foreach (var xinst in gpuInstancing.LocalTransforms)
|
|
|
- {
|
|
|
- var dstInst = dstScene.AddRigidMesh(dstMesh, dstNode, xinst);
|
|
|
+ var dstInstance = dstScene.AddRigidMesh(dstMesh, dstNode);
|
|
|
|
|
|
- // if we add morphing the the mesh, all meshes would morph simultaneously??
|
|
|
- _CopyMorphingAnimation(dstInst, srcInstance);
|
|
|
- }
|
|
|
+ _CopyMorphingAnimation(dstInstance, srcNode);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- var dstInst = dstScene.AddRigidMesh(dstMesh, dstNode);
|
|
|
+ // use gpu instancing extension
|
|
|
+
|
|
|
+ foreach (var xinst in gpuInstancing.LocalTransforms)
|
|
|
+ {
|
|
|
+ var dstInstance = dstScene.AddRigidMesh(dstMesh, dstNode, xinst);
|
|
|
|
|
|
- _CopyMorphingAnimation(dstInst, srcInstance);
|
|
|
+ // if we add morphing to the mesh, all meshes would morph simultaneously??
|
|
|
+ _CopyMorphingAnimation(dstInstance, srcNode);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- var joints = new (NodeBuilder, Matrix4x4)[srcInstance.Skin.JointsCount];
|
|
|
+ // skinned mesh instance
|
|
|
+
|
|
|
+ var joints = new (NodeBuilder, Matrix4x4)[srcNode.Skin.JointsCount];
|
|
|
|
|
|
for (int i = 0; i < joints.Length; ++i)
|
|
|
{
|
|
|
- var (j, ibm) = srcInstance.Skin.GetJoint(i);
|
|
|
- joints[i] = (dstNodes[j], ibm);
|
|
|
+ var (j, ibm) = srcNode.Skin.GetJoint(i);
|
|
|
+ joints[i] = (nodesDict[j], ibm);
|
|
|
}
|
|
|
|
|
|
var dstInst = dstScene.AddSkinnedMesh(dstMesh, joints);
|
|
|
|
|
|
- _CopyMorphingAnimation(dstInst, srcInstance);
|
|
|
+ _CopyMorphingAnimation(dstInst, srcNode);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -605,4 +466,220 @@ namespace SharpGLTF.Scenes
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Helper class to create a Schema2.Scene from one or multiple <see cref="SceneBuilder"/> instances.
|
|
|
+ /// </summary>
|
|
|
+ class Schema2SceneBuilder
|
|
|
+ {
|
|
|
+ #region data
|
|
|
+
|
|
|
+ private readonly Dictionary<Materials.MaterialBuilder, Material> _Materials = new Dictionary<Materials.MaterialBuilder, Material>();
|
|
|
+
|
|
|
+ private readonly Dictionary<MESHBUILDER, Mesh> _Meshes = new Dictionary<MESHBUILDER, Mesh>();
|
|
|
+
|
|
|
+ private readonly Dictionary<NodeBuilder, Node> _Nodes = new Dictionary<NodeBuilder, Node>();
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region settings
|
|
|
+ public int GpuMeshInstancingMinCount { get; set; }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region API
|
|
|
+
|
|
|
+ public Mesh GetMesh(MESHBUILDER key) { return key == null ? null : _Meshes.TryGetValue(key, out Mesh val) ? val : null; }
|
|
|
+
|
|
|
+ public Node GetNode(NodeBuilder key) { return key == null ? null : _Nodes.TryGetValue(key, out Node val) ? val : null; }
|
|
|
+
|
|
|
+ public static bool HasContent(Node node, bool checkTransform = true)
|
|
|
+ {
|
|
|
+ if (checkTransform && node.LocalMatrix != Matrix4x4.Identity) return true;
|
|
|
+
|
|
|
+ if (node.VisualChildren.Any()) return true;
|
|
|
+
|
|
|
+ if (node.Mesh != null) return true;
|
|
|
+ if (node.Skin != null) return true;
|
|
|
+ if (node.Camera != null) return true;
|
|
|
+ if (node.PunctualLight != null) return true;
|
|
|
+ if (node.GetGpuInstancing() != null) return true;
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddGeometryResources(ModelRoot root, IEnumerable<SceneBuilder> srcScenes, SceneBuilderSchema2Settings settings)
|
|
|
+ {
|
|
|
+ // gather all unique MeshBuilders
|
|
|
+
|
|
|
+ var srcMeshes = srcScenes
|
|
|
+ .SelectMany(item => item.Instances)
|
|
|
+ .Select(item => item.Content?.GetGeometryAsset())
|
|
|
+ .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
|
|
|
+ .Distinct()
|
|
|
+ .ToArray();
|
|
|
+
|
|
|
+ // gather all unique MaterialBuilders
|
|
|
+
|
|
|
+ var materialGroups = srcMeshes
|
|
|
+ .SelectMany(item => item.Primitives)
|
|
|
+ .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item))
|
|
|
+ .Select(item => item.Material)
|
|
|
+ .Distinct()
|
|
|
+ .ToList()
|
|
|
+ // group by equal content, to reduce material splitting whenever possible.
|
|
|
+ .GroupBy(item => item, Materials.MaterialBuilder.ContentComparer);
|
|
|
+
|
|
|
+ // create a Schema2.Material for every MaterialBuilder.
|
|
|
+
|
|
|
+ foreach (var mg in materialGroups)
|
|
|
+ {
|
|
|
+ var val = root.CreateMaterial(mg.Key);
|
|
|
+ foreach (var key in mg) _Materials[key] = val;
|
|
|
+ }
|
|
|
+
|
|
|
+ // create a Schema2.Mesh for every MeshBuilder.
|
|
|
+
|
|
|
+ var dstMeshes = root.CreateMeshes(mat => _Materials[mat], settings, srcMeshes);
|
|
|
+
|
|
|
+ for (int i = 0; i < srcMeshes.Length; ++i)
|
|
|
+ {
|
|
|
+ _Meshes[srcMeshes[i]] = dstMeshes[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: here we could check that every dstMesh has been correctly created.
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AddArmatureResources(IEnumerable<SceneBuilder> srcScenes, Func<Node> nodeFactory)
|
|
|
+ {
|
|
|
+ // ALIGNMENT ISSUE:
|
|
|
+ // the toolkit builder is designed in a way that every instance can reuse the same node many times, even from different scenes.
|
|
|
+ // so the only way to handle this is to forcefully recreate all the nodes on every scene.
|
|
|
+
|
|
|
+ // gather all NodeBuilder unique armatures
|
|
|
+
|
|
|
+ var armatures = srcScenes
|
|
|
+ .SelectMany(item => item.Instances)
|
|
|
+ .Select(item => item.Content?.GetArmatureRoot())
|
|
|
+ .Where(item => item != null)
|
|
|
+ .Select(item => item.Root)
|
|
|
+ .Distinct()
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ // create Schema2.Node trees for every armature
|
|
|
+
|
|
|
+ foreach (var armature in armatures)
|
|
|
+ {
|
|
|
+ CreateArmature(armature, nodeFactory);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CreateArmature(NodeBuilder srcNode, Func<Node> nodeFactory)
|
|
|
+ {
|
|
|
+ var dstNode = nodeFactory();
|
|
|
+
|
|
|
+ srcNode.TryCopyNameAndExtrasTo(dstNode);
|
|
|
+
|
|
|
+ _Nodes[srcNode] = dstNode;
|
|
|
+
|
|
|
+ if (srcNode.HasAnimations)
|
|
|
+ {
|
|
|
+ dstNode.LocalTransform = srcNode.LocalTransform.GetDecomposed();
|
|
|
+
|
|
|
+ // Copies all the animations to the target node.
|
|
|
+ if (srcNode.Scale != null) foreach (var t in srcNode.Scale.Tracks) dstNode.WithScaleAnimation(t.Key, t.Value);
|
|
|
+ if (srcNode.Rotation != null) foreach (var t in srcNode.Rotation.Tracks) dstNode.WithRotationAnimation(t.Key, t.Value);
|
|
|
+ if (srcNode.Translation != null) foreach (var t in srcNode.Translation.Tracks) dstNode.WithTranslationAnimation(t.Key, t.Value);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ dstNode.LocalTransform = srcNode.LocalTransform;
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach (var c in srcNode.VisualChildren) CreateArmature(c, () => dstNode.CreateNode());
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<Transforms.SparseWeight8> animation)
|
|
|
+ {
|
|
|
+ Guard.NotNull(dstNode, nameof(dstNode));
|
|
|
+ Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
|
|
|
+
|
|
|
+ if (animation == null) return;
|
|
|
+
|
|
|
+ var dstMesh = dstNode.Mesh;
|
|
|
+
|
|
|
+ dstMesh.SetMorphWeights(animation.Value);
|
|
|
+
|
|
|
+ foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void SetMorphAnimation(Node dstNode, Animations.AnimatableProperty<ArraySegment<float>> animation)
|
|
|
+ {
|
|
|
+ Guard.NotNull(dstNode, nameof(dstNode));
|
|
|
+ Guard.NotNull(dstNode.Mesh, nameof(dstNode.Mesh), "call after IOperator.ApplyTo");
|
|
|
+
|
|
|
+ if (animation == null) return;
|
|
|
+
|
|
|
+ var dstMesh = dstNode.Mesh;
|
|
|
+
|
|
|
+ dstMesh.SetMorphWeights(animation.Value);
|
|
|
+
|
|
|
+ foreach (var t in animation.Tracks) dstNode.WithMorphingAnimation(t.Key, t.Value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddScene(Scene dstScene, SceneBuilder srcScene)
|
|
|
+ {
|
|
|
+ _Nodes.Clear();
|
|
|
+ AddArmatureResources(new[] { srcScene }, () => dstScene.CreateNode());
|
|
|
+
|
|
|
+ // gather single operators (RigidTransformer and SkinnedTransformer)
|
|
|
+
|
|
|
+ var srcSingleOperators = srcScene
|
|
|
+ .Instances
|
|
|
+ .Select(item => item.Content)
|
|
|
+ .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
|
|
|
+ .OfType<IOperator<Scene>>();
|
|
|
+
|
|
|
+ // gather multi operators (Fixed Transformer)
|
|
|
+
|
|
|
+ var srcChildren = srcScene
|
|
|
+ .Instances
|
|
|
+ .Select(item => item.Content)
|
|
|
+ .Where(item => !Geometry.MeshBuilderToolkit.IsEmpty(item.GetGeometryAsset()))
|
|
|
+ .OfType<FixedTransformer>();
|
|
|
+
|
|
|
+ var srcMultiOperators = _MeshInstancing.CreateFrom(srcChildren, this.GpuMeshInstancingMinCount);
|
|
|
+
|
|
|
+ // apply operators
|
|
|
+
|
|
|
+ var srcOperators = srcSingleOperators.Concat(srcMultiOperators);
|
|
|
+
|
|
|
+ foreach (var op in srcOperators)
|
|
|
+ {
|
|
|
+ op.ApplyTo(dstScene, this);
|
|
|
+ }
|
|
|
+
|
|
|
+ #if DEBUG
|
|
|
+ srcScene._VerifyConversion(dstScene);
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region nested types
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Represents an object that can operate on a target object.
|
|
|
+ /// </summary>
|
|
|
+ /// <typeparam name="T">
|
|
|
+ /// The target type.
|
|
|
+ /// This is usually <see cref="Scene"/> or <see cref="Node"/>.
|
|
|
+ /// </typeparam>
|
|
|
+ public interface IOperator<T>
|
|
|
+ {
|
|
|
+ void ApplyTo(T target, Schema2SceneBuilder context);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
}
|