3131#include " Assets/RadientMeshAssetManager.hpp"
3232#include " Assets/RadientMeshVertexSource.hpp"
3333#include " Assets/RadientMeshViewSource.hpp"
34+ #include " Assets/RadientMorphTargetData.hpp"
3435#include " Assets/RadientTextureAssetManager.hpp"
3536#include " Errors.hpp"
3637#include " GLTFBuilder.hpp"
4647#include " TinyGltfModelView.hpp"
4748
4849#include < algorithm>
50+ #include < cmath>
51+ #include < cstring>
52+ #include < limits>
4953#include < memory>
5054#include < string>
5155#include < unordered_map>
@@ -194,9 +198,10 @@ struct PlannedIndexData
194198
195199struct PlannedPrimitive
196200{
197- Uint32 VertexDataIndex = ~0u ;
198- Uint32 IndexDataIndex = ~0u ;
199- int MaterialId = -1 ;
201+ Uint32 VertexDataIndex = ~0u ;
202+ Uint32 IndexDataIndex = ~0u ;
203+ Uint32 SourcePrimitiveIndex = ~0u ;
204+ int MaterialId = -1 ;
200205};
201206
202207struct PlannedMesh
@@ -343,10 +348,11 @@ class RadientGLTFGeometryPlan
343348 if (RADIENT_FAILED (Status))
344349 return Status;
345350
346- PlannedPrimitive& Primitive = Mesh.Primitives .emplace_back ();
347- Primitive.VertexDataIndex = VertexDataIndex;
348- Primitive.IndexDataIndex = IndexDataIndex;
349- Primitive.MaterialId = GltfPrimitive.GetMaterialId ();
351+ PlannedPrimitive& Primitive = Mesh.Primitives .emplace_back ();
352+ Primitive.VertexDataIndex = VertexDataIndex;
353+ Primitive.IndexDataIndex = IndexDataIndex;
354+ Primitive.SourcePrimitiveIndex = static_cast <Uint32>(PrimitiveIndex);
355+ Primitive.MaterialId = GltfPrimitive.GetMaterialId ();
350356 }
351357
352358 return RADIENT_STATUS_OK ;
@@ -433,6 +439,190 @@ class RadientGLTFGeometryPlan
433439 std::vector<bool > m_ScannedMeshes;
434440};
435441
442+ struct MorphTargetAttributeBuildData
443+ {
444+ std::string Semantic;
445+ Uint32 ComponentCount = 0 ;
446+ std::vector<Float32> Deltas;
447+ };
448+
449+ struct MorphTargetBuildData
450+ {
451+ std::string Name;
452+ Float32 DefaultWeight = 0 .f;
453+ std::vector<MorphTargetAttributeBuildData> Attributes;
454+ std::vector<RadientMorphTargetAttributeDesc> AttributeDescs;
455+ std::vector<RadientMorphTargetAttributeCreateInfo> AttributeCreateInfos;
456+ };
457+
458+ std::unique_ptr<RadientMorphTargetData> BuildMorphTargetData (const GLTF ::Mesh& Mesh)
459+ {
460+ if (Mesh.Primitives .empty ())
461+ return nullptr ;
462+
463+ const size_t TargetCount = Mesh.Primitives .front ().MorphTargets .size ();
464+ Uint32 VertexCount = 0 ;
465+ for (size_t PrimitiveIndex = 0 ; PrimitiveIndex < Mesh.Primitives .size (); ++PrimitiveIndex)
466+ {
467+ const GLTF ::Primitive& Primitive = Mesh.Primitives [PrimitiveIndex];
468+ if (Primitive.MorphTargets .size () != TargetCount)
469+ {
470+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
471+ " ' because primitive " , PrimitiveIndex, " has " , Primitive.MorphTargets .size (),
472+ " targets, while primitive 0 has " , TargetCount);
473+ return nullptr ;
474+ }
475+ if (Primitive.VertexCount > (std::numeric_limits<Uint32>::max)() - VertexCount)
476+ {
477+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
478+ " ' because its combined vertex count exceeds the supported range" );
479+ return nullptr ;
480+ }
481+ VertexCount += Primitive.VertexCount ;
482+ }
483+ if (TargetCount == 0 )
484+ return nullptr ;
485+ if (TargetCount > (std::numeric_limits<Uint32>::max)())
486+ {
487+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
488+ " ' because the target count exceeds the supported range" );
489+ return nullptr ;
490+ }
491+
492+ if (!Mesh.Weights .empty () && Mesh.Weights .size () != TargetCount)
493+ {
494+ LOG_WARNING_MESSAGE (" GLTF mesh '" , Mesh.Name , " ' provides " , Mesh.Weights .size (),
495+ " default morph weights for " , TargetCount,
496+ " targets; missing defaults will be zero and extra defaults will be ignored" );
497+ }
498+
499+ // Radient uses one logical vertex domain for each mesh. Concatenate the
500+ // primitive streams in stored primitive order and leave missing attribute
501+ // regions initialized to zero.
502+ try
503+ {
504+ std::vector<MorphTargetBuildData> Targets (TargetCount);
505+ for (size_t TargetIndex = 0 ; TargetIndex < TargetCount; ++TargetIndex)
506+ {
507+ MorphTargetBuildData& Target = Targets[TargetIndex];
508+ if (TargetIndex < Mesh.MorphTargetNames .size ())
509+ Target.Name = Mesh.MorphTargetNames [TargetIndex];
510+ if (TargetIndex < Mesh.Weights .size ())
511+ {
512+ if (!std::isfinite (Mesh.Weights [TargetIndex]))
513+ {
514+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
515+ " ' because default weight " , TargetIndex, " is not finite" );
516+ return nullptr ;
517+ }
518+ Target.DefaultWeight = Mesh.Weights [TargetIndex];
519+ }
520+
521+ for (const GLTF ::Primitive& Primitive : Mesh.Primitives )
522+ {
523+ for (const GLTF ::MorphTargetAttribute& SourceAttribute : Primitive.MorphTargets [TargetIndex].Attributes )
524+ {
525+ const auto ExistingAttribute = std::find_if (
526+ Target.Attributes .begin (), Target.Attributes .end (),
527+ [&SourceAttribute](const MorphTargetAttributeBuildData& Attribute) {
528+ return Attribute.Semantic == SourceAttribute.Semantic ;
529+ });
530+ if (ExistingAttribute != Target.Attributes .end ())
531+ {
532+ if (ExistingAttribute->ComponentCount != SourceAttribute.NumComponents )
533+ {
534+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
535+ " ' because target " , TargetIndex, " attribute '" ,
536+ SourceAttribute.Semantic ,
537+ " ' has inconsistent component counts between primitives" );
538+ return nullptr ;
539+ }
540+ continue ;
541+ }
542+
543+ const bool IsStandardSemantic =
544+ SourceAttribute.Semantic == RadientMorphTargetPositionSemantic ||
545+ SourceAttribute.Semantic == RadientMorphTargetNormalSemantic ||
546+ SourceAttribute.Semantic == RadientMorphTargetTangentSemantic;
547+ if (SourceAttribute.NumComponents == 0 || SourceAttribute.NumComponents > 4 ||
548+ (IsStandardSemantic && SourceAttribute.NumComponents != 3 ) ||
549+ VertexCount > (std::numeric_limits<size_t >::max)() / SourceAttribute.NumComponents )
550+ {
551+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
552+ " ' because target " , TargetIndex, " attribute '" ,
553+ SourceAttribute.Semantic , " ' has an unsupported size" );
554+ return nullptr ;
555+ }
556+
557+ MorphTargetAttributeBuildData& Attribute = Target.Attributes .emplace_back ();
558+ Attribute.Semantic = SourceAttribute.Semantic ;
559+ Attribute.ComponentCount = SourceAttribute.NumComponents ;
560+ Attribute.Deltas .resize (size_t {VertexCount} * Attribute.ComponentCount , 0 .f );
561+ }
562+ }
563+
564+ Uint32 FirstVertex = 0 ;
565+ for (const GLTF ::Primitive& Primitive : Mesh.Primitives )
566+ {
567+ const GLTF ::MorphTarget& SourceTarget = Primitive.MorphTargets [TargetIndex];
568+ for (MorphTargetAttributeBuildData& Attribute : Target.Attributes )
569+ {
570+ const GLTF ::MorphTargetAttribute* const pSourceAttribute =
571+ SourceTarget.FindAttribute (Attribute.Semantic .c_str ());
572+ if (pSourceAttribute == nullptr )
573+ continue ;
574+
575+ const size_t ValueCount = size_t {Primitive.VertexCount } * Attribute.ComponentCount ;
576+ if (pSourceAttribute->FirstValue > SourceTarget.Values .size () ||
577+ ValueCount > SourceTarget.Values .size () - pSourceAttribute->FirstValue )
578+ {
579+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
580+ " ' because target " , TargetIndex, " attribute '" ,
581+ Attribute.Semantic , " ' has an invalid value range" );
582+ return nullptr ;
583+ }
584+
585+ std::memcpy (Attribute.Deltas .data () + size_t {FirstVertex} * Attribute.ComponentCount ,
586+ SourceTarget.GetAttributeData (*pSourceAttribute),
587+ ValueCount * sizeof (Float32));
588+ }
589+ FirstVertex += Primitive.VertexCount ;
590+ }
591+ }
592+
593+ std::vector<RadientMorphTargetCreateInfo> TargetCreateInfos (TargetCount);
594+ for (size_t TargetIndex = 0 ; TargetIndex < TargetCount; ++TargetIndex)
595+ {
596+ MorphTargetBuildData& Target = Targets[TargetIndex];
597+ Target.AttributeDescs .resize (Target.Attributes .size ());
598+ Target.AttributeCreateInfos .resize (Target.Attributes .size ());
599+ for (size_t AttributeIndex = 0 ; AttributeIndex < Target.Attributes .size (); ++AttributeIndex)
600+ {
601+ const MorphTargetAttributeBuildData& Attribute = Target.Attributes [AttributeIndex];
602+ Target.AttributeDescs [AttributeIndex].Semantic = Attribute.Semantic .c_str ();
603+ Target.AttributeDescs [AttributeIndex].ComponentCount = Attribute.ComponentCount ;
604+ Target.AttributeCreateInfos [AttributeIndex].pDeltas = Attribute.Deltas .data ();
605+ }
606+
607+ RadientMorphTargetCreateInfo& TargetCI = TargetCreateInfos[TargetIndex];
608+ TargetCI.Desc .Name = Target.Name .c_str ();
609+ TargetCI.Desc .pAttributes = Target.AttributeDescs .empty () ? nullptr : Target.AttributeDescs .data ();
610+ TargetCI.Desc .AttributeCount = static_cast <Uint32>(Target.AttributeDescs .size ());
611+ TargetCI.Desc .DefaultWeight = Target.DefaultWeight ;
612+ TargetCI.pAttributeData = Target.AttributeCreateInfos .empty () ? nullptr : Target.AttributeCreateInfos .data ();
613+ }
614+
615+ return std::make_unique<RadientMorphTargetData>(
616+ TargetCreateInfos.data (), static_cast <Uint32>(TargetCreateInfos.size ()), VertexCount);
617+ }
618+ catch (const std::exception& Error)
619+ {
620+ LOG_WARNING_MESSAGE (" Ignoring morph targets for GLTF mesh '" , Mesh.Name ,
621+ " ' because their data could not be packed: " , Error.what ());
622+ return nullptr ;
623+ }
624+ }
625+
436626class RadientMeshLoader
437627{
438628public:
@@ -494,6 +684,7 @@ class RadientMeshLoader
494684 Primitives.reserve (pPlannedMesh->Primitives .size ());
495685 GeometryIndices.reserve (pPlannedMesh->Primitives .size ());
496686 pNewMesh->Primitives .reserve (pPlannedMesh->Primitives .size ());
687+ bool MorphTargetsLoaded = true ;
497688
498689 std::vector<const PlannedPrimitive*> SortedPrimitives;
499690 SortedPrimitives.reserve (pPlannedMesh->Primitives .size ());
@@ -548,13 +739,22 @@ class RadientMeshLoader
548739 static_cast <Uint32>(PlannedPrimitive.MaterialId ) :
549740 0u ;
550741
551- pNewMesh->Primitives .emplace_back (0u ,
552- pIndexData->IndexCount ,
553- 0u ,
554- pVertexData->VertexCount ,
555- MaterialSlot,
556- pVertexData->BBMin ,
557- pVertexData->BBMax );
742+ GLTF ::Primitive& NewPrimitive = pNewMesh->Primitives .emplace_back (0u ,
743+ pIndexData->IndexCount ,
744+ 0u ,
745+ pVertexData->VertexCount ,
746+ MaterialSlot,
747+ pVertexData->BBMin ,
748+ pVertexData->BBMax );
749+
750+ const auto GltfPrimitive = GltfMesh.GetPrimitive (PlannedPrimitive.SourcePrimitiveIndex );
751+ if (!GLTF::LoadMorphTargets (GltfModel, GltfPrimitive, pVertexData->VertexCount , NewPrimitive))
752+ {
753+ LOG_WARNING_MESSAGE (" Failed to load morph targets for GLTF mesh '" , pNewMesh->Name ,
754+ " ' primitive " , PlannedPrimitive.SourcePrimitiveIndex ,
755+ " ; loading the mesh without morph targets" );
756+ MorphTargetsLoaded = false ;
757+ }
558758
559759 RadientMeshPrimitiveCreateInfo& Primitive = Primitives.emplace_back ();
560760 Primitive.FirstIndex = 0 ;
@@ -569,6 +769,15 @@ class RadientMeshLoader
569769
570770 pNewMesh->UpdateBoundingBox ();
571771
772+ std::unique_ptr<RadientMorphTargetData> pMorphTargetData;
773+ if (MorphTargetsLoaded)
774+ pMorphTargetData = BuildMorphTargetData (*pNewMesh);
775+ else
776+ {
777+ for (GLTF ::Primitive& Primitive : pNewMesh->Primitives )
778+ Primitive.MorphTargets .clear ();
779+ }
780+
572781 RadientMeshViewCreateInfo ViewCI;
573782 ViewCI.pPrimitives = Primitives.data ();
574783 ViewCI.PrimitiveCount = static_cast <Uint32>(Primitives.size ());
@@ -580,7 +789,8 @@ class RadientMeshLoader
580789 MeshGeometryData.data (),
581790 static_cast <Uint32>(MeshGeometryData.size ()),
582791 ViewCI,
583- pMeshAsset.GetAddressOfEmpty ());
792+ pMeshAsset.GetAddressOfEmpty (),
793+ std::move (pMorphTargetData));
584794 if (RADIENT_FAILED (Status) || pMeshAsset == nullptr )
585795 {
586796 m_Status = RADIENT_FAILED (Status) ? Status : RADIENT_STATUS_FAILED ;
0 commit comments