FBX -> Scene, part 2. Code changes, file renames (#1704)

* First pass FBX -> Scene File conversion.
This is the simple pass, minimizing code changes and focused on comments.

Signed-off-by: stankowi <4838196+AMZN-stankowi@users.noreply.github.com>

* Step 1 of part 2 of the FBX -> Scene rename

Signed-off-by: stankowi <4838196+AMZN-stankowi@users.noreply.github.com>

* Renaming FbxSceneBuilder folder to SceneBuilder

Signed-off-by: stankowi <4838196+AMZN-stankowi@users.noreply.github.com>

* Renamed files

Signed-off-by: stankowi <4838196+AMZN-stankowi@users.noreply.github.com>

* More FBX -> Scene

Signed-off-by: stankowi <4838196+AMZN-stankowi@users.noreply.github.com>
This commit is contained in:
AMZN-stankowi
2021-07-01 15:46:55 -07:00
committed by GitHub
parent 328a0f09a5
commit 73522e90c9
83 changed files with 475 additions and 559 deletions
@@ -0,0 +1,72 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
if (NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
ly_add_target(
NAME SceneBuilder.Static STATIC
NAMESPACE AZ
FILES_CMAKE
scenebuilder_files.cmake
Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
COMPILE_DEFINITIONS
PRIVATE
SCENE_BUILDER_EXPORTS
INCLUDE_DIRECTORIES
PUBLIC
../..
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
PUBLIC
AZ::AzToolsFramework
AZ::SceneCore
AZ::SceneData
AZ::SDKWrapper
)
ly_add_target(
NAME SceneBuilder MODULE
NAMESPACE AZ
FILES_CMAKE
scenebuilder_shared_files.cmake
COMPILE_DEFINITIONS
PRIVATE
SCENE_BUILDER_EXPORTS
INCLUDE_DIRECTORIES
PUBLIC
../..
BUILD_DEPENDENCIES
PUBLIC
AZ::SceneBuilder.Static
PRIVATE
AZ::AzCore
)
ly_add_dependencies(AssetBuilder AZ::SceneBuilder)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME SceneBuilder.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
scenebuilder_testing_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::SceneBuilder
)
ly_add_googletest(
NAME AZ::SceneBuilder.Tests
)
endif()
@@ -0,0 +1,119 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#if !defined(AZ_MONOLITHIC_BUILD)
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Module/Environment.h>
#include <SceneAPI/SceneBuilder/SceneImportRequestHandler.h>
#include <SceneAPI/SceneBuilder/SceneImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpColorStreamImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpMeshImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpUvMapImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpSkinImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpSkinWeightsImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
static AZStd::vector<AZ::ComponentDescriptor*> g_componentDescriptors;
void Reflect(AZ::SerializeContext* /*context*/)
{
// Descriptor registration is done in Reflect instead of Initialize because the ResourceCompilerScene initializes the libraries before
// there's an application.
using namespace AZ::SceneAPI;
using namespace AZ::SceneAPI::SceneBuilder;
if (g_componentDescriptors.empty())
{
// Global importer and behavior
g_componentDescriptors.push_back(SceneBuilder::SceneImporter::CreateDescriptor());
g_componentDescriptors.push_back(SceneImportRequestHandler::CreateDescriptor());
// Node and attribute importers
g_componentDescriptors.push_back(AssImpBitangentStreamImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpColorStreamImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpMaterialImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpMeshImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpTangentStreamImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpTransformImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpUvMapImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpSkinImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpSkinWeightsImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpBoneImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpAnimationImporter::CreateDescriptor());
g_componentDescriptors.push_back(AssImpBlendShapeImporter::CreateDescriptor());
for (AZ::ComponentDescriptor* descriptor : g_componentDescriptors)
{
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Handler::RegisterComponentDescriptor, descriptor);
}
}
}
void ReflectBehavior([[maybe_unused]] AZ::BehaviorContext* context)
{
// stub in until LYN-1284 is done
}
void Activate()
{
}
void Deactivate()
{
}
void Uninitialize()
{
if (!g_componentDescriptors.empty())
{
for (AZ::ComponentDescriptor* descriptor : g_componentDescriptors)
{
descriptor->ReleaseDescriptor();
}
g_componentDescriptors.clear();
g_componentDescriptors.shrink_to_fit();
}
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
extern "C" AZ_DLL_EXPORT void InitializeDynamicModule(void* env)
{
AZ::Environment::Attach(static_cast<AZ::EnvironmentInstance>(env));
}
extern "C" AZ_DLL_EXPORT void Reflect(AZ::SerializeContext* context)
{
AZ::SceneAPI::SceneBuilder::Reflect(context);
}
extern "C" AZ_DLL_EXPORT void ReflectBehavior(AZ::BehaviorContext* context)
{
AZ::SceneAPI::SceneBuilder::ReflectBehavior(context);
}
extern "C" AZ_DLL_EXPORT void UninitializeDynamicModule()
{
AZ::SceneAPI::SceneBuilder::Uninitialize();
AZ::Environment::Detach();
}
#endif // !defined(AZ_MONOLITHIC_BUILD)
@@ -0,0 +1,124 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SceneCore/Events/ImportEventContext.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
AssImpImportContext::AssImpImportContext(const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode)
: m_sourceScene(sourceScene)
, m_sourceSceneSystem(sourceSceneSystem)
, m_sourceNode(sourceNode)
{
}
AssImpNodeEncounteredContext::AssImpNodeEncounteredContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode)
: AssImpImportContext(sourceScene, sourceSceneSystem, sourceNode)
, NodeEncounteredContext(scene, currentGraphPosition, nodeNameMap)
{
}
AssImpNodeEncounteredContext::AssImpNodeEncounteredContext(
Events::ImportEventContext& parent,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode)
: AssImpImportContext(sourceScene, sourceSceneSystem, sourceNode)
, NodeEncounteredContext(parent.GetScene(), currentGraphPosition, nodeNameMap)
{
}
AssImpSceneDataPopulatedContext::AssImpSceneDataPopulatedContext(AssImpNodeEncounteredContext& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& graphData, const AZStd::string& dataName)
: AssImpImportContext(parent.m_sourceScene, parent.m_sourceSceneSystem, parent.m_sourceNode)
, SceneDataPopulatedContextBase(parent, graphData, dataName)
{
}
AssImpSceneDataPopulatedContext::AssImpSceneDataPopulatedContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData, const AZStd::string& dataName)
: AssImpImportContext(sourceScene, sourceSceneSystem, sourceNode)
, SceneDataPopulatedContextBase(scene, currentGraphPosition, nodeNameMap, nodeData, dataName)
{
}
AssImpSceneNodeAppendedContext::AssImpSceneNodeAppendedContext(AssImpSceneDataPopulatedContext& parent,
Containers::SceneGraph::NodeIndex newIndex)
: AssImpImportContext(parent.m_sourceScene, parent.m_sourceSceneSystem, parent.m_sourceNode)
, SceneNodeAppendedContextBase(parent.m_scene, newIndex, parent.m_nodeNameMap)
{
}
AssImpSceneNodeAppendedContext::AssImpSceneNodeAppendedContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap, AssImpSDKWrapper::AssImpNodeWrapper& sourceNode)
: AssImpImportContext(sourceScene, sourceSceneSystem, sourceNode)
, SceneNodeAppendedContextBase(scene, currentGraphPosition, nodeNameMap)
{
}
AssImpSceneAttributeDataPopulatedContext::AssImpSceneAttributeDataPopulatedContext(AssImpSceneNodeAppendedContext& parent, const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData, const Containers::SceneGraph::NodeIndex attributeNodeIndex, const AZStd::string& dataName)
: AssImpImportContext(parent.m_sourceScene, parent.m_sourceSceneSystem, parent.m_sourceNode)
, SceneAttributeDataPopulatedContextBase(parent, nodeData, attributeNodeIndex, dataName)
{
}
AssImpSceneAttributeNodeAppendedContext::AssImpSceneAttributeNodeAppendedContext(AssImpSceneAttributeDataPopulatedContext& parent, Containers::SceneGraph::NodeIndex newIndex)
: AssImpImportContext(parent.m_sourceScene, parent.m_sourceSceneSystem, parent.m_sourceNode)
, SceneAttributeNodeAppendedContextBase(parent, newIndex)
{
}
AssImpSceneNodeAddedAttributesContext::AssImpSceneNodeAddedAttributesContext(AssImpSceneNodeAppendedContext& parent)
: AssImpImportContext(parent.m_sourceScene, parent.m_sourceSceneSystem, parent.m_sourceNode)
, SceneNodeAddedAttributesContextBase(parent)
{
}
AssImpSceneNodeFinalizeContext::AssImpSceneNodeFinalizeContext(AssImpSceneNodeAddedAttributesContext& parent)
: AssImpImportContext(parent.m_sourceScene, parent.m_sourceSceneSystem, parent.m_sourceNode)
, SceneNodeFinalizeContextBase(parent)
{
}
AssImpFinalizeSceneContext::AssImpFinalizeSceneContext(Containers::Scene& scene,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap)
: FinalizeSceneContextBase(scene, nodeNameMap)
, m_sourceScene(sourceScene)
, m_sourceSceneSystem(sourceSceneSystem)
{
}
} // namespace SceneAPI
} // namespace SceneBuilder
} // namespace AZ
@@ -0,0 +1,182 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/string/string.h>
#include <SceneAPI/SceneBuilder/ImportContexts/ImportContexts.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
namespace AZ
{
namespace AssImpSDKWrapper
{
class AssImpNodeWrapper;
class AssImpSceneWrapper;
}
namespace SceneAPI
{
class SceneSystem;
namespace SceneBuilder
{
class RenamedNodesMap;
// AssImpImportContext
// Base structure containing common data needed for all import contexts
// Member Variables:
// m_sourceNode - AssImp node being used for data processing.
struct AssImpImportContext
{
AZ_RTTI(AssImpImportContext, "{B1076AFF-991B-423C-8D3E-D5C9230434AB}");
AssImpImportContext(const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode);
const AssImpSDKWrapper::AssImpSceneWrapper& m_sourceScene;
AssImpSDKWrapper::AssImpNodeWrapper& m_sourceNode;
const SceneSystem& m_sourceSceneSystem; // Needed for unit and axis conversion
};
// AssImpNodeEncounteredContext
// Context pushed to indicate that a new AssImp Node has been found and any
// importers that have means to process the contained data should do so
struct AssImpNodeEncounteredContext
: public AssImpImportContext
, public NodeEncounteredContext
{
AZ_RTTI(AssImpNodeEncounteredContext, "{C2305BC5-EAEC-4515-BAD6-45E63C3FBD3D}", AssImpImportContext, NodeEncounteredContext);
AssImpNodeEncounteredContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode);
AssImpNodeEncounteredContext(Events::ImportEventContext& parent,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode);
};
// AssImpSceneDataPopulatedContext
// Context pushed to indicate that a piece of scene data has been fully
// processed and any importers that wish to place it within the scene graph
// may now do so.
struct AssImpSceneDataPopulatedContext
: public AssImpImportContext
, public SceneDataPopulatedContextBase
{
AZ_RTTI(AssImpSceneDataPopulatedContext, "{888DA37E-4234-4990-AD50-E6E54AFA9C35}", AssImpImportContext, SceneDataPopulatedContextBase);
AssImpSceneDataPopulatedContext(AssImpNodeEncounteredContext& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData,
const AZStd::string& dataName);
AssImpSceneDataPopulatedContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData,
const AZStd::string& dataName);
};
// AssImpSceneNodeAppendedContext
// Context pushed to indicate that data has been added to the scene graph.
// Generally created due to the insertion of a node during SceneDataPopulatedContext
// processing.
struct AssImpSceneNodeAppendedContext
: public AssImpImportContext
, public SceneNodeAppendedContextBase
{
AZ_RTTI(AssImpSceneNodeAppendedContext, "{9C8B688E-8ECD-4EF0-9AC6-21BBCFE8F5A3}", AssImpImportContext, SceneNodeAppendedContextBase);
AssImpSceneNodeAppendedContext(AssImpSceneDataPopulatedContext& parent, Containers::SceneGraph::NodeIndex newIndex);
AssImpSceneNodeAppendedContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap,
AssImpSDKWrapper::AssImpNodeWrapper& sourceNode);
};
// AssImpSceneAttributeDataPopulatedContext
// Context pushed to indicate that attribute data has been found and processed
struct AssImpSceneAttributeDataPopulatedContext
: public AssImpImportContext
, public SceneAttributeDataPopulatedContextBase
{
AZ_RTTI(AssImpSceneAttributeDataPopulatedContext, "{A5EFB485-2F36-4214-972B-0EFF4EFBF33D}", AssImpImportContext, SceneAttributeDataPopulatedContextBase);
AssImpSceneAttributeDataPopulatedContext(AssImpSceneNodeAppendedContext& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData,
const Containers::SceneGraph::NodeIndex attributeNodeIndex,const AZStd::string& dataName);
};
// AssImpSceneAttributeNodeAppendedContext
// Context pushed to indicate that an attribute node has been added to the scene graph
struct AssImpSceneAttributeNodeAppendedContext
: public AssImpImportContext
, public SceneAttributeNodeAppendedContextBase
{
AZ_RTTI(AssImpSceneAttributeNodeAppendedContext, "{96FDC405-2D3B-4030-A301-B3A2B5432498}", AssImpImportContext, SceneAttributeNodeAppendedContextBase);
AssImpSceneAttributeNodeAppendedContext(AssImpSceneAttributeDataPopulatedContext& parent, Containers::SceneGraph::NodeIndex newIndex);
};
// AssImpSceneNodeAddedAttributesContext
// Context pushed to indicate that all attribute processors have completed their
// work for a specific data node.
struct AssImpSceneNodeAddedAttributesContext
: public AssImpImportContext
, public SceneNodeAddedAttributesContextBase
{
AZ_RTTI(AssImpSceneNodeAddedAttributesContext, "{D305EAA5-5F16-4AAD-805D-DF07A1B355B9}", AssImpImportContext, SceneNodeAddedAttributesContextBase);
AssImpSceneNodeAddedAttributesContext(AssImpSceneNodeAppendedContext& parent);
};
// AssImpSceneNodeFinalizeContext
// Context pushed last after all other contexts for a scene node to allow any
// post-processing needed for an importer.
struct AssImpSceneNodeFinalizeContext
: public AssImpImportContext
, public SceneNodeFinalizeContextBase
{
AZ_RTTI(AssImpSceneNodeFinalizeContext, "{FD8B4AD5-3735-4D55-9455-504AB1DCA655}", AssImpImportContext, SceneNodeFinalizeContextBase);
AssImpSceneNodeFinalizeContext(AssImpSceneNodeAddedAttributesContext& parent);
};
// AssImpFinalizeSceneContext
// Context pushed after the scene has been fully created. This can be used to finalize pending work
// such as resolving named links.
struct AssImpFinalizeSceneContext
: public FinalizeSceneContextBase
{
AZ_RTTI(AssImpFinalizeSceneContext, "{6B23A54A-44BF-4661-A130-6B4D06A57B9F}", FinalizeSceneContextBase);
AssImpFinalizeSceneContext(
Containers::Scene& scene,
const AssImpSDKWrapper::AssImpSceneWrapper& sourceScene,
const SceneSystem& sourceSceneSystem,
RenamedNodesMap& nodeNameMap);
const AssImpSDKWrapper::AssImpSceneWrapper& m_sourceScene;
const SceneSystem& m_sourceSceneSystem; // Needed for unit and axis conversion
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,107 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/ImportContexts/ImportContexts.h>
#include <SceneAPI/SceneCore/Events/ImportEventContext.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
ImportContext::ImportContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap)
: m_scene(scene)
, m_currentGraphPosition(currentGraphPosition)
, m_nodeNameMap(nodeNameMap)
{
}
ImportContext::ImportContext(Containers::Scene& scene, RenamedNodesMap& nodeNameMap)
: m_scene(scene)
, m_nodeNameMap(nodeNameMap)
{
m_currentGraphPosition = Containers::SceneGraph::NodeIndex();
}
NodeEncounteredContext::NodeEncounteredContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap)
: ImportContext(scene, currentGraphPosition, nodeNameMap)
{
}
NodeEncounteredContext::NodeEncounteredContext(
Events::ImportEventContext& parent, Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap)
: ImportContext(parent.GetScene(), currentGraphPosition, nodeNameMap)
{
}
SceneDataPopulatedContextBase::SceneDataPopulatedContextBase(NodeEncounteredContext& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& graphData, const AZStd::string& dataName)
: ImportContext(parent.m_scene, parent.m_currentGraphPosition, parent.m_nodeNameMap)
, m_graphData(graphData)
, m_dataName(dataName)
{
}
SceneDataPopulatedContextBase::SceneDataPopulatedContextBase(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData, const AZStd::string& dataName)
: ImportContext(scene, currentGraphPosition, nodeNameMap)
, m_graphData(nodeData)
, m_dataName(dataName)
{
}
SceneNodeAppendedContextBase::SceneNodeAppendedContextBase(SceneDataPopulatedContextBase& parent,
Containers::SceneGraph::NodeIndex newIndex)
: ImportContext(parent.m_scene, newIndex, parent.m_nodeNameMap)
{
}
SceneNodeAppendedContextBase::SceneNodeAppendedContextBase(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition, RenamedNodesMap& nodeNameMap)
: ImportContext(scene, currentGraphPosition, nodeNameMap)
{
}
SceneAttributeDataPopulatedContextBase::SceneAttributeDataPopulatedContextBase(SceneNodeAppendedContextBase& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData,
const Containers::SceneGraph::NodeIndex attributeNodeIndex, const AZStd::string& dataName)
: ImportContext(parent.m_scene, attributeNodeIndex, parent.m_nodeNameMap)
, m_graphData(nodeData)
, m_dataName(dataName)
{
}
SceneAttributeNodeAppendedContextBase::SceneAttributeNodeAppendedContextBase(SceneAttributeDataPopulatedContextBase& parent, Containers::SceneGraph::NodeIndex newIndex)
: ImportContext(parent.m_scene, newIndex, parent.m_nodeNameMap)
{
}
SceneNodeAddedAttributesContextBase::SceneNodeAddedAttributesContextBase(SceneNodeAppendedContextBase& parent)
: ImportContext(parent.m_scene, parent.m_currentGraphPosition, parent.m_nodeNameMap)
{
}
SceneNodeFinalizeContextBase::SceneNodeFinalizeContextBase(SceneNodeAddedAttributesContextBase& parent)
: ImportContext(parent.m_scene, parent.m_currentGraphPosition, parent.m_nodeNameMap)
{
}
FinalizeSceneContextBase::FinalizeSceneContextBase(Containers::Scene& scene, RenamedNodesMap& nodeNameMap)
: ImportContext(scene, nodeNameMap)
{
}
} // namespace SceneAPI
} // namespace SceneBuilder
} // namespace AZ
@@ -0,0 +1,179 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/string/string.h>
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
namespace AZ
{
namespace SceneAPI
{
namespace Containers
{
class Scene;
}
namespace DataTypes
{
class IGraphObject;
}
namespace Events
{
class ImportEventContext;
}
namespace SceneBuilder
{
class RenamedNodesMap;
// ImportContext
// Base structure containing common data needed for all import contexts
struct ImportContext
: public Events::ICallContext
{
AZ_RTTI(ImportContext, "{68E546D5-9B79-4293-AD37-4A4BA688892F}", Events::ICallContext);
ImportContext(Containers::Scene& scene, Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap);
ImportContext(Containers::Scene& scene, RenamedNodesMap& nodeNameMap);
Containers::Scene& m_scene;
Containers::SceneGraph::NodeIndex m_currentGraphPosition;
RenamedNodesMap& m_nodeNameMap; // Map of the nodes that have received a new name.
};
// NodeEncounteredContext
// Context pushed to indicate that a new Node has been found and any
// importers that have means to process the contained data should do so
// Member Variables:
// m_createdData - out container that importers must add their created data
// to.
struct NodeEncounteredContext
: public ImportContext
{
AZ_RTTI(NodeEncounteredContext, "{40C31D76-7101-4ACD-8849-0D6D0AF62855}", ImportContext);
NodeEncounteredContext(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap);
NodeEncounteredContext(Events::ImportEventContext& parent,
Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap);
AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>> m_createdData;
};
// SceneDataPopulatedContextBase
// Context pushed to indicate that a piece of scene data has been fully
// processed and any importers that wish to place it within the scene graph
// may now do so.
// Member Variables:
// m_graphData - the piece of data that should be inserted in the graph
// m_dataName - the name that should be used as the basis for the scene node
// name
struct SceneDataPopulatedContextBase
: public ImportContext
{
AZ_RTTI(SceneDataPopulatedContextBase, "{5F4CE8D2-EEAC-49F7-8065-0B6372162D6F}", ImportContext);
SceneDataPopulatedContextBase(NodeEncounteredContext& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData,
const AZStd::string& dataName);
SceneDataPopulatedContextBase(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition,
RenamedNodesMap& nodeNameMap,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData, const AZStd::string& dataName);
const AZStd::shared_ptr<DataTypes::IGraphObject>& m_graphData;
const AZStd::string m_dataName;
};
// SceneNodeAppendedContextBase
// Context pushed to indicate that data has been added to the scene graph.
// Generally created due to the insertion of a node during SceneDataPopulatedContextBase
// processing.
struct SceneNodeAppendedContextBase
: public ImportContext
{
AZ_RTTI(SceneNodeAppendedContextBase, "{0A69FB6C-2B1B-46E7-AEC3-C4B8ABBFDD69}", ImportContext);
SceneNodeAppendedContextBase(SceneDataPopulatedContextBase& parent, Containers::SceneGraph::NodeIndex newIndex);
SceneNodeAppendedContextBase(Containers::Scene& scene,
Containers::SceneGraph::NodeIndex currentGraphPosition, RenamedNodesMap& nodeNameMap);
};
// SceneAttributeDataPopulatedContextBase
// Context pushed to indicate that attribute data has been found and processed
struct SceneAttributeDataPopulatedContextBase
: public ImportContext
{
AZ_RTTI(SceneAttributeDataPopulatedContextBase, "{DA133E14-0770-435B-9A4E-38679367F56C}", ImportContext);
SceneAttributeDataPopulatedContextBase(SceneNodeAppendedContextBase& parent,
const AZStd::shared_ptr<DataTypes::IGraphObject>& nodeData,
const Containers::SceneGraph::NodeIndex attributeNodeIndex, const AZStd::string& dataName);
const AZStd::shared_ptr<DataTypes::IGraphObject>& m_graphData;
const AZStd::string m_dataName;
};
// SceneAttributeNodeAppendedContextBase
// Context pushed to indicate that an attribute node has been added to the scene graph
struct SceneAttributeNodeAppendedContextBase
: public ImportContext
{
AZ_RTTI(SceneAttributeNodeAppendedContextBase, "{8A382A1E-CFE7-47D2-BA5B-CFDF1FB9F03D}", ImportContext);
SceneAttributeNodeAppendedContextBase(SceneAttributeDataPopulatedContextBase& parent,
Containers::SceneGraph::NodeIndex newIndex);
};
// SceneNodeAddedAttributesContextBase
// Context pushed to indicate that all attribute processors have completed their
// work for a specific data node.
struct SceneNodeAddedAttributesContextBase
: public ImportContext
{
AZ_RTTI(SceneNodeAddedAttributesContextBase, "{65B97E48-16A0-4BBD-B364-CFDA9E3600B6}", ImportContext);
SceneNodeAddedAttributesContextBase(SceneNodeAppendedContextBase& parent);
};
// SceneNodeFinalizeContextBase
// Context pushed last after all other contexts for a scene node to allow any
// post-processing needed for an importer.
struct SceneNodeFinalizeContextBase
: public ImportContext
{
AZ_RTTI(SceneNodeFinalizeContextBase, "{F2C7D1BC-8065-423E-9212-241EB426A2BB}", ImportContext);
SceneNodeFinalizeContextBase(SceneNodeAddedAttributesContextBase& parent);
};
// FinalizeSceneContextBase
// Context pushed after the scene has been fully created. This can be used to finalize pending work
// such as resolving named links.
struct FinalizeSceneContextBase
: public ImportContext
{
AZ_RTTI(FinalizeSceneContextBase, "{91C54F51-9B4D-4C61-956C-9D530725D737}", ImportContext);
FinalizeSceneContextBase(Containers::Scene& scene, RenamedNodesMap& nodeNameMap);
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,675 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AssImpTypeConverter.h>
#include <assimp/mesh.h>
#include <assimp/scene.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneData/GraphData/AnimationData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const char* AssImpAnimationImporter::s_animationNodeName = "animation";
// Downstream only supports 30 frames per second sample rate. Adjusting to 60 doubles the
// length of the animations, they still play back at 30 frames per second.
const double AssImpAnimationImporter::s_defaultTimeStepBetweenFrames = 1.0 / 30.0;
AZ::u32 GetNumKeyFrames(AZ::u32 keysSize, double duration, double ticksPerSecond)
{
if (AZ::IsClose(ticksPerSecond, 0))
{
AZ_Warning("AnimationImporter", false, "Animation ticks per second should not be zero, defaulting to %d keyframes for animation.", keysSize);
return keysSize;
}
const double totalTicks = duration / ticksPerSecond;
AZ::u32 numKeys = keysSize;
// +1 because the animation is from [0, duration] - we have a keyframe at the end of the duration which needs to be included
double totalFramesAtDefaultTimeStep = totalTicks / AssImpAnimationImporter::s_defaultTimeStepBetweenFrames + 1;
if (!AZ::IsClose(totalFramesAtDefaultTimeStep, numKeys, 1))
{
numKeys = AZStd::ceilf(totalFramesAtDefaultTimeStep);
}
return numKeys;
}
double GetTimeForFrame(AZ::u32 frame, double ticksPerSecond)
{
return frame * AssImpAnimationImporter::s_defaultTimeStepBetweenFrames * ticksPerSecond;
}
// Helper class to store key data, when translating from AssImp layout to the engine's scene format.
struct KeyData
{
KeyData(float value, float time) :
mValue(value),
mTime(time)
{
}
bool operator<(const KeyData& other) const
{
return mTime < other.mTime;
}
float mValue = 0;
float mTime = 0;
};
template<class T>
void LerpTemplate(T& start, const T& end, float t)
{
start = start * (1.0f - t) + end * t;
}
template<>
void LerpTemplate(aiQuaternion& start, const aiQuaternion& end, float t)
{
aiQuaternion::Interpolate(start, start, end, t);
}
template<>
void LerpTemplate(float& start, const float& end, float t)
{
start = AZ::Lerp(start, end, t);
}
template<class KeyContainerType, class FrameValueType>
bool SampleKeyFrame(FrameValueType& result, const KeyContainerType& keys, AZ::u32 numKeys, double time, AZ::u32& lastIndex)
{
if (numKeys == 0)
{
AZ_Error("AnimationImporter", numKeys > 0, "Animation key set must have at least 1 key");
return false;
}
if (numKeys == 1)
{
result = keys[0].mValue;
return true;
}
while (lastIndex < numKeys - 1 && time >= keys[lastIndex + 1].mTime)
{
++lastIndex;
}
result = keys[lastIndex].mValue;
if (lastIndex < numKeys - 1)
{
auto nextValue = keys[lastIndex + 1].mValue;
float normalizedTimeBetweenFrames = 0;
if (keys[lastIndex + 1].mTime != keys[lastIndex].mTime)
{
normalizedTimeBetweenFrames =
(time - keys[lastIndex].mTime) / (keys[lastIndex + 1].mTime - keys[lastIndex].mTime);
}
else
{
AZ_Warning("AnimationImporter", false,
"Animation has keys with duplicate time %5.5f, at indices %d and %d. The second will be ignored.",
keys[lastIndex].mTime,
lastIndex,
lastIndex + 1);
}
LerpTemplate(result, nextValue, normalizedTimeBetweenFrames);
}
return true;
}
AssImpAnimationImporter::AssImpAnimationImporter()
{
BindToCall(&AssImpAnimationImporter::ImportAnimation);
}
void AssImpAnimationImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpAnimationImporter, SceneCore::LoadingComponent>()->Version(5); // [LYN-4226] Invert PostRotation matrix in animation chains
}
}
AZStd::string GetName(const aiMeshAnim* anim)
{
return anim->mName.C_Str();
}
AZStd::string GetName(const aiNodeAnim* anim)
{
return anim->mNodeName.C_Str();
}
AZStd::string GetName(const aiMeshMorphAnim* anim)
{
return anim->mName.C_Str();
}
struct ConsolidatedNodeAnim
{
ConsolidatedNodeAnim() = default;
ConsolidatedNodeAnim(const ConsolidatedNodeAnim& other) = delete;
ConsolidatedNodeAnim(ConsolidatedNodeAnim&& other) noexcept
: mNumPositionKeys{ other.mNumPositionKeys },
mPositionKeys{ other.mPositionKeys },
mNumRotationKeys{ other.mNumRotationKeys },
mRotationKeys{ other.mRotationKeys },
mNumScalingKeys{ other.mNumScalingKeys },
mScalingKeys{ other.mScalingKeys },
m_ownedPositionKeys{ std::move(other.m_ownedPositionKeys) },
m_ownedScalingKeys{ std::move(other.m_ownedScalingKeys) },
m_ownedRotationKeys{ std::move(other.m_ownedRotationKeys) },
mPreState{ other.mPreState },
mPostState{ other.mPostState }
{
}
ConsolidatedNodeAnim& operator=(const ConsolidatedNodeAnim& other) = delete;
ConsolidatedNodeAnim& operator=(ConsolidatedNodeAnim&& other) noexcept
{
if (this == &other)
{
return *this;
}
mNumPositionKeys = other.mNumPositionKeys;
mPositionKeys = other.mPositionKeys;
mNumRotationKeys = other.mNumRotationKeys;
mRotationKeys = other.mRotationKeys;
mNumScalingKeys = other.mNumScalingKeys;
mScalingKeys = other.mScalingKeys;
m_ownedPositionKeys = std::move(other.m_ownedPositionKeys);
m_ownedScalingKeys = std::move(other.m_ownedScalingKeys);
m_ownedRotationKeys = std::move(other.m_ownedRotationKeys);
mPreState = other.mPreState;
mPostState = other.mPostState;
return *this;
}
// These variables are named using AssImp naming convention for consistency
AZ::u32 mNumPositionKeys{};
aiVectorKey* mPositionKeys{};
AZ::u32 mNumRotationKeys{};
aiQuatKey* mRotationKeys{};
AZ::u32 mNumScalingKeys{};
aiVectorKey* mScalingKeys{};
AZStd::vector<aiVectorKey> m_ownedPositionKeys{};
AZStd::vector<aiVectorKey> m_ownedScalingKeys{};
AZStd::vector<aiQuatKey> m_ownedRotationKeys{};
aiAnimBehaviour mPreState{};
aiAnimBehaviour mPostState{};
};
AZStd::pair<const aiAnimation*, ConsolidatedNodeAnim> MakeMyPair(const aiAnimation* animation, const aiNodeAnim* anim)
{
ConsolidatedNodeAnim consolidatedNodeAnim{};
consolidatedNodeAnim.mNumRotationKeys = anim->mNumRotationKeys;
consolidatedNodeAnim.mNumPositionKeys = anim->mNumPositionKeys;
consolidatedNodeAnim.mNumScalingKeys = anim->mNumScalingKeys;
consolidatedNodeAnim.mRotationKeys = anim->mRotationKeys;
consolidatedNodeAnim.mPositionKeys = anim->mPositionKeys;
consolidatedNodeAnim.mScalingKeys = anim->mScalingKeys;
return {animation, AZStd::move(consolidatedNodeAnim)};
}
auto MakeMyPair(const aiAnimation* animation, const aiMeshAnim* anim)
{
return AZStd::make_pair(animation, anim);
}
auto MakeMyPair(const aiAnimation* animation, const aiMeshMorphAnim* anim)
{
return AZStd::make_pair(animation, anim);
}
Events::ProcessingResult AssImpAnimationImporter::ImportAnimation(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "Animation");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
// Add check for animation layers at the scene level.
if (!scene->HasAnimations() || IsPivotNode(currentNode->mName))
{
return Events::ProcessingResult::Ignored;
}
AZStd::unordered_multimap<AZStd::string, AZStd::pair<const aiAnimation*, ConsolidatedNodeAnim>> boneAnimations;
typedef AZStd::pair<const aiAnimation*, const aiMeshMorphAnim*> AnimAndMorphAnim;
typedef AZStd::unordered_map<AZStd::string, AnimAndMorphAnim> ChannelToMorphAnim;
typedef AZStd::unordered_map<AZStd::string, ChannelToMorphAnim> NodeToChannelToMorphAnim;
NodeToChannelToMorphAnim meshMorphAnimations;
// Goes through all the animation channels of a given type and adds them to a map so we can easily find
// all the animations for a given node
// In the case of bone animations, the data is copied into a ConsolidatedNodeAnim so we can
// do fix-ups later without affecting the original data
auto mapAnimationsFunc = [](AZ::u32 numChannels, auto** channels, const aiAnimation* animation, auto& map)
{
map.reserve(numChannels);
for (AZ::u32 channelIndex = 0; channelIndex < numChannels; ++channelIndex)
{
auto* nodeAnim = channels[channelIndex];
AZStd::string name = GetName(nodeAnim);
map.emplace(name, MakeMyPair(animation, nodeAnim));
}
};
for (AZ::u32 animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex)
{
const aiAnimation* animation = scene->mAnimations[animIndex];
if (animation->mTicksPerSecond == 0)
{
AZ_Error(
"AnimationImporter", false,
"Animation name %s has a sample rate of 0 ticks per second and cannot be processed.",
animation->mName.C_Str());
return Events::ProcessingResult::Failure;
}
mapAnimationsFunc(animation->mNumChannels, animation->mChannels, animation, boneAnimations);
for (AZ::u32 channelIndex = 0; channelIndex < animation->mNumMorphMeshChannels; ++channelIndex)
{
auto* nodeAnim = animation->mMorphMeshChannels[channelIndex];
AZStd::string name = GetName(nodeAnim);
AZStd::vector<AZStd::string> meshNodeNameAndChannel;
// Morph target animations include the channel in the name,
// so if a mesh is named Mesh01, the morph target for the first channel
// will be named Mesh01*0
AZ::StringFunc::Tokenize(name, meshNodeNameAndChannel, '*');
if (meshNodeNameAndChannel.size() != 2)
{
AZ_Error(
"AnimationImporter", false,
"Morph animation name %s was not in the expected format of: node name, asterisk, node channel. "
"Example: 'NodeName*0'",
name.c_str());
continue;
}
AZStd::string meshNodeName(meshNodeNameAndChannel[0]);
AZStd::string channel(meshNodeNameAndChannel[1]);
meshMorphAnimations[meshNodeName][channel] = AnimAndMorphAnim(animation, nodeAnim);
}
}
// Go through all the bone animations and find any that reference a pivot node
// We'll make a new node anim and store all the combined animation channels there
// with the name set to the base bone name
decltype(boneAnimations) combinedAnimations;
for (auto&& animation : boneAnimations)
{
size_t pos = AZ::StringFunc::Find(animation.first, PivotNodeMarker, 0);
if (pos != animation.first.npos)
{
AZStd::string_view name, part;
SplitPivotNodeName(aiString(animation.first.c_str()), pos, name, part);
auto&& iterator = combinedAnimations.find(name);
if (iterator == combinedAnimations.end())
{
combinedAnimations.emplace(AZStd::string(name), AZStd::move(animation.second));
iterator = combinedAnimations.find(name);
}
if (iterator != combinedAnimations.end())
{
auto& existingNode = iterator->second.second;
auto&& src = animation.second.second;
if (part == "Translation")
{
existingNode.mNumPositionKeys = src.mNumPositionKeys;
existingNode.mPositionKeys = src.mPositionKeys;
}
else if (part == "Rotation")
{
existingNode.mNumRotationKeys = src.mNumRotationKeys;
existingNode.mRotationKeys = src.mRotationKeys;
}
else if (part == "Scaling")
{
existingNode.mNumScalingKeys = src.mNumScalingKeys;
existingNode.mScalingKeys = src.mScalingKeys;
}
}
}
}
if (!combinedAnimations.empty())
{
boneAnimations.swap(combinedAnimations);
}
Events::ProcessingResultCombiner combinedAnimationResult;
if (context.m_sourceNode.ContainsMesh())
{
const aiMesh* firstMesh = scene->mMeshes[currentNode->mMeshes[0]];
if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(firstMesh->mName.C_Str());
channelsForMeshName != meshMorphAnimations.end())
{
const auto [nodeIterName, channels] = *channelsForMeshName;
for (const auto& [channel, animAndMorphAnim] : channels)
{
const auto& [animation, morphAnimation] = animAndMorphAnim;
combinedAnimationResult += ImportBlendShapeAnimation(
context, animation, morphAnimation, firstMesh);
}
}
}
AZStd::string nodeName = s_animationNodeName;
RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition);
AZ_TraceContext("Animation node name", nodeName);
// If there are no bone animations, but there are mesh animations,
// then a stub animation needs to be created so the exporter can create the exported morph target animation.
if (boneAnimations.empty() && !meshMorphAnimations.empty())
{
const aiAnimation* animation = scene->mAnimations[0];
for (AZ::u32 channelIndex = 0; channelIndex < animation->mNumMorphMeshChannels; ++channelIndex)
{
const aiMeshMorphAnim* nodeAnim = animation->mMorphMeshChannels[channelIndex];
// Morph animations need a regular animation on the node, as well.
// If there is no bone animation on the current node, then generate one here.
AZStd::shared_ptr<SceneData::GraphData::AnimationData> createdAnimationData =
AZStd::make_shared<SceneData::GraphData::AnimationData>();
const size_t numKeyframes = GetNumKeyFrames(
nodeAnim->mNumKeys,
animation->mDuration,
animation->mTicksPerSecond);
createdAnimationData->ReserveKeyFrames(numKeyframes);
const double timeStepBetweenFrames = 1.0 / animation->mTicksPerSecond;
createdAnimationData->SetTimeStepBetweenFrames(timeStepBetweenFrames);
// Set every frame of the animation to the start location of the node.
aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode);
DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform);
for (AZ::u32 time = 0; time <= numKeyframes; ++time)
{
createdAnimationData->AddKeyFrame(localTransform);
}
const AZStd::string stubBoneAnimForMorphName(AZStd::string::format("%s%s", nodeName.c_str(), nodeAnim->mName.C_Str()));
Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild(
context.m_currentGraphPosition, stubBoneAnimForMorphName.c_str(), AZStd::move(createdAnimationData));
context.m_scene.GetGraph().MakeEndPoint(addNode);
}
return combinedAnimationResult.GetResult();
}
AZStd::unordered_set<AZStd::string> boneList;
for (int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
aiMesh* mesh = scene->mMeshes[meshIndex];
for (int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
aiBone* bone = mesh->mBones[boneIndex];
boneList.insert(bone->mName.C_Str());
}
}
decltype(boneAnimations) fillerAnimations;
// Go through all the animations and make sure we create placeholder animations for any bones missing them
for (auto&& anim : boneAnimations)
{
for (auto boneName : boneList)
{
if (!IsPivotNode(aiString(boneName.c_str())))
{
if (!boneAnimations.contains(boneName) &&
!fillerAnimations.contains(boneName))
{
// Create 1 key for each type that just copies the current transform
ConsolidatedNodeAnim emptyAnimation;
auto node = scene->mRootNode->FindNode(boneName.c_str());
aiMatrix4x4 globalTransform = GetConcatenatedLocalTransform(node);
aiVector3D position, scale;
aiQuaternion rotation;
globalTransform.Decompose(scale, rotation, position);
emptyAnimation.mNumRotationKeys = emptyAnimation.mNumPositionKeys = emptyAnimation.mNumScalingKeys = 1;
emptyAnimation.m_ownedPositionKeys.emplace_back(0, position);
emptyAnimation.mPositionKeys = emptyAnimation.m_ownedPositionKeys.data();
emptyAnimation.m_ownedRotationKeys.emplace_back(0, rotation);
emptyAnimation.mRotationKeys = emptyAnimation.m_ownedRotationKeys.data();
emptyAnimation.m_ownedScalingKeys.emplace_back(0, scale);
emptyAnimation.mScalingKeys = emptyAnimation.m_ownedScalingKeys.data();
fillerAnimations.insert(
AZStd::make_pair(boneName, AZStd::make_pair(anim.second.first, AZStd::move(emptyAnimation))));
}
}
}
}
boneAnimations.insert(AZStd::make_move_iterator(fillerAnimations.begin()), AZStd::make_move_iterator(fillerAnimations.end()));
auto animItr = boneAnimations.equal_range(currentNode->mName.C_Str());
if (animItr.first == animItr.second)
{
return combinedAnimationResult.GetResult();
}
bool onlyOne = false;
for (auto it = animItr.first; it != animItr.second; ++it)
{
if (onlyOne)
{
AZ_Error("AnimationImporter", false, "Bone %s has multiple animations. Only 1 animation per bone is supported", currentNode->mName.C_Str());
break;
}
const aiAnimation* animation = it->second.first;
const ConsolidatedNodeAnim* anim = &it->second.second;
// We don't currently handle having a different number of keys, with 1 exception:
// 1 key is essentially a constant so we do handle that case
if ((anim->mNumPositionKeys != anim->mNumRotationKeys && anim->mNumPositionKeys > 1 && anim->mNumRotationKeys > 1) ||
(anim->mNumPositionKeys != anim->mNumScalingKeys && anim->mNumPositionKeys > 1 && anim->mNumScalingKeys > 1) ||
(anim->mNumRotationKeys != anim->mNumScalingKeys && anim->mNumRotationKeys > 1 && anim->mNumScalingKeys > 1))
{
AZ_Error("AnimationImporter", false, "Bone Animation with different number of position (%d)/rotation (%d)/scaling (%d) keys not supported",
anim->mNumPositionKeys, anim->mNumRotationKeys, anim->mNumScalingKeys);
return Events::ProcessingResult::Failure;
}
// Resample the animations at a fixed time step. This matches the behaviour of
// the previous SDK used. Longer term, this could be data driven, or based on the
// smallest time step between key frames.
// AssImp has an animation->mTicksPerSecond and animation->mDuration, but those
// are less predictable than just using a fixed time step.
// AssImp documentation claims animation->mDuration is the duration of the animation in ticks, but
// not all animations we've tested follow that pattern. Sometimes duration is in seconds.
const size_t numKeyFrames = GetNumKeyFrames(
AZStd::max(AZStd::max(anim->mNumScalingKeys, anim->mNumPositionKeys), anim->mNumRotationKeys),
animation->mDuration,
animation->mTicksPerSecond);
AZStd::shared_ptr<SceneData::GraphData::AnimationData> createdAnimationData =
AZStd::make_shared<SceneData::GraphData::AnimationData>();
createdAnimationData->ReserveKeyFrames(numKeyFrames);
createdAnimationData->SetTimeStepBetweenFrames(s_defaultTimeStepBetweenFrames);
AZ::u32 lastScaleIndex = 0;
AZ::u32 lastPositionIndex = 0;
AZ::u32 lastRotationIndex = 0;
for (AZ::u32 frame = 0; frame < numKeyFrames; ++frame)
{
const double time = GetTimeForFrame(frame, animation->mTicksPerSecond);
aiVector3D scale(1.0f, 1.0f, 1.0f);
aiVector3D position(0.0f, 0.0f, 0.0f);
aiQuaternion rotation(1.0f, 0.0f, 0.0f, 0.0f);
if (!SampleKeyFrame(scale, anim->mScalingKeys, anim->mNumScalingKeys, time, lastScaleIndex) ||
!SampleKeyFrame(position, anim->mPositionKeys, anim->mNumPositionKeys, time, lastPositionIndex) ||
!SampleKeyFrame(rotation, anim->mRotationKeys, anim->mNumRotationKeys, time, lastRotationIndex))
{
return Events::ProcessingResult::Failure;
}
aiMatrix4x4 transform(scale, rotation, position);
DataTypes::MatrixType animTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(transform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(animTransform);
context.m_sourceSceneSystem.ConvertBoneUnit(animTransform);
createdAnimationData->AddKeyFrame(animTransform);
}
Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild(
context.m_currentGraphPosition, nodeName.c_str(), AZStd::move(createdAnimationData));
context.m_scene.GetGraph().MakeEndPoint(addNode);
onlyOne = true;
}
combinedAnimationResult += Events::ProcessingResult::Success;
return combinedAnimationResult.GetResult();
}
Events::ProcessingResult AssImpAnimationImporter::ImportBlendShapeAnimation(
AssImpSceneNodeAppendedContext& context,
const aiAnimation* animation,
const aiMeshMorphAnim* meshMorphAnim,
const aiMesh* mesh)
{
if (meshMorphAnim->mNumKeys == 0)
{
return Events::ProcessingResult::Ignored;
}
Events::ProcessingResultCombiner combinedAnimationResult;
// In:
// Key index
// Time
// Values in AssImp, Channel in FBX SDK
// Weights in AssImp, Values in FBX SDK
// Num values & weights
// Out:
// One BlendShapeAnimationData per value (Channel in FBX SDK) index
// SetTimeStepBetweenFrames set on the animation data
// Keyframes. Weights (Values in FBX SDK) per key time.
// Keyframes generated for every single frame of the animation.
typedef AZStd::map<int, AZStd::vector<KeyData>> ValueToKeyDataMap;
ValueToKeyDataMap valueToKeyDataMap;
// Key time can be less than zero, normalize to have zero be the lowest time.
double keyOffset = 0;
for (int keyIdx = 0; keyIdx < meshMorphAnim->mNumKeys; keyIdx++)
{
aiMeshMorphKey& key = meshMorphAnim->mKeys[keyIdx];
for (int valIdx = 0; valIdx < key.mNumValuesAndWeights; ++valIdx)
{
int currentValue = key.mValues[valIdx];
KeyData thisKey(key.mWeights[valIdx], key.mTime);
valueToKeyDataMap[currentValue].insert(
AZStd::upper_bound(valueToKeyDataMap[currentValue].begin(), valueToKeyDataMap[currentValue].end(),thisKey),
thisKey);
if (key.mTime < keyOffset)
{
keyOffset = key.mTime;
}
}
}
for (const auto& [meshIdx, keys] : valueToKeyDataMap)
{
AZStd::shared_ptr<SceneData::GraphData::BlendShapeAnimationData> morphAnimNode =
AZStd::make_shared<SceneData::GraphData::BlendShapeAnimationData>();
const size_t numKeyFrames = GetNumKeyFrames(keys.size(), animation->mDuration, animation->mTicksPerSecond);
morphAnimNode->ReserveKeyFrames(numKeyFrames);
morphAnimNode->SetTimeStepBetweenFrames(s_defaultTimeStepBetweenFrames);
aiAnimMesh* aiAnimMesh = mesh->mAnimMeshes[meshIdx];
AZStd::string_view nodeName(aiAnimMesh->mName.C_Str());
const AZ::u32 maxKeys = keys.size();
AZ::u32 keyIdx = 0;
for (AZ::u32 frame = 0; frame < numKeyFrames; ++frame)
{
const double time = GetTimeForFrame(frame, animation->mTicksPerSecond);
float weight = 0;
if (!SampleKeyFrame(weight, keys, keys.size(), time + keyOffset, keyIdx))
{
return Events::ProcessingResult::Failure;
}
morphAnimNode->AddKeyFrame(weight);
}
const size_t dotIndex = nodeName.find_last_of('.');
nodeName = nodeName.substr(dotIndex + 1);
morphAnimNode->SetBlendShapeName(nodeName.data());
AZStd::string animNodeName(AZStd::string::format("%s_%s", s_animationNodeName, nodeName.data()));
Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild(
context.m_currentGraphPosition, animNodeName.c_str(), AZStd::move(morphAnimNode));
context.m_scene.GetGraph().MakeEndPoint(addNode);
}
return Events::ProcessingResult::Success;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,48 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
struct aiAnimation;
struct aiMesh;
struct aiMeshMorphAnim;
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpAnimationImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpAnimationImporter, "{93b3f4e3-6fcd-42b9-a74e-5923f76d25c7}", SceneCore::LoadingComponent);
AssImpAnimationImporter();
~AssImpAnimationImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportAnimation(AssImpSceneNodeAppendedContext& context);
Events::ProcessingResult ImportBlendShapeAnimation(
AssImpSceneNodeAppendedContext& context,
const aiAnimation* animation,
const aiMeshMorphAnim* meshMorphAnim,
const aiMesh* mesh);
static const double s_defaultTimeStepBetweenFrames;
protected:
static const char* s_animationNodeName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,130 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const char* AssImpBitangentStreamImporter::m_defaultNodeName = "Bitangent";
AssImpBitangentStreamImporter::AssImpBitangentStreamImporter()
{
BindToCall(&AssImpBitangentStreamImporter::ImportBitangentStreams);
}
void AssImpBitangentStreamImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpBitangentStreamImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-3250
}
}
Events::ProcessingResult AssImpBitangentStreamImporter::ImportBitangentStreams(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", m_defaultNodeName);
if (!context.m_sourceNode.ContainsMesh())
{
return Events::ProcessingResult::Ignored;
}
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex)
{
return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
};
// If there are no bitangents on any meshes, there's nothing to import in this function.
const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!anyMeshHasTangentsAndBitangents)
{
return Events::ProcessingResult::Ignored;
}
// AssImp nodes with multiple meshes on them occur when AssImp split a mesh on material.
// This logic recombines those meshes to minimize the changes needed to replace FBX SDK with AssImp, FBX SDK did not separate meshes,
// and the engine has code to do this later.
const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!allMeshesHaveTangentsAndBitangents)
{
AZ_Error(
Utilities::ErrorWindow, false,
"Node with name %s has meshes with and without bitangents. "
"Placeholder incorrect bitangents will be generated to allow the data to process, "
"but the source art needs to be fixed to correct this. Either apply bitangents to all meshes on this node, "
"or remove all bitangents from all meshes on this node.",
currentNode->mName.C_Str());
}
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
AZStd::shared_ptr<SceneData::GraphData::MeshVertexBitangentData> bitangentStream =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexBitangentData>();
// AssImp only has one bitangentStream per mesh.
bitangentStream->SetBitangentSetIndex(0);
bitangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
bitangentStream->ReserveContainerSpace(vertexCount);
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (!mesh->HasTangentsAndBitangents())
{
// This node has mixed meshes with and without bitangents.
// An error was already thrown above. Output stub bitangents so
// the mesh can still be output in some form, even if the data isn't correct.
// The bitangent count needs to match the vertex count on the associated mesh node.
bitangentStream->AppendBitangent(Vector3::CreateAxisY());
}
else
{
const Vector3 bitangent(
AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mBitangents[v]));
bitangentStream->AppendBitangent(bitangent);
}
}
}
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName);
Events::ProcessingResult bitangentResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, m_defaultNodeName);
bitangentResults = Events::Process(dataPopulated);
if (bitangentResults != Events::ProcessingResult::Failure)
{
bitangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
return bitangentResults;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,35 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpBitangentStreamImporter : public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpBitangentStreamImporter, "{49FC818A-956F-43DA-BBAC-73198E0C5A1F}", SceneCore::LoadingComponent);
AssImpBitangentStreamImporter();
~AssImpBitangentStreamImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportBitangentStreams(AssImpSceneNodeAppendedContext& context);
protected:
static const char* m_defaultNodeName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,239 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/string/conversions.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/SkinMeshData.h>
#include <SceneAPI/SceneData/GraphData/BlendShapeData.h>
#include <assimp/scene.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
AssImpBlendShapeImporter::AssImpBlendShapeImporter()
{
BindToCall(&AssImpBlendShapeImporter::ImportBlendShapes);
}
void AssImpBlendShapeImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpBlendShapeImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-2576
}
}
Events::ProcessingResult AssImpBlendShapeImporter::ImportBlendShapes(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "Blend Shapes");
int numMesh = context.m_sourceNode.GetAssImpNode()->mNumMeshes;
bool animMeshExists = false;
for (int idx = 0; idx < numMesh; idx++)
{
int meshId = context.m_sourceNode.GetAssImpNode()->mMeshes[idx];
aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshId];
if (aiMesh->mNumAnimMeshes)
{
animMeshExists = true;
break;
}
}
if (!animMeshExists)
{
return Events::ProcessingResult::Ignored;
}
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
if (!meshDataResult.IsSuccess())
{
return meshDataResult.GetError();
}
Events::ProcessingResultCombiner combinedBlendShapeResult;
// 1. Loop through meshes & anims
// Create storage: Anim to meshes
// 2. Loop through anims & meshes
// Create an anim mesh for each anim, with meshes re-combined.
// AssImp separates meshes that have multiple materials.
// This code re-combines them to match previous FBX SDK behavior,
// so they can be separated by engine code instead.
AZStd::map<AZStd::string_view, AZStd::vector<AZStd::pair<int, int>>> animToMeshToAnimMeshIndices;
for (int nodeMeshIdx = 0; nodeMeshIdx < numMesh; nodeMeshIdx++)
{
int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx];
const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++)
{
aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx];
animToMeshToAnimMeshIndices[aiAnimMesh->mName.C_Str()].emplace_back(nodeMeshIdx, animIdx);
}
}
for (const auto& animToMeshIndex : animToMeshToAnimMeshIndices)
{
AZStd::shared_ptr<SceneData::GraphData::BlendShapeData> blendShapeData =
AZStd::make_shared<SceneData::GraphData::BlendShapeData>();
// Some DCC tools, like Maya, include a full path separated by '.' in the node names.
// For example, "cone_skin_blendShapeNode.cone_squash"
// Downstream processing doesn't want anything but the last part of that node name,
// so find the last '.' and remove anything before it.
AZStd::string nodeName(animToMeshIndex.first);
size_t dotIndex = nodeName.rfind('.');
if (dotIndex != AZStd::string::npos)
{
nodeName.erase(0, dotIndex + 1);
}
int vertexOffset = 0;
RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape");
AZ_TraceContext("Blend shape name", nodeName);
for (const auto& meshIndex : animToMeshIndex.second)
{
int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[meshIndex.first];
const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
const aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[meshIndex.second];
AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumUVSets> uvSetUsedFlags;
for (AZ::u8 uvSetIndex = 0; uvSetIndex < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIndex)
{
uvSetUsedFlags.set(uvSetIndex, aiAnimMesh->HasTextureCoords(uvSetIndex));
}
AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumColorSets> colorSetUsedFlags;
for (AZ::u8 colorSetIndex = 0; colorSetIndex < SceneData::GraphData::BlendShapeData::MaxNumColorSets;
++colorSetIndex)
{
colorSetUsedFlags.set(colorSetIndex, aiAnimMesh->HasVertexColors(colorSetIndex));
}
blendShapeData->ReserveData(
aiAnimMesh->mNumVertices, aiAnimMesh->HasTangentsAndBitangents(), uvSetUsedFlags, colorSetUsedFlags);
for (int vertIdx = 0; vertIdx < aiAnimMesh->mNumVertices; ++vertIdx)
{
AZ::Vector3 vertex(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mVertices[vertIdx]));
context.m_sourceSceneSystem.SwapVec3ForUpAxis(vertex);
context.m_sourceSceneSystem.ConvertUnit(vertex);
blendShapeData->AddPosition(vertex);
blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx + vertexOffset, vertIdx + vertexOffset);
// Add normals
if (aiAnimMesh->HasNormals())
{
AZ::Vector3 normal(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mNormals[vertIdx]));
context.m_sourceSceneSystem.SwapVec3ForUpAxis(normal);
normal.NormalizeSafe();
blendShapeData->AddNormal(normal);
}
// Add tangents and bitangents
if (aiAnimMesh->HasTangentsAndBitangents())
{
// Vector4's constructor that takes in a vector3 sets w to 1.0f automatically.
const AZ::Vector4 tangent(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mTangents[vertIdx]));
const AZ::Vector3 bitangent = AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mBitangents[vertIdx]);
blendShapeData->AddTangentAndBitangent(tangent, bitangent);
}
// Add UVs
for (AZ::u8 uvSetIdx = 0; uvSetIdx < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIdx)
{
if (aiAnimMesh->HasTextureCoords(uvSetIdx))
{
const AZ::Vector2 vertexUV(
aiAnimMesh->mTextureCoords[uvSetIdx][vertIdx].x,
// The engine's V coordinate is reverse of how it's stored in assImp.
1.0f - aiAnimMesh->mTextureCoords[uvSetIdx][vertIdx].y);
blendShapeData->AddUV(vertexUV, uvSetIdx);
}
}
// Add colors
for (AZ::u8 colorSetIdx = 0; colorSetIdx < SceneData::GraphData::BlendShapeData::MaxNumColorSets; ++colorSetIdx)
{
if (aiAnimMesh->HasVertexColors(colorSetIdx))
{
SceneAPI::DataTypes::Color color =
AssImpSDKWrapper::AssImpTypeConverter::ToColor(aiAnimMesh->mColors[colorSetIdx][vertIdx]);
blendShapeData->AddColor(color, colorSetIdx);
}
}
}
// aiAnimMesh just has a list of positions for vertices. The face indices are on the original mesh.
for (int faceIdx = 0; faceIdx < aiMesh->mNumFaces; ++faceIdx)
{
aiFace face = aiMesh->mFaces[faceIdx];
DataTypes::IBlendShapeData::Face blendFace;
if (face.mNumIndices != 3)
{
// AssImp should have triangulated everything, so if this happens then someone has
// probably changed AssImp's import settings. The engine only supports triangles.
AZ_Error(
Utilities::ErrorWindow, false,
"Mesh for node %s has a face with %d vertices, only 3 vertices are supported per face.",
nodeName.c_str(),
face.mNumIndices);
continue;
}
for (int idx = 0; idx < face.mNumIndices; ++idx)
{
blendFace.vertexIndex[idx] = face.mIndices[idx] + vertexOffset;
}
blendShapeData->AddFace(blendFace);
}
vertexOffset += aiMesh->mNumVertices;
}
// Report problem if no vertex or face converted to MeshData
if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0)
{
AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str());
return Events::ProcessingResult::Failure;
}
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
Events::ProcessingResult blendShapeResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, blendShapeData, newIndex, nodeName);
blendShapeResult = Events::Process(dataPopulated);
if (blendShapeResult != Events::ProcessingResult::Failure)
{
blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedBlendShapeResult += blendShapeResult;
}
return combinedBlendShapeResult.GetResult();
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpBlendShapeImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpBlendShapeImporter, "{B0F7174B-9863-4C03-BFB2-83BF29B1A2DD}", SceneCore::LoadingComponent);
AssImpBlendShapeImporter();
~AssImpBlendShapeImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportBlendShapes(AssImpSceneNodeAppendedContext& context);
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,175 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneData/GraphData/BoneData.h>
#include <SceneAPI/SceneData/GraphData/RootBoneData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
AssImpBoneImporter::AssImpBoneImporter()
{
BindToCall(&AssImpBoneImporter::ImportBone);
}
void AssImpBoneImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpBoneImporter, SceneCore::LoadingComponent>()->Version(2);
}
}
void MakeBoneMap(const aiScene* scene, AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiNode* currentNode = queue.front();
queue.pop();
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
}
aiMatrix4x4 CalculateWorldTransform(const aiNode* currentNode)
{
aiMatrix4x4 transform = {};
const aiNode* iteratingNode = currentNode;
while (iteratingNode)
{
transform = iteratingNode->mTransformation * transform;
iteratingNode = iteratingNode->mParent;
}
return transform;
}
Events::ProcessingResult AssImpBoneImporter::ImportBone(AssImpNodeEncounteredContext& context)
{
AZ_TraceContext("Importer", "Bone");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (IsPivotNode(currentNode->mName))
{
return Events::ProcessingResult::Ignored;
}
bool isBone = false;
{
AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
MakeBoneMap(scene, boneLookup);
isBone = boneLookup.contains(currentNode->mName.C_Str());
// If we have an animation, the bones will be listed in there
if (!isBone)
{
for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex)
{
aiAnimation* animation = scene->mAnimations[animIndex];
for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex)
{
aiNodeAnim* nodeAnim = animation->mChannels[channelIndex];
if (nodeAnim->mNodeName == currentNode->mName)
{
isBone = true;
break;
}
}
if (isBone)
{
break;
}
}
}
}
if(!isBone)
{
return Events::ProcessingResult::Ignored;
}
// If the current scene node (our eventual parent) contains bone data, we are not a root bone
AZStd::shared_ptr<SceneData::GraphData::BoneData> createdBoneData;
if (NodeHasAncestorOfType(
context.m_scene.GetGraph(), context.m_currentGraphPosition, DataTypes::IBoneData::TYPEINFO_Uuid()))
{
createdBoneData = AZStd::make_shared<SceneData::GraphData::BoneData>();
}
else
{
createdBoneData = AZStd::make_shared<SceneData::GraphData::RootBoneData>();
}
aiMatrix4x4 transform = CalculateWorldTransform(currentNode);
SceneAPI::DataTypes::MatrixType globalTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(transform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(globalTransform);
context.m_sourceSceneSystem.ConvertBoneUnit(globalTransform);
createdBoneData->SetWorldTransform(globalTransform);
context.m_createdData.push_back(AZStd::move(createdBoneData));
return Events::ProcessingResult::Success;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpBoneImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpBoneImporter, "{E7A62DE7-B660-4920-BF91-32738175D5A7}", SceneCore::LoadingComponent);
AssImpBoneImporter();
~AssImpBoneImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportBone(AssImpNodeEncounteredContext& context);
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,131 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpColorStreamImporter.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexColorData.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const char* AssImpColorStreamImporter::m_defaultNodeName = "Col";
AssImpColorStreamImporter::AssImpColorStreamImporter()
{
BindToCall(&AssImpColorStreamImporter::ImportColorStreams);
}
void AssImpColorStreamImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpColorStreamImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-3250
}
}
Events::ProcessingResult AssImpColorStreamImporter::ImportColorStreams(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", m_defaultNodeName);
if (!context.m_sourceNode.ContainsMesh())
{
return Events::ProcessingResult::Ignored;
}
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
// This node has at least one mesh, verify that the color channel counts are the same for all meshes.
const int expectedColorChannels = scene->mMeshes[currentNode->mMeshes[0]]->GetNumColorChannels();
const bool allMeshesHaveSameNumberOfColorChannels =
AZStd::all_of(currentNode->mMeshes + 1, currentNode->mMeshes + currentNode->mNumMeshes, [scene, expectedColorChannels](const unsigned int meshIndex)
{
return scene->mMeshes[meshIndex]->GetNumColorChannels() == expectedColorChannels;
});
AZ_Error(
Utilities::ErrorWindow,
allMeshesHaveSameNumberOfColorChannels,
"Color channel counts for node %s has meshes with different color channel counts. "
"The color channel count for the first mesh will be used, and placeholder incorrect color values "
"will be generated to allow the data to process, but the source art needs to be fixed to correct this. "
"All meshes on this node should have the same number of color channels.",
currentNode->mName.C_Str());
if (expectedColorChannels == 0)
{
return Events::ProcessingResult::Ignored;
}
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
Events::ProcessingResultCombiner combinedVertexColorResults;
for (int colorSetIndex = 0; colorSetIndex < expectedColorChannels; ++colorSetIndex)
{
AZStd::shared_ptr<SceneData::GraphData::MeshVertexColorData> vertexColors =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexColorData>();
vertexColors->ReserveContainerSpace(vertexCount);
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (colorSetIndex < mesh->GetNumColorChannels())
{
AZ::SceneAPI::DataTypes::Color vertexColor(
AssImpSDKWrapper::AssImpTypeConverter::ToColor(mesh->mColors[colorSetIndex][v]));
vertexColors->AppendColor(vertexColor);
}
else
{
// An error was already emitted if this mesh has less color channels
// than other meshes on the parent node. Append an arbitrary color value, fully opaque black,
// so the mesh can still be processed.
// It's better to let the engine load a partially valid mesh than to completely fail.
vertexColors->AppendColor(AZ::SceneAPI::DataTypes::Color(0.0f,0.0f,0.0f,1.0f));
}
}
}
AZStd::string nodeName(AZStd::string::format("%s%d", m_defaultNodeName, colorSetIndex));
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
Events::ProcessingResult colorMapResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, vertexColors, newIndex, nodeName.c_str());
colorMapResults = Events::Process(dataPopulated);
if (colorMapResults != Events::ProcessingResult::Failure)
{
colorMapResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedVertexColorResults += colorMapResults;
}
return combinedVertexColorResults.GetResult();
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,35 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpColorStreamImporter : public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpColorStreamImporter, "{071F4764-F3B0-438A-9CB7-19A1248F3B54}", SceneCore::LoadingComponent);
AssImpColorStreamImporter();
~AssImpColorStreamImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportColorStreams(AssImpSceneNodeAppendedContext& context);
protected:
static const char* m_defaultNodeName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,89 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <assimp/scene.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
bool IsSkinnedMesh(const aiNode& node, const aiScene& scene)
{
unsigned boneCount = 0;
for (unsigned mesh = 0; mesh < node.mNumMeshes; ++mesh)
{
if (scene.mMeshes[node.mMeshes[mesh]]->HasBones())
{
++boneCount;
}
}
if (boneCount > 1 && boneCount != node.mNumMeshes)
{
AZ_Error("AssImpImporterUtilities", false, "Node has %d meshes but only %d are skinned. "
"This is unexpected and may result in errors", node.mNumMeshes, boneCount);
}
return boneCount > 0;
}
bool IsPivotNode(const aiString& nodeName, size_t* pos)
{
AZStd::string_view name(nodeName.C_Str(), nodeName.length);
size_t myPos;
if (!pos)
{
pos = &myPos;
}
*pos = AZ::StringFunc::Find(name, PivotNodeMarker);
return *pos != name.npos;
}
void SplitPivotNodeName(const aiString& nodeName, size_t pivotPos, AZStd::string_view& baseNodeName, AZStd::string_view& pivotType)
{
AZStd::string_view nodeNameView(nodeName.data, nodeName.length);
baseNodeName = AZStd::string_view(nodeNameView.substr(0, pivotPos));
pivotType = AZStd::string_view(nodeNameView.substr(pivotPos + sizeof PivotNodeMarker - 1));
}
aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode)
{
const aiNode* parent = currentNode->mParent;
aiMatrix4x4 combinedTransform = currentNode->mTransformation;
while (parent)
{
size_t pos;
if (IsPivotNode(parent->mName, &pos))
{
combinedTransform = parent->mTransformation * combinedTransform;
parent = parent->mParent;
}
else
{
break;
}
}
return combinedTransform;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <assimp/matrix4x4.h>
#include <AzCore/std/string/string.h>
struct aiNode;
struct aiScene;
struct aiString;
namespace AZ::SceneAPI::SceneBuilder
{
inline constexpr char PivotNodeMarker[] = "_$AssimpFbx$_";
bool IsSkinnedMesh(const aiNode& node, const aiScene& scene);
// Checks if a node name is a pivot node and optionally returns the position in the name of the pivot marker (for splitting out the parts later)
bool IsPivotNode(const aiString& nodeName, size_t* pos = nullptr);
// Returns the bone name and type of a pivot node
void SplitPivotNodeName(const aiString& nodeName, size_t pivotPos, AZStd::string_view& baseNodeName, AZStd::string_view& pivotType);
// Gets the entire, combined local transform for a node taking pivot nodes into account. When pivot nodes are not used, this just returns the node's transform
aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode);
} // namespace AZ
@@ -0,0 +1,210 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpMaterialWrapper.h>
#include <SceneAPI/SceneData/GraphData/MaterialData.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
#include <assimp/scene.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
AssImpMaterialImporter::AssImpMaterialImporter()
{
BindToCall(&AssImpMaterialImporter::ImportMaterials);
}
void AssImpMaterialImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpMaterialImporter, SceneCore::LoadingComponent>()->Version(1);
}
}
Events::ProcessingResult AssImpMaterialImporter::ImportMaterials(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "Material");
if (!context.m_sourceNode.ContainsMesh())
{
return Events::ProcessingResult::Ignored;
}
Events::ProcessingResultCombiner combinedMaterialImportResults;
AZStd::unordered_map<int, AZStd::shared_ptr<SceneData::GraphData::MaterialData>> materialMap;
for (int idx = 0; idx < context.m_sourceNode.m_assImpNode->mNumMeshes; ++idx)
{
int meshIndex = context.m_sourceNode.m_assImpNode->mMeshes[idx];
const aiMesh* assImpMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshIndex];
AZ_Assert(assImpMesh, "Asset Importer Mesh should not be null.");
int materialIndex = assImpMesh->mMaterialIndex;
AZ_TraceContext("Material Index", materialIndex);
auto matFound = materialMap.find(materialIndex);
AZStd::shared_ptr<SceneData::GraphData::MaterialData> material;
AZStd::string materialName;
if (matFound == materialMap.end())
{
std::shared_ptr<AssImpSDKWrapper::AssImpMaterialWrapper> assImpMaterial =
std::shared_ptr<AssImpSDKWrapper::AssImpMaterialWrapper>(new AssImpSDKWrapper::AssImpMaterialWrapper(context.m_sourceScene.GetAssImpScene()->mMaterials[materialIndex]));
materialName = assImpMaterial->GetName().c_str();
RenamedNodesMap::SanitizeNodeName(materialName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "Material");
AZ_TraceContext("Material Name", materialName);
material = AZStd::make_shared<SceneData::GraphData::MaterialData>();
material->SetMaterialName(assImpMaterial->GetName());
material->SetTexture(DataTypes::IMaterialData::TextureMapType::Diffuse,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Diffuse)).c_str());
material->SetTexture(DataTypes::IMaterialData::TextureMapType::Specular,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Specular)).c_str());
material->SetTexture(DataTypes::IMaterialData::TextureMapType::Bump,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Bump)).c_str());
material->SetTexture(DataTypes::IMaterialData::TextureMapType::Normal,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Normal)).c_str());
material->SetUniqueId(assImpMaterial->GetUniqueId());
material->SetDiffuseColor(assImpMaterial->GetDiffuseColor());
material->SetSpecularColor(assImpMaterial->GetSpecularColor());
material->SetEmissiveColor(assImpMaterial->GetEmissiveColor());
material->SetShininess(assImpMaterial->GetShininess());
material->SetUseColorMap(assImpMaterial->GetUseColorMap());
material->SetBaseColor(assImpMaterial->GetBaseColor());
material->SetUseMetallicMap(assImpMaterial->GetUseMetallicMap());
material->SetMetallicFactor(assImpMaterial->GetMetallicFactor());
material->SetTexture(
DataTypes::IMaterialData::TextureMapType::Metallic,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Metallic).c_str()));
material->SetUseRoughnessMap(assImpMaterial->GetUseRoughnessMap());
material->SetRoughnessFactor(assImpMaterial->GetRoughnessFactor());
material->SetTexture(
DataTypes::IMaterialData::TextureMapType::Roughness,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Roughness).c_str()));
material->SetUseEmissiveMap(assImpMaterial->GetUseEmissiveMap());
material->SetEmissiveIntensity(assImpMaterial->GetEmissiveIntensity());
material->SetUseAOMap(assImpMaterial->GetUseAOMap());
material->SetTexture(
DataTypes::IMaterialData::TextureMapType::AmbientOcclusion,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::AmbientOcclusion).c_str()));
material->SetTexture(
DataTypes::IMaterialData::TextureMapType::Emissive,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::Emissive).c_str()));
material->SetTexture(
DataTypes::IMaterialData::TextureMapType::BaseColor,
ResolveTexturePath(context.m_sourceScene.GetSceneFileName(),
assImpMaterial->GetTextureFileName(SDKMaterial::MaterialWrapper::MaterialMapType::BaseColor).c_str()));
AZ_Assert(material, "Failed to allocate scene material data.");
if (!material)
{
combinedMaterialImportResults += Events::ProcessingResult::Failure;
continue;
}
materialMap[materialIndex] = material;
}
else
{
material = matFound->second;
materialName = material.get()->GetMaterialName();
}
Events::ProcessingResult materialResult;
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, materialName.c_str());
AZ_Assert(newIndex.IsValid(), "Failed to create SceneGraph node for attribute.");
if (!newIndex.IsValid())
{
combinedMaterialImportResults += Events::ProcessingResult::Failure;
continue;
}
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, material, newIndex, materialName);
materialResult = Events::Process(dataPopulated);
if (materialResult != Events::ProcessingResult::Failure)
{
materialResult = SceneAPI::SceneBuilder::AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedMaterialImportResults += materialResult;
}
return combinedMaterialImportResults.GetResult();
}
AZStd::string AssImpMaterialImporter::ResolveTexturePath(const AZStd::string& sceneFilePath, const AZStd::string& textureFilePath) const
{
if (textureFilePath.empty())
{
return textureFilePath;
}
AZStd::string texturePathRelativeToScene;
AZStd::string cleanedUpSceneFilePath(sceneFilePath);
AZ::StringFunc::Path::StripFullName(cleanedUpSceneFilePath);
AZ::StringFunc::Path::Join(cleanedUpSceneFilePath.c_str(), textureFilePath.c_str(), texturePathRelativeToScene);
// If the texture did start with marker to change directories upward, then it's relative to the scene file,
// and that path needs to be resolved. It can't be resolved later. This was handled internally by FBX SDK,
// but is not handled by AssImp.
if (textureFilePath.starts_with(".."))
{
// Not checking for the file existing because it may not be there yet.
return texturePathRelativeToScene;
}
// The engine only supports relative paths to scan folders.
// Scene files may have paths to textures, relative to the scene file. Check for those, first.
if (AZ::IO::FileIOBase::GetInstance()->Exists(texturePathRelativeToScene.c_str()))
{
return texturePathRelativeToScene;
}
return textureFilePath;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,37 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpMaterialImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpMaterialImporter, "{CD936FA9-17B8-40B9-AA3C-5F593BEFFC94}", SceneCore::LoadingComponent);
AssImpMaterialImporter();
~AssImpMaterialImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportMaterials(AssImpSceneNodeAppendedContext& context);
private:
AZStd::string ResolveTexturePath(const AZStd::string& sceneFilePath, const AZStd::string& textureFilePath) const;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,62 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <assimp/scene.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpMeshImporter.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
AssImpMeshImporter::AssImpMeshImporter()
{
BindToCall(&AssImpMeshImporter::ImportMesh);
}
void AssImpMeshImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpMeshImporter, SceneCore::LoadingComponent>()->Version(2);
}
}
Events::ProcessingResult AssImpMeshImporter::ImportMesh(AssImpNodeEncounteredContext& context)
{
AZ_TraceContext("Importer", "Mesh");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (!context.m_sourceNode.ContainsMesh() || IsSkinnedMesh(*currentNode, *scene))
{
return Events::ProcessingResult::Ignored;
}
if (BuildSceneMeshFromAssImpMesh(currentNode, scene, context.m_sourceSceneSystem, context.m_createdData, [] {
return AZStd::make_shared<SceneData::GraphData::MeshData>();
}))
{
return Events::ProcessingResult::Success;
}
return Events::ProcessingResult::Failure;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpMeshImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpMeshImporter, "{41611339-1D32-474A-A6A4-25CE4430AAFB}", SceneCore::LoadingComponent);
AssImpMeshImporter();
~AssImpMeshImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportMesh(AssImpNodeEncounteredContext& context);
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,62 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <assimp/scene.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpSkinImporter.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneData/GraphData/SkinMeshData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
AssImpSkinImporter::AssImpSkinImporter()
{
BindToCall(&AssImpSkinImporter::ImportSkin);
}
void AssImpSkinImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpSkinImporter, SceneCore::LoadingComponent>()->Version(1);
}
}
Events::ProcessingResult AssImpSkinImporter::ImportSkin(AssImpNodeEncounteredContext& context)
{
AZ_TraceContext("Importer", "Skin");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (!context.m_sourceNode.ContainsMesh() || !IsSkinnedMesh(*currentNode, *scene))
{
return Events::ProcessingResult::Ignored;
}
if (BuildSceneMeshFromAssImpMesh(currentNode, scene, context.m_sourceSceneSystem, context.m_createdData, [] {
return AZStd::make_shared<SceneData::GraphData::SkinMeshData>();
}))
{
return Events::ProcessingResult::Success;
}
return Events::ProcessingResult::Failure;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpSkinImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpSkinImporter, "{8FBCA725-C04E-42B7-9669-82DB3BB0901F}", SceneCore::LoadingComponent);
AssImpSkinImporter();
~AssImpSkinImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportSkin(AssImpNodeEncounteredContext& context);
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,147 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/string/conversions.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpSkinWeightsImporter.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneCore/Events/ImportEventContext.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/SkinWeightData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const AZStd::string AssImpSkinWeightsImporter::s_skinWeightName = "SkinWeight_";
AssImpSkinWeightsImporter::AssImpSkinWeightsImporter()
{
BindToCall(&AssImpSkinWeightsImporter::ImportSkinWeights);
BindToCall(&AssImpSkinWeightsImporter::SetupNamedBoneLinks);
}
void AssImpSkinWeightsImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpSkinWeightsImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-2576
}
}
Events::ProcessingResult AssImpSkinWeightsImporter::ImportSkinWeights(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "Skin Weights");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if(currentNode->mNumMeshes <= 0)
{
return Events::ProcessingResult::Ignored;
}
Events::ProcessingResultCombiner combinedSkinWeightsResult;
// Don't create this until a bone with weights is encountered
Containers::SceneGraph::NodeIndex weightsIndexForMesh;
AZStd::string skinWeightName;
AZStd::shared_ptr<SceneData::GraphData::SkinWeightData> skinWeightData;
const uint64_t totalVertices = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
int vertexCount = 0;
for(unsigned nodeMeshIndex = 0; nodeMeshIndex < currentNode->mNumMeshes; ++nodeMeshIndex)
{
int sceneMeshIndex = currentNode->mMeshes[nodeMeshIndex];
const aiMesh* mesh = scene->mMeshes[sceneMeshIndex];
for(unsigned b = 0; b < mesh->mNumBones; ++b)
{
const aiBone* bone = mesh->mBones[b];
if(bone->mNumWeights <= 0)
{
continue;
}
if (!weightsIndexForMesh.IsValid())
{
skinWeightName = s_skinWeightName;
RenamedNodesMap::SanitizeNodeName(skinWeightName, context.m_scene.GetGraph(), context.m_currentGraphPosition);
weightsIndexForMesh =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, skinWeightName.c_str());
AZ_Error("SkinWeightsImporter", weightsIndexForMesh.IsValid(), "Failed to create SceneGraph node for attribute.");
if (!weightsIndexForMesh.IsValid())
{
combinedSkinWeightsResult += Events::ProcessingResult::Failure;
continue;
}
skinWeightData = AZStd::make_shared<SceneData::GraphData::SkinWeightData>();
}
Pending pending;
pending.m_bone = bone;
pending.m_numVertices = totalVertices;
pending.m_skinWeightData = skinWeightData;
pending.m_vertOffset = vertexCount;
m_pendingSkinWeights.push_back(pending);
}
vertexCount += mesh->mNumVertices;
}
Events::ProcessingResult skinWeightsResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName);
skinWeightsResult = Events::Process(dataPopulated);
if (skinWeightsResult != Events::ProcessingResult::Failure)
{
skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedSkinWeightsResult += skinWeightsResult;
return combinedSkinWeightsResult.GetResult();
}
Events::ProcessingResult AssImpSkinWeightsImporter::SetupNamedBoneLinks(AssImpFinalizeSceneContext& /*context*/)
{
AZ_TraceContext("Importer", "Skin Weights");
for (auto& it : m_pendingSkinWeights)
{
it.m_skinWeightData->ResizeContainerSpace(it.m_numVertices);
AZStd::string boneName = it.m_bone->mName.C_Str();
int boneId = it.m_skinWeightData->GetBoneId(boneName);
for(unsigned weight = 0; weight < it.m_bone->mNumWeights; ++weight)
{
DataTypes::ISkinWeightData::Link link;
link.boneId = boneId;
link.weight = it.m_bone->mWeights[weight].mWeight;
it.m_skinWeightData->AddAndSortLink(it.m_bone->mWeights[weight].mVertexId + it.m_vertOffset, link);
}
}
const auto result = m_pendingSkinWeights.empty() ? Events::ProcessingResult::Ignored : Events::ProcessingResult::Success;
m_pendingSkinWeights.clear();
return result;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <assimp/scene.h>
#include <AzCore/std/containers/vector.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/ISkinWeightData.h>
namespace AZ
{
namespace SceneData
{
namespace GraphData
{
class SkinWeightData;
}
}
namespace SceneAPI
{
namespace Events
{
class PostImportEventContext;
}
namespace SceneBuilder
{
class AssImpSkinWeightsImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpSkinWeightsImporter, "{79B5E863-C155-473A-BC0D-B85F8D8303EB}", SceneCore::LoadingComponent);
AssImpSkinWeightsImporter();
~AssImpSkinWeightsImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportSkinWeights(AssImpSceneNodeAppendedContext& context);
Events::ProcessingResult SetupNamedBoneLinks(AssImpFinalizeSceneContext& context);
protected:
struct Pending
{
const aiBone* m_bone = nullptr;
unsigned m_numVertices = 0;
unsigned m_vertOffset = 0;
AZStd::shared_ptr<SceneData::GraphData::SkinWeightData> m_skinWeightData;
};
//! List of skin weights that still need to be filled in. Setting the data for skin weights is
//! delayed until after the tree has been fully constructed as bones are linked by name, but until
//! the graph has been fully filled in, those names can change which would break the names recorded
//! for the skin.
AZStd::vector<Pending> m_pendingSkinWeights;
static const AZStd::string s_skinWeightName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,132 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexTangentData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const char* AssImpTangentStreamImporter::m_defaultNodeName = "Tangent";
AssImpTangentStreamImporter::AssImpTangentStreamImporter()
{
BindToCall(&AssImpTangentStreamImporter::ImportTangentStreams);
}
void AssImpTangentStreamImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpTangentStreamImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-3250
}
}
Events::ProcessingResult AssImpTangentStreamImporter::ImportTangentStreams(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", m_defaultNodeName);
if (!context.m_sourceNode.ContainsMesh())
{
return Events::ProcessingResult::Ignored;
}
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex)
{
return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
};
// If there are no tangents on any meshes, there's nothing to import in this function.
const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!anyMeshHasTangentsAndBitangents)
{
return Events::ProcessingResult::Ignored;
}
// AssImp nodes with multiple meshes on them occur when AssImp split a mesh on material.
// This logic recombines those meshes to minimize the changes needed to replace FBX SDK with AssImp, FBX SDK did not separate meshes,
// and the engine has code to do this later.
const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!allMeshesHaveTangentsAndBitangents)
{
AZ_Error(
Utilities::ErrorWindow, false,
"Node with name %s has meshes with and without tangents. "
"Placeholder incorrect tangents will be generated to allow the data to process, "
"but the source art needs to be fixed to correct this. Either apply tangents to all meshes on this node, "
"or remove all tangents from all meshes on this node.",
currentNode->mName.C_Str());
}
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
AZStd::shared_ptr<SceneData::GraphData::MeshVertexTangentData> tangentStream =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexTangentData>();
// AssImp only has one tangentStream per mesh.
tangentStream->SetTangentSetIndex(0);
tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
tangentStream->ReserveContainerSpace(vertexCount);
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (!mesh->HasTangentsAndBitangents())
{
// This node has mixed meshes with and without tangents.
// An error was already thrown above. Output stub tangents so
// the mesh can still be output in some form, even if the data isn't correct.
// The tangent count needs to match the vertex count on the associated mesh node.
tangentStream->AppendTangent(Vector4(0.f, 1.f, 0.f, 1.f));
}
else
{
const Vector4 tangent(
AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v]));
tangentStream->AppendTangent(tangent);
}
}
}
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName);
Events::ProcessingResult tangentResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, m_defaultNodeName);
tangentResults = Events::Process(dataPopulated);
if (tangentResults != Events::ProcessingResult::Failure)
{
tangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
return tangentResults;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,35 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpTangentStreamImporter : public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpTangentStreamImporter, "{AB2D1D1E-5A19-40AF-B4F4-C652DD578F43}", SceneCore::LoadingComponent);
AssImpTangentStreamImporter();
~AssImpTangentStreamImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportTangentStreams(AssImpSceneNodeAppendedContext& context);
protected:
static const char* m_defaultNodeName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,203 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/TransformData.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <assimp/scene.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const char* AssImpTransformImporter::s_transformNodeName = "transform";
AssImpTransformImporter::AssImpTransformImporter()
{
BindToCall(&AssImpTransformImporter::ImportTransform);
}
void AssImpTransformImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpTransformImporter, SceneCore::LoadingComponent>()->Version(1);
}
}
void GetAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiNode* currentNode = queue.front();
queue.pop();
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
}
Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "transform");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName))
{
return Events::ProcessingResult::Ignored;
}
AZStd::unordered_multimap<AZStd::string, const aiBone*> boneLookup;
GetAllBones(scene, boneLookup);
auto boneIterator = boneLookup.find(currentNode->mName.C_Str());
const bool isBone = boneIterator != boneLookup.end();
DataTypes::MatrixType localTransform;
if (isBone)
{
AZStd::vector<DataTypes::MatrixType> offsets, inverseOffsets;
auto iteratingNode = currentNode;
while (iteratingNode && boneLookup.count(iteratingNode->mName.C_Str()))
{
AZStd::string name = iteratingNode->mName.C_Str();
auto range = boneLookup.equal_range(name);
if (range.first != range.second)
{
// There can be multiple offsetMatrices for a given bone, we're only interested in grabbing the first one
auto boneFirstOffsetMatrix = range.first->second->mOffsetMatrix;
auto azMat = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(boneFirstOffsetMatrix);
offsets.push_back(azMat);
inverseOffsets.push_back(azMat.GetInverseFull());
}
iteratingNode = iteratingNode->mParent;
}
if (inverseOffsets.size() == 1)
{
// If this is the root bone, just use the inverseOffset, otherwise the equation below just results in the identity matrix
localTransform = inverseOffsets[0];
}
else
{
localTransform = offsets.at(1) // parent bone offset
* inverseOffsets.at(inverseOffsets.size() - 1) // Inverse of root bone offset
* offsets.at(offsets.size() - 1) // Root bone offset
* inverseOffsets.at(0); // Inverse of current node offset
}
}
else
{
localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(currentNode));
}
// Don't bother adding a node with the identity matrix
if (localTransform == DataTypes::MatrixType::Identity())
{
return Events::ProcessingResult::Ignored;
}
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform);
AZStd::shared_ptr<SceneData::GraphData::TransformData> transformData =
AZStd::make_shared<SceneData::GraphData::TransformData>(localTransform);
AZ_Error(SceneAPI::Utilities::ErrorWindow, transformData, "Failed to allocate transform data.");
if (!transformData)
{
return Events::ProcessingResult::Failure;
}
// If it is non-endpoint data populated node, add a transform attribute
if (context.m_scene.GetGraph().HasNodeContent(context.m_currentGraphPosition))
{
if (!context.m_scene.GetGraph().IsNodeEndPoint(context.m_currentGraphPosition))
{
AZStd::string nodeName = s_transformNodeName;
RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition);
AZ_TraceContext("Transform node name", nodeName);
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
AZ_Error(SceneAPI::Utilities::ErrorWindow, newIndex.IsValid(), "Failed to create SceneGraph node for attribute.");
if (!newIndex.IsValid())
{
return Events::ProcessingResult::Failure;
}
Events::ProcessingResult transformAttributeResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, transformData, newIndex, nodeName);
transformAttributeResult = Events::Process(dataPopulated);
if (transformAttributeResult != Events::ProcessingResult::Failure)
{
transformAttributeResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
return transformAttributeResult;
}
}
else
{
bool addedData = context.m_scene.GetGraph().SetContent(context.m_currentGraphPosition, transformData);
AZ_Error(SceneAPI::Utilities::ErrorWindow, addedData, "Failed to add node data");
return addedData ? Events::ProcessingResult::Success : Events::ProcessingResult::Failure;
}
return Events::ProcessingResult::Ignored;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,35 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpTransformImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpTransformImporter, "{A7494C53-5822-40EF-9B60-B1FF09FBFA59}", SceneCore::LoadingComponent);
AssImpTransformImporter();
~AssImpTransformImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportTransform(AssImpSceneNodeAppendedContext& context);
static const char* s_transformNodeName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,180 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Math/Vector2.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpUvMapImporter.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
const char* AssImpUvMapImporter::m_defaultNodeName = "UV";
AssImpUvMapImporter::AssImpUvMapImporter()
{
BindToCall(&AssImpUvMapImporter::ImportUvMaps);
}
void AssImpUvMapImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpUvMapImporter, SceneCore::LoadingComponent>()->Version(4); // LYN-3250
}
}
Events::ProcessingResult AssImpUvMapImporter::ImportUvMaps(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", m_defaultNodeName);
if (!context.m_sourceNode.ContainsMesh())
{
return Events::ProcessingResult::Ignored;
}
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
// AssImp separates meshes that have multiple materials.
// This code re-combines them to match previous FBX SDK behavior,
// so they can be separated by engine code instead.
bool foundTextureCoordinates = false;
AZStd::array<int, AI_MAX_NUMBER_OF_TEXTURECOORDS> meshesPerTextureCoordinateIndex = {};
for (int localMeshIndex = 0; localMeshIndex < currentNode->mNumMeshes; ++localMeshIndex)
{
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[localMeshIndex]];
for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
if (!mesh->mTextureCoords[texCoordIndex])
{
continue;
}
++meshesPerTextureCoordinateIndex[texCoordIndex];
foundTextureCoordinates = true;
}
}
if (!foundTextureCoordinates)
{
return Events::ProcessingResult::Ignored;
}
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
AZ_Error(
Utilities::ErrorWindow,
meshesPerTextureCoordinateIndex[texCoordIndex] == 0 ||
meshesPerTextureCoordinateIndex[texCoordIndex] == currentNode->mNumMeshes,
"Texture coordinate index %d for node %s is not on all meshes on this node. "
"Placeholder arbitrary texture values will be generated to allow the data to process, but the source art "
"needs to be fixed to correct this. All meshes on this node should have the same number of texture coordinate channels.",
texCoordIndex,
currentNode->mName.C_Str());
}
Events::ProcessingResultCombiner combinedUvMapResults;
for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
// No meshes have this texture coordinate index, skip it.
if (meshesPerTextureCoordinateIndex[texCoordIndex] == 0)
{
continue;
}
AZStd::shared_ptr<SceneData::GraphData::MeshVertexUVData> uvMap =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexUVData>();
uvMap->ReserveContainerSpace(vertexCount);
bool customNameFound = false;
AZStd::string name(AZStd::string::format("%s%d", m_defaultNodeName, texCoordIndex));
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
if(mesh->mTextureCoords[texCoordIndex])
{
if (mesh->mTextureCoordsNames[texCoordIndex].length > 0)
{
if (!customNameFound)
{
name = mesh->mTextureCoordsNames[texCoordIndex].C_Str();
customNameFound = true;
}
else
{
AZ_Warning(Utilities::WarningWindow,
strcmp(name.c_str(), mesh->mTextureCoordsNames[texCoordIndex].C_Str()) == 0,
"Node %s has conflicting mesh coordinate names at index %d, %s and %s. Using %s.",
currentNode->mName.C_Str(),
texCoordIndex,
name.c_str(),
mesh->mTextureCoordsNames[texCoordIndex].C_Str(),
name.c_str());
}
}
}
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (mesh->mTextureCoords[texCoordIndex])
{
AZ::Vector2 vertexUV(
mesh->mTextureCoords[texCoordIndex][v].x,
// The engine's V coordinate is reverse of how it's coming in from the SDK.
1.0f - mesh->mTextureCoords[texCoordIndex][v].y);
uvMap->AppendUV(vertexUV);
}
else
{
// An error was already emitted if the UV channels for all meshes on this node do not match.
// Append an arbitrary UV value so that the mesh can still be processed.
// It's better to let the engine load a partially valid mesh than to completely fail.
uvMap->AppendUV(AZ::Vector2::CreateZero());
}
}
}
uvMap->SetCustomName(name.c_str());
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, name.c_str());
Events::ProcessingResult uvMapResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, uvMap, newIndex, name);
uvMapResults = Events::Process(dataPopulated);
if (uvMapResults != Events::ProcessingResult::Failure)
{
uvMapResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedUvMapResults += uvMapResults;
}
return combinedUvMapResults.GetResult();
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,37 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class AssImpUvMapImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(AssImpUvMapImporter, "{BF02F231-848B-4CDB-9B11-55EEE15CFAA6}", SceneCore::LoadingComponent);
AssImpUvMapImporter();
~AssImpUvMapImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportUvMaps(AssImpSceneNodeAppendedContext& context);
protected:
static const char* m_defaultNodeName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,434 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Math/Transform.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <SceneAPI/SceneData/GraphData/AnimationData.h>
#include <SceneAPI/SceneData/GraphData/BoneData.h>
#include <SceneAPI/SceneData/GraphData/MaterialData.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexColorData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.h>
#include <SceneAPI/SceneData/GraphData/SkinWeightData.h>
#include <SceneAPI/SceneData/GraphData/TransformData.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
static const float g_sceneUtilityEqualityEpsilon = 0.001f;
CoreProcessingResult AddDataNodeWithContexts(SceneDataPopulatedContextBase& dataPopulated)
{
AZ_TraceContext("Node Name", dataPopulated.m_dataName);
const char* nodeTypeName = dataPopulated.m_graphData ? dataPopulated.m_graphData->RTTI_GetTypeName() : "Null";
AZ_TraceContext("Node Type", (!nodeTypeName || nodeTypeName[0] == '\0' ? "Null" : nodeTypeName));
Events::ProcessingResultCombiner nodeResults;
nodeResults += Events::Process(dataPopulated);
dataPopulated.m_scene.GetGraph().SetContent(dataPopulated.m_currentGraphPosition,
AZStd::move(dataPopulated.m_graphData));
if (azrtti_istypeof<AssImpSceneDataPopulatedContext>(dataPopulated))
{
AssImpSceneDataPopulatedContext* dataPopulatedContext = azrtti_cast<AssImpSceneDataPopulatedContext*>(&dataPopulated);
AssImpSceneNodeAppendedContext nodeAppended(*dataPopulatedContext, dataPopulated.m_currentGraphPosition);
nodeResults += Events::Process(nodeAppended);
AssImpSceneNodeAddedAttributesContext addedAttributes(nodeAppended);
nodeResults += Events::Process(addedAttributes);
AssImpSceneNodeFinalizeContext finalizeNode(addedAttributes);
nodeResults += Events::Process(finalizeNode);
}
return nodeResults.GetResult();
}
CoreProcessingResult AddAttributeDataNodeWithContexts(SceneAttributeDataPopulatedContextBase& dataPopulated)
{
AZ_TraceContext("Node Name", dataPopulated.m_dataName);
const char* nodeTypeName = dataPopulated.m_graphData ? dataPopulated.m_graphData->RTTI_GetTypeName() : "Null";
AZ_TraceContext("Node Type", (!nodeTypeName || nodeTypeName[0] == '\0' ? "Null" : nodeTypeName));
Events::ProcessingResultCombiner nodeResults;
nodeResults += Events::Process(dataPopulated);
dataPopulated.m_scene.GetGraph().MakeEndPoint(dataPopulated.m_currentGraphPosition);
dataPopulated.m_scene.GetGraph().SetContent(dataPopulated.m_currentGraphPosition,
AZStd::move(dataPopulated.m_graphData));
if (azrtti_istypeof<AssImpSceneAttributeDataPopulatedContext>(dataPopulated))
{
AssImpSceneAttributeDataPopulatedContext* dataPopulatedContext = azrtti_cast<AssImpSceneAttributeDataPopulatedContext*>(&dataPopulated);
AssImpSceneAttributeNodeAppendedContext nodeAppended(*dataPopulatedContext, dataPopulated.m_currentGraphPosition);
nodeResults += Events::Process(nodeAppended);
}
return nodeResults.GetResult();
}
bool AreSceneGraphsEqual(const CoreSceneGraph& lhsGraph, const CoreSceneGraph& rhsGraph)
{
auto lhsContentStorage = lhsGraph.GetContentStorage();
auto lhsNameStorage = lhsGraph.GetNameStorage();
auto lhsNameContentView = Containers::Views::MakePairView(lhsNameStorage, lhsContentStorage);
Containers::SceneGraph::NodeIndex lhsRootIndex = lhsGraph.GetRoot();
auto lhsDownwardView =
Containers::Views::MakeSceneGraphDownwardsView<Containers::Views::BreadthFirst>(lhsGraph, lhsRootIndex,
lhsNameContentView.begin(), true);
auto rhsContentStorage = rhsGraph.GetContentStorage();
auto rhsNameStorage = rhsGraph.GetNameStorage();
auto rhsNameContentView = Containers::Views::MakePairView(rhsNameStorage, rhsContentStorage);
Containers::SceneGraph::NodeIndex rhsRootIndex = rhsGraph.GetRoot();
auto rhsDownwardView =
Containers::Views::MakeSceneGraphDownwardsView<Containers::Views::BreadthFirst>(rhsGraph, rhsRootIndex,
rhsNameContentView.begin(), true);
auto lhsIt = lhsDownwardView.begin();
auto rhsIt = rhsDownwardView.begin();
while (lhsIt != lhsDownwardView.end() && rhsIt != rhsDownwardView.end())
{
if (!IsGraphDataEqual(lhsIt->second, rhsIt->second))
{
return false;
}
if (lhsIt->first != rhsIt->first)
{
return false;
}
++lhsIt;
++rhsIt;
}
return (lhsIt == lhsDownwardView.end() && rhsIt == rhsDownwardView.end());
}
bool operator==(const SceneData::GraphData::MeshData& lhs,
const SceneData::GraphData::MeshData& rhs)
{
if (lhs.GetVertexCount() != rhs.GetVertexCount())
{
return false;
}
if (lhs.HasNormalData() != rhs.HasNormalData())
{
return false;
}
if (lhs.GetFaceCount() != rhs.GetFaceCount())
{
return false;
}
bool hasNormals = lhs.HasNormalData();
unsigned int vertexCount = lhs.GetVertexCount();
for (unsigned int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
if (lhs.GetPosition(vertexIndex) != rhs.GetPosition(vertexIndex))
{
return false;
}
if (hasNormals && (lhs.GetNormal(vertexIndex) != rhs.GetNormal(vertexIndex)))
{
return false;
}
}
unsigned int faceCount = lhs.GetFaceCount();
for (unsigned int faceIndex = 0; faceIndex < faceCount; ++faceIndex)
{
if (lhs.GetFaceMaterialId(faceIndex) != rhs.GetFaceMaterialId(faceIndex))
{
return false;
}
if (lhs.GetFaceInfo(faceIndex) != rhs.GetFaceInfo(faceIndex))
{
return false;
}
}
return true;
}
bool operator==(const SceneData::GraphData::SkinWeightData& lhs,
const SceneData::GraphData::SkinWeightData& rhs)
{
if (lhs.GetVertexCount() != rhs.GetVertexCount())
{
return false;
}
if (lhs.GetBoneCount() != rhs.GetBoneCount())
{
return false;
}
size_t vertexCount = lhs.GetVertexCount();
for (size_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
{
if (lhs.GetLinkCount(vertexIndex) != rhs.GetLinkCount(vertexIndex))
{
return false;
}
size_t linkCount = lhs.GetLinkCount(vertexIndex);
for (size_t linkIndex = 0; linkIndex < linkCount; ++linkIndex)
{
const DataTypes::ISkinWeightData::Link lhsLink = lhs.GetLink(vertexIndex, linkIndex);
const DataTypes::ISkinWeightData::Link rhsLink = rhs.GetLink(vertexIndex, linkIndex);
if (lhsLink.boneId != rhsLink.boneId || !IsClose(lhsLink.weight, rhsLink.weight, g_sceneUtilityEqualityEpsilon))
{
return false;
}
if (lhs.GetBoneName(lhsLink.boneId) != rhs.GetBoneName(rhsLink.boneId))
{
return false;
}
}
}
return true;
}
bool operator==(const SceneData::GraphData::BoneData& lhs,
const SceneData::GraphData::BoneData& rhs)
{
return (lhs.GetWorldTransform() == rhs.GetWorldTransform());
}
bool operator==(const DataTypes::Color& lhs, const DataTypes::Color& rhs)
{
if (!IsClose(lhs.alpha, rhs.alpha, g_sceneUtilityEqualityEpsilon) ||
!IsClose(lhs.blue, rhs.blue, g_sceneUtilityEqualityEpsilon) ||
!IsClose(lhs.green, rhs.green, g_sceneUtilityEqualityEpsilon) ||
!IsClose(lhs.red, rhs.red, g_sceneUtilityEqualityEpsilon))
{
return false;
}
return true;
}
bool operator!=(const DataTypes::Color& lhs, const DataTypes::Color& rhs)
{
return !(lhs == rhs);
}
bool operator==(const SceneData::GraphData::MeshVertexColorData& lhs,
const SceneData::GraphData::MeshVertexColorData& rhs)
{
if (lhs.GetCount() != rhs.GetCount())
{
return false;
}
size_t colorCount = lhs.GetCount();
for (size_t colorIndex = 0; colorIndex < colorCount; ++colorIndex)
{
if (lhs.GetColor(colorIndex) != rhs.GetColor(colorIndex))
{
return false;
}
}
return true;
}
bool operator==(const SceneData::GraphData::MeshVertexUVData& lhs,
const SceneData::GraphData::MeshVertexUVData& rhs)
{
if (lhs.GetCount() != rhs.GetCount())
{
return false;
}
size_t uvCount = lhs.GetCount();
for (size_t uvIndex = 0; uvIndex < uvCount; ++uvIndex)
{
if (lhs.GetUV(uvIndex) != rhs.GetUV(uvIndex))
{
return false;
}
}
return true;
}
bool operator==(const SceneData::GraphData::MaterialData& lhs,
const SceneData::GraphData::MaterialData& rhs)
{
if (lhs.IsNoDraw() != rhs.IsNoDraw())
{
return false;
}
if (lhs.GetTexture(DataTypes::IMaterialData::TextureMapType::Diffuse) !=
rhs.GetTexture(DataTypes::IMaterialData::TextureMapType::Diffuse))
{
return false;
}
if (lhs.GetTexture(DataTypes::IMaterialData::TextureMapType::Specular) !=
rhs.GetTexture(DataTypes::IMaterialData::TextureMapType::Specular))
{
return false;
}
if (lhs.GetTexture(DataTypes::IMaterialData::TextureMapType::Bump) !=
rhs.GetTexture(DataTypes::IMaterialData::TextureMapType::Bump))
{
return false;
}
return true;
}
bool operator==(const SceneData::GraphData::TransformData& lhs,
const SceneData::GraphData::TransformData& rhs)
{
return lhs.GetMatrix() == rhs.GetMatrix();
}
bool operator==(const SceneData::GraphData::AnimationData& lhs,
const SceneData::GraphData::AnimationData& rhs)
{
if (lhs.GetKeyFrameCount() != rhs.GetKeyFrameCount())
{
return false;
}
size_t keyFrameCount = lhs.GetKeyFrameCount();
for (size_t keyFrameIndex = 0; keyFrameIndex < keyFrameCount; ++keyFrameIndex)
{
if (lhs.GetKeyFrame(keyFrameIndex) != rhs.GetKeyFrame(keyFrameIndex))
{
return false;
}
}
return true;
}
bool IsGraphDataEqual(const AZStd::shared_ptr<const DataTypes::IGraphObject>& lhs,
const AZStd::shared_ptr<const DataTypes::IGraphObject>& rhs)
{
// If both are null, they are considered equal
if (!lhs && !rhs)
{
return true;
}
// If only one is null, they are considered not equal
if (!lhs || !rhs)
{
return false;
}
// If they have disparate types they are considered not equal
if (lhs->RTTI_GetType() != rhs->RTTI_GetType())
{
return false;
}
if (lhs->RTTI_IsTypeOf(SceneData::GraphData::BoneData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::BoneData* lhsBone =
azrtti_cast<const SceneData::GraphData::BoneData*>(lhs.get());
const SceneData::GraphData::BoneData* rhsBone =
azrtti_cast<const SceneData::GraphData::BoneData*>(rhs.get());
return (*lhsBone == *rhsBone);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::MeshData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::MeshData* lhsMesh =
azrtti_cast<const SceneData::GraphData::MeshData*>(lhs.get());
const SceneData::GraphData::MeshData* rhsMesh =
azrtti_cast<const SceneData::GraphData::MeshData*>(rhs.get());
return (*lhsMesh == *rhsMesh);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::SkinWeightData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::SkinWeightData* lhsSkinWeights =
azrtti_cast<const SceneData::GraphData::SkinWeightData*>(lhs.get());
const SceneData::GraphData::SkinWeightData* rhsSkinWeights =
azrtti_cast<const SceneData::GraphData::SkinWeightData*>(rhs.get());
return (*lhsSkinWeights == *rhsSkinWeights);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::MeshVertexColorData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::MeshVertexColorData* lhsColorData =
azrtti_cast<const SceneData::GraphData::MeshVertexColorData*>(lhs.get());
const SceneData::GraphData::MeshVertexColorData* rhsColorData =
azrtti_cast<const SceneData::GraphData::MeshVertexColorData*>(rhs.get());
return (*lhsColorData == *rhsColorData);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::MeshVertexUVData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::MeshVertexUVData* lhsUVData =
azrtti_cast<const SceneData::GraphData::MeshVertexUVData*>(lhs.get());
const SceneData::GraphData::MeshVertexUVData* rhsUVData =
azrtti_cast<const SceneData::GraphData::MeshVertexUVData*>(rhs.get());
return (*lhsUVData == *rhsUVData);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::MaterialData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::MaterialData* lhsMaterialData =
azrtti_cast<const SceneData::GraphData::MaterialData*>(lhs.get());
const SceneData::GraphData::MaterialData* rhsMaterialData =
azrtti_cast<const SceneData::GraphData::MaterialData*>(rhs.get());
return (*lhsMaterialData == *rhsMaterialData);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::TransformData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::TransformData* lhsTransform =
azrtti_cast<const SceneData::GraphData::TransformData*>(lhs.get());
const SceneData::GraphData::TransformData* rhsTransform =
azrtti_cast<const SceneData::GraphData::TransformData*>(rhs.get());
return (*lhsTransform == *rhsTransform);
}
else if (lhs->RTTI_IsTypeOf(SceneData::GraphData::AnimationData::TYPEINFO_Uuid()))
{
const SceneData::GraphData::AnimationData* lhsAnimation =
azrtti_cast<const SceneData::GraphData::AnimationData*>(lhs.get());
const SceneData::GraphData::AnimationData* rhsAnimation =
azrtti_cast<const SceneData::GraphData::AnimationData*>(rhs.get());
return (*lhsAnimation == *rhsAnimation);
}
return true;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,48 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneBuilder/ImportContexts/ImportContexts.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/DataTypes/MatrixType.h>
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
namespace AZ
{
struct Uuid;
namespace SceneAPI
{
namespace SceneBuilder
{
struct SceneImportContext;
using CoreScene = Containers::Scene;
using CoreSceneGraph = Containers::SceneGraph;
using CoreGraphNodeIndex = Containers::SceneGraph::NodeIndex;
using CoreProcessingResult = Events::ProcessingResult;
inline bool NodeIsOfType(const CoreSceneGraph& graph, CoreGraphNodeIndex nodeIndex, const AZ::Uuid& uuid);
inline bool NodeParentIsOfType(const CoreSceneGraph& graph, CoreGraphNodeIndex nodeIndex,
const AZ::Uuid& uuid);
inline bool NodeHasAncestorOfType(const CoreSceneGraph& graph, CoreGraphNodeIndex nodeIndex,
const AZ::Uuid& uuid);
CoreProcessingResult AddDataNodeWithContexts(SceneDataPopulatedContextBase& dataContext);
CoreProcessingResult AddAttributeDataNodeWithContexts(SceneAttributeDataPopulatedContextBase& dataContext);
bool AreSceneGraphsEqual(const CoreSceneGraph& lhsGraph, const CoreSceneGraph& rhsGraph);
inline bool AreScenesEqual(const CoreScene& lhs, const CoreScene& rhs);
bool IsGraphDataEqual(const AZStd::shared_ptr<const DataTypes::IGraphObject>& lhs,
const AZStd::shared_ptr<const DataTypes::IGraphObject>& rhs);
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.inl>
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
bool NodeIsOfType(const CoreSceneGraph& sceneGraph, CoreGraphNodeIndex nodeIndex, const AZ::Uuid& uuid)
{
if (nodeIndex.IsValid() && sceneGraph.HasNodeContent(nodeIndex) &&
sceneGraph.GetNodeContent(nodeIndex)->RTTI_IsTypeOf(uuid))
{
return true;
}
else
{
return false;
}
}
bool NodeParentIsOfType(const CoreSceneGraph& sceneGraph, CoreGraphNodeIndex nodeIndex, const AZ::Uuid& uuid)
{
CoreGraphNodeIndex parentIndex = sceneGraph.GetNodeParent(nodeIndex);
return NodeIsOfType(sceneGraph, parentIndex, uuid);
}
bool NodeHasAncestorOfType(const CoreSceneGraph& sceneGraph, CoreGraphNodeIndex nodeIndex, const AZ::Uuid& uuid)
{
CoreGraphNodeIndex parentIndex = sceneGraph.GetNodeParent(nodeIndex);
while (parentIndex.IsValid())
{
if (NodeIsOfType(sceneGraph, parentIndex, uuid))
{
return true;
}
parentIndex = sceneGraph.GetNodeParent(parentIndex);
}
return false;
}
bool AreScenesEqual(const CoreScene& lhs, const CoreScene& rhs)
{
if (lhs.GetGraph().GetNodeCount() != rhs.GetGraph().GetNodeCount())
{
return false;
}
if (!AreSceneGraphsEqual(lhs.GetGraph(), rhs.GetGraph()))
{
return false;
}
return true;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,137 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <assimp/mesh.h>
#include <assimp/scene.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/BlendShapeData.h>
#include <SceneAPI/SceneData/GraphData/BoneData.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
namespace AZ::SceneAPI::SceneBuilder
{
bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const SceneSystem& sceneSystem, AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>>& meshes,
const AZStd::function<AZStd::shared_ptr<SceneData::GraphData::MeshData>()>& makeMeshFunc)
{
AZStd::unordered_map<int, int> assImpMatIndexToLYIndex;
int lyMeshIndex = 0;
if(!currentNode || !scene)
{
return false;
}
auto newMesh = makeMeshFunc();
newMesh->SetUnitSizeInMeters(sceneSystem.GetUnitSizeInMeters());
newMesh->SetOriginalUnitSizeInMeters(sceneSystem.GetOriginalUnitSizeInMeters());
// AssImp separates meshes that have multiple materials.
// This code re-combines them to match previous FBX SDK behavior,
// so they can be separated by engine code instead.
int vertOffset = 0;
for (int m = 0; m < currentNode->mNumMeshes; ++m)
{
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]];
// Lumberyard materials are created in order based on mesh references in the scene
if (assImpMatIndexToLYIndex.find(mesh->mMaterialIndex) == assImpMatIndexToLYIndex.end())
{
assImpMatIndexToLYIndex.insert(AZStd::pair<int, int>(mesh->mMaterialIndex, lyMeshIndex++));
}
for (int vertIdx = 0; vertIdx < mesh->mNumVertices; ++vertIdx)
{
AZ::Vector3 vertex(mesh->mVertices[vertIdx].x, mesh->mVertices[vertIdx].y, mesh->mVertices[vertIdx].z);
sceneSystem.SwapVec3ForUpAxis(vertex);
sceneSystem.ConvertUnit(vertex);
newMesh->AddPosition(vertex);
newMesh->SetVertexIndexToControlPointIndexMap(vertIdx + vertOffset, vertIdx + vertOffset);
if (mesh->HasNormals())
{
AZ::Vector3 normal(mesh->mNormals[vertIdx].x, mesh->mNormals[vertIdx].y, mesh->mNormals[vertIdx].z);
sceneSystem.SwapVec3ForUpAxis(normal);
normal.NormalizeSafe();
newMesh->AddNormal(normal);
}
}
for (int faceIdx = 0; faceIdx < mesh->mNumFaces; ++faceIdx)
{
aiFace face = mesh->mFaces[faceIdx];
AZ::SceneAPI::DataTypes::IMeshData::Face meshFace;
if (face.mNumIndices != 3)
{
// AssImp should have triangulated everything, so if this happens then someone has
// probably changed AssImp's import settings. The engine only supports triangles.
AZ_Error(Utilities::ErrorWindow, false,
"Mesh on node %s has a face with %d vertices, only 3 vertices are supported per face.",
currentNode->mName.C_Str(),
face.mNumIndices);
continue;
}
for (int idx = 0; idx < face.mNumIndices; ++idx)
{
meshFace.vertexIndex[idx] = face.mIndices[idx] + vertOffset;
}
newMesh->AddFace(meshFace, assImpMatIndexToLYIndex[mesh->mMaterialIndex]);
}
vertOffset += mesh->mNumVertices;
}
meshes.push_back(newMesh);
return true;
}
GetMeshDataFromParentResult GetMeshDataFromParent(AssImpSceneNodeAppendedContext& context)
{
const DataTypes::IGraphObject* const parentData =
context.m_scene.GetGraph().GetNodeContent(context.m_currentGraphPosition).get();
if (!parentData)
{
AZ_Error(Utilities::ErrorWindow, false,
"GetMeshDataFromParent failed because the parent was null, it should only be called with a valid parent node");
return AZ::Failure(Events::ProcessingResult::Failure);
}
if (!parentData->RTTI_IsTypeOf(SceneData::GraphData::MeshData::TYPEINFO_Uuid()))
{
// The parent node may contain bone information and not mesh information, skip it.
if (parentData->RTTI_IsTypeOf(SceneData::GraphData::BoneData::TYPEINFO_Uuid()))
{
// Return the ignore processing result in the failure.
return AZ::Failure(Events::ProcessingResult::Ignored);
}
AZ_Error(Utilities::ErrorWindow, false,
"Tried to get mesh data from parent for non-mesh parent data");
return AZ::Failure(Events::ProcessingResult::Failure);
}
const SceneData::GraphData::MeshData* const parentMeshData =
azrtti_cast<const SceneData::GraphData::MeshData* const>(parentData);
return AZ::Success(parentMeshData);
}
uint64_t GetVertexCountForAllMeshesOnNode(const aiNode& node, const aiScene& scene)
{
return AZStd::accumulate(node.mMeshes, node.mMeshes + node.mNumMeshes, uint64_t{ 0u },
[&scene](auto runningTotal, unsigned int meshIndex)
{
return runningTotal + scene.mMeshes[meshIndex]->mNumVertices;
});
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
struct aiNode;
struct aiScene;
namespace AZ
{
namespace SceneData
{
namespace GraphData
{
class MeshData;
class BlendShapeData;
}
}
namespace SceneAPI
{
namespace DataTypes
{
class IGraphObject;
}
struct AssImpSceneNodeAppendedContext;
class SceneSystem;
namespace SceneBuilder
{
bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const SceneSystem& sceneSystem, AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>>& meshes,
const AZStd::function<AZStd::shared_ptr<SceneData::GraphData::MeshData>()>& makeMeshFunc);
typedef AZ::Outcome<const SceneData::GraphData::MeshData* const, Events::ProcessingResult> GetMeshDataFromParentResult;
GetMeshDataFromParentResult GetMeshDataFromParent(AssImpSceneNodeAppendedContext& context);
// If a node in the original scene file has a mesh with multiple materials on it, the associated AssImp
// node will have multiple meshes on it, broken apart per material. This returns the total number
// of vertices on all meshes on the given node.
uint64_t GetVertexCountForAllMeshesOnNode(const aiNode& node, const aiScene& scene);
}
}
}
@@ -0,0 +1,158 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/string/conversions.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
bool RenamedNodesMap::SanitizeNodeName(AZStd::string& name, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName)
{
AZ_TraceContext("Node name", name);
const AZStd::string originalNodeName(name);
bool isNameUpdated = false;
// Nodes can't have an empty name, except of the root, otherwise nodes can't be referenced.
if (name.empty())
{
name = defaultName;
isNameUpdated = true;
}
// The scene graph uses an arbitrary character (by default dot) to separate the names of the parents
// therefore that character can't be used in the name.
AZStd::replace_if(name.begin(), name.end(),
[&isNameUpdated](char c) -> bool
{
if (c == Containers::SceneGraph::GetNodeSeperationCharacter())
{
isNameUpdated = true;
return true;
}
else
{
return false;
}
}, '_');
// Nodes under a particular parent have to be unique. Multiple nodes can share the same name, but they
// can't reference the same parent in that case. This is to make sure the node can be quickly found as
// the full path will be unique. To fix any issues, an index is appended.
size_t index = 1;
const size_t offset = name.length();
while (graph.Find(parentNode, name).IsValid())
{
// Remove the previously tried extension.
name.erase(offset, name.length() - offset);
name += ('_');
name += AZStd::to_string(aznumeric_cast<u64>(index));
index++;
isNameUpdated = true;
}
if (isNameUpdated)
{
AZ_TraceContext("New node name", name);
AZ_TracePrintf(Utilities::WarningWindow, "The name of the node '%s' was invalid or conflicting and was updated to '%s'.",
originalNodeName.c_str(), name.c_str());
}
return isNameUpdated;
}
bool RenamedNodesMap::RegisterNode(const std::shared_ptr<SDKNode::NodeWrapper>& node, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName)
{
return node ? RegisterNode(*node, graph, parentNode, defaultName) : false;
}
bool RenamedNodesMap::RegisterNode(const std::shared_ptr<const SDKNode::NodeWrapper>& node, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName)
{
return node ? RegisterNode(*node, graph, parentNode, defaultName) : false;
}
bool RenamedNodesMap::RegisterNode(const SDKNode::NodeWrapper& node, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName)
{
AZStd::string name = node.GetName();
if (SanitizeNodeName(name, graph, parentNode, defaultName))
{
AZ_TraceContext("New node name", name);
// Only register if the name is updated, otherwise the name in the source scene's node can be returned.
auto entry = m_idToName.find(node.GetUniqueId());
if (entry == m_idToName.end())
{
m_idToName.insert(AZStd::make_pair(node.GetUniqueId(), AZStd::move(name)));
return true;
}
else
{
AZ_TraceContext("Previous name", entry->second);
if (entry->second == name)
{
return true;
}
else
{
AZ_Assert(false, "Node has already been registered with a different name.");
return false;
}
}
}
else
{
return true;
}
}
const char* RenamedNodesMap::GetNodeName(const std::shared_ptr<SDKNode::NodeWrapper>& node) const
{
return node ? GetNodeName(*node) : "<invalid>";
}
const char* RenamedNodesMap::GetNodeName(const std::shared_ptr<const SDKNode::NodeWrapper>& node) const
{
return node ? GetNodeName(*node) : "<invalid>";
}
const char* RenamedNodesMap::GetNodeName(const AZStd::shared_ptr<SDKNode::NodeWrapper>& node) const
{
return node ? GetNodeName(*node) : "<invalid>";
}
const char* RenamedNodesMap::GetNodeName(const AZStd::shared_ptr<const SDKNode::NodeWrapper>& node) const
{
return node ? GetNodeName(*node) : "<invalid>";
}
const char* RenamedNodesMap::GetNodeName(const SDKNode::NodeWrapper& node) const
{
auto entry = m_idToName.find(node.GetUniqueId());
if (entry != m_idToName.end())
{
return entry->second.c_str();
}
else
{
return node.GetName();
}
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,60 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <memory>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SDKWrapper/NodeWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
class RenamedNodesMap
{
public:
//! Checks if the provided name is valid for the position in the graph and makes corrections if
//! problems are found.
//! @param name The name of the node in the scene graph.
//! @param graph The scene graph the node will be added to.
//! @param parentNode The node that will be the intended parent for the the node who's name is being checked.
//! @param defaultName If the provided name is empty, the defaultName will be used.
//! @return True if the name was updated otherwise false.
static bool SanitizeNodeName(AZStd::string& name, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName = "unnamed");
//! Register the name for later reference. If the name needs to be sanitized, the sanitized name will be stored.
//! @param node The node that's to be registered.
//! @param graph The scene graph the node will be added to.
//! @param parentNode The node that will be the intended parent for the the node who's name is being checked.
//! @param defaultName If the provided name is empty, the defaultName will be used.
//! @return True if the node was successfully registered.
bool RegisterNode(const std::shared_ptr<SDKNode::NodeWrapper>& node, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName = "unnamed");
bool RegisterNode(const std::shared_ptr<const SDKNode::NodeWrapper>& node, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName = "unnamed");
bool RegisterNode(const SDKNode::NodeWrapper& node, const Containers::SceneGraph& graph,
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName = "unnamed");
//! Returns the name of the given node, which may be sanitized if this was needed.
const char* GetNodeName(const std::shared_ptr<SDKNode::NodeWrapper>& node) const;
const char* GetNodeName(const std::shared_ptr<const SDKNode::NodeWrapper>& node) const;
const char* GetNodeName(const AZStd::shared_ptr<SDKNode::NodeWrapper>& node) const;
const char* GetNodeName(const AZStd::shared_ptr<const SDKNode::NodeWrapper>& node) const;
const char* GetNodeName(const SDKNode::NodeWrapper& node) const;
private:
AZStd::unordered_map<u64, AZStd::string> m_idToName;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,32 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
../../../SceneCore/Containers/SceneManifest.h
../../../SceneCore/Containers/SceneManifest.inl
../../../SceneCore/Containers/SceneManifest.cpp
../../../SceneCore/Events/AssetImportRequest.cpp
../../../SceneCore/Events/AssetImportRequest.h
../../../SceneCore/Events/CallProcessorBinder.cpp
../../../SceneCore/Events/CallProcessorBinder.h
../../../SceneData/GraphData/MeshVertexUVData.h
../../../SceneData/GraphData/MeshVertexUVData.cpp
../../../SceneData/GraphData/MeshVertexColorData.h
../../../SceneData/GraphData/MeshVertexColorData.cpp
../../../SceneData/GraphData/BoneData.h
../../../SceneData/GraphData/BoneData.cpp
../../../SceneData/GraphData/MeshData.h
../../../SceneData/GraphData/MeshData.cpp
../../../SceneData/GraphData/SkinWeightData.h
../../../SceneData/GraphData/SkinWeightData.cpp
../../../SceneData/GraphData/MaterialData.h
../../../SceneData/GraphData/MaterialData.cpp
../../../SceneData/GraphData/AnimationData.h
../../../SceneData/GraphData/AnimationData.cpp
../../../SceneData/GraphData/BlendShapeData.h
../../../SceneData/GraphData/BlendShapeData.cpp
)
@@ -0,0 +1,32 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
../../../SceneCore/Containers/SceneManifest.h
../../../SceneCore/Containers/SceneManifest.inl
../../../SceneCore/Containers/SceneManifest.cpp
../../../SceneCore/Events/AssetImportRequest.cpp
../../../SceneCore/Events/AssetImportRequest.h
../../../SceneCore/Events/CallProcessorBinder.cpp
../../../SceneCore/Events/CallProcessorBinder.h
../../../SceneData/GraphData/MeshVertexUVData.h
../../../SceneData/GraphData/MeshVertexUVData.cpp
../../../SceneData/GraphData/MeshVertexColorData.h
../../../SceneData/GraphData/MeshVertexColorData.cpp
../../../SceneData/GraphData/BoneData.h
../../../SceneData/GraphData/BoneData.cpp
../../../SceneData/GraphData/MeshData.h
../../../SceneData/GraphData/MeshData.cpp
../../../SceneData/GraphData/SkinWeightData.h
../../../SceneData/GraphData/SkinWeightData.cpp
../../../SceneData/GraphData/MaterialData.h
../../../SceneData/GraphData/MaterialData.cpp
../../../SceneData/GraphData/AnimationData.h
../../../SceneData/GraphData/AnimationData.cpp
../../../SceneData/GraphData/BlendShapeData.h
../../../SceneData/GraphData/BlendShapeData.cpp
)
@@ -0,0 +1,9 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
)
@@ -0,0 +1,20 @@
#pragma once
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/PlatformDef.h>
#if defined(AZ_MONOLITHIC_BUILD)
#define SCENE_BUILDER_API
#else
#ifdef SCENE_BUILDER_EXPORTS
#define SCENE_BUILDER_API AZ_DLL_EXPORT
#else
#define SCENE_BUILDER_API AZ_DLL_IMPORT
#endif
#endif // AZ_MONOLITHIC_BUILD
@@ -0,0 +1,103 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <SceneAPI/SceneBuilder/SceneImportRequestHandler.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
#include <SceneAPI/SceneCore/Events/ImportEventContext.h>
namespace AZ
{
namespace SceneAPI
{
void SceneImporterSettings::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext)
{
serializeContext->Class<SceneImporterSettings>()
->Version(2)
->Field("SupportedFileTypeExtensions", &SceneImporterSettings::m_supportedFileTypeExtensions);
}
}
void SceneImportRequestHandler::Activate()
{
if (auto* settingsRegistry = AZ::SettingsRegistry::Get())
{
settingsRegistry->GetObject(m_settings, "/O3DE/SceneAPI/AssetImporter");
}
BusConnect();
}
void SceneImportRequestHandler::Deactivate()
{
BusDisconnect();
}
void SceneImportRequestHandler::Reflect(ReflectContext* context)
{
SceneImporterSettings::Reflect(context);
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<SceneImportRequestHandler, AZ::Component>()->Version(1)->Attribute(
AZ::Edit::Attributes::SystemComponentTags,
AZStd::vector<AZ::Crc32>(
{AssetBuilderSDK::ComponentTags::AssetBuilder,
AssetImportRequest::GetAssetImportRequestComponentTag()}));
}
}
void SceneImportRequestHandler::GetSupportedFileExtensions(AZStd::unordered_set<AZStd::string>& extensions)
{
extensions.insert(m_settings.m_supportedFileTypeExtensions.begin(), m_settings.m_supportedFileTypeExtensions.end());
}
Events::LoadingResult SceneImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, [[maybe_unused]] RequestingApplication requester)
{
AZStd::string extension;
StringFunc::Path::GetExtension(path.c_str(), extension);
AZStd::to_lower(extension.begin(), extension.end());
if (!m_settings.m_supportedFileTypeExtensions.contains(extension))
{
return Events::LoadingResult::Ignored;
}
scene.SetSource(path, guid);
// Push contexts
Events::ProcessingResultCombiner contextResult;
contextResult += Events::Process<Events::PreImportEventContext>(path);
contextResult += Events::Process<Events::ImportEventContext>(path, scene);
contextResult += Events::Process<Events::PostImportEventContext>(scene);
if (contextResult.GetResult() == Events::ProcessingResult::Success)
{
return Events::LoadingResult::AssetLoaded;
}
else
{
return Events::LoadingResult::AssetFailure;
}
}
void SceneImportRequestHandler::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
{
provided.emplace_back(AZ_CRC_CE("AssetImportRequestHandler"));
}
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,52 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <SceneAPI/SceneCore/Components/BehaviorComponent.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
namespace AZ
{
namespace SceneAPI
{
struct SceneImporterSettings
{
AZ_TYPE_INFO(SceneImporterSettings, "{8BB6C7AD-BF99-44DC-9DA1-E7AD3F03DC10}");
static void Reflect(AZ::ReflectContext* context);
AZStd::unordered_set<AZStd::string> m_supportedFileTypeExtensions;
};
class SceneImportRequestHandler
: public AZ::Component
, public Events::AssetImportRequestBus::Handler
{
public:
AZ_COMPONENT(SceneImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}");
~SceneImportRequestHandler() override = default;
void Activate() override;
void Deactivate() override;
static void Reflect(ReflectContext* context);
void GetSupportedFileExtensions(AZStd::unordered_set<AZStd::string>& extensions) override;
Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid,
RequestingApplication requester) override;
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
private:
SceneImporterSettings m_settings;
static constexpr const char* SettingsFilename = "AssetImporterSettings.json";
};
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,251 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneBuilder/SceneImporter.h>
#include <SceneAPI/SceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/TransformData.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
struct QueueNode
{
std::shared_ptr<SDKNode::NodeWrapper> m_node;
Containers::SceneGraph::NodeIndex m_parent;
QueueNode() = delete;
QueueNode(std::shared_ptr<SDKNode::NodeWrapper>&& node, Containers::SceneGraph::NodeIndex parent)
: m_node(std::move(node))
, m_parent(parent)
{
}
};
SceneImporter::SceneImporter()
: m_sceneSystem(new SceneSystem())
{
m_sceneWrapper = AZStd::make_unique<AssImpSDKWrapper::AssImpSceneWrapper>();
BindToCall(&SceneImporter::ImportProcessing);
}
void SceneImporter::Reflect(ReflectContext* context)
{
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<SceneImporter, SceneCore::LoadingComponent>()->Version(2); // SPEC-5776
}
}
Events::ProcessingResult SceneImporter::ImportProcessing(Events::ImportEventContext& context)
{
m_sceneWrapper->Clear();
if (!m_sceneWrapper->LoadSceneFromFile(context.GetInputDirectory().c_str()))
{
return Events::ProcessingResult::Failure;
}
typedef AZStd::function<bool(Containers::Scene & scene)> ConvertFunc;
ConvertFunc convertFunc;
m_sceneSystem->Set(m_sceneWrapper.get());
if (!azrtti_istypeof<AssImpSDKWrapper::AssImpSceneWrapper>(m_sceneWrapper.get()))
{
return Events::ProcessingResult::Failure;
}
convertFunc = AZStd::bind(&SceneImporter::ConvertScene, this, AZStd::placeholders::_1);
if (convertFunc(context.GetScene()))
{
return Events::ProcessingResult::Success;
}
else
{
return Events::ProcessingResult::Failure;
}
}
bool SceneImporter::ConvertScene(Containers::Scene& scene) const
{
std::shared_ptr<SDKNode::NodeWrapper> sceneRoot = m_sceneWrapper->GetRootNode();
if (!sceneRoot)
{
return false;
}
const AssImpSDKWrapper::AssImpSceneWrapper* assImpSceneWrapper = azrtti_cast <AssImpSDKWrapper::AssImpSceneWrapper*>(m_sceneWrapper.get());
AZStd::pair<AssImpSDKWrapper::AssImpSceneWrapper::AxisVector, int32_t> upAxisAndSign = assImpSceneWrapper->GetUpVectorAndSign();
if (upAxisAndSign.second <= 0)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Negative scene orientation is not a currently supported orientation.");
return false;
}
switch (upAxisAndSign.first)
{
case AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::X:
scene.SetOriginalSceneOrientation(Containers::Scene::SceneOrientation::XUp);
break;
case AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::Y:
scene.SetOriginalSceneOrientation(Containers::Scene::SceneOrientation::YUp);
break;
case AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::Z:
scene.SetOriginalSceneOrientation(Containers::Scene::SceneOrientation::ZUp);
break;
default:
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Unknown scene orientation, %d.", upAxisAndSign.first);
AZ_Assert(false, "Unknown scene orientation, %d.", upAxisAndSign.first);
break;
}
AZStd::queue<SceneBuilder::QueueNode> nodes;
nodes.emplace(AZStd::move(sceneRoot), scene.GetGraph().GetRoot());
RenamedNodesMap nodeNameMap;
while (!nodes.empty())
{
SceneBuilder::QueueNode& node = nodes.front();
AZ_Assert(node.m_node, "Empty asset importer node queued");
if (!nodeNameMap.RegisterNode(node.m_node, scene.GetGraph(), node.m_parent))
{
AZ_TracePrintf(Utilities::ErrorWindow, "Failed to register asset importer node in name table.");
// Skip this node since it could not be registered
nodes.pop();
continue;
}
AZStd::string nodeName = nodeNameMap.GetNodeName(node.m_node);
SanitizeNodeName(nodeName);
AZ_TraceContext("SceneAPI Node Name", nodeName);
Containers::SceneGraph::NodeIndex newNode = scene.GetGraph().AddChild(node.m_parent, nodeName.c_str());
AZ_Error(Utilities::ErrorWindow, newNode.IsValid(), "Failed to add Asset Importer node to scene graph");
if (!newNode.IsValid())
{
continue;
}
AssImpNodeEncounteredContext sourceNodeEncountered(scene, newNode, *assImpSceneWrapper, *m_sceneSystem, nodeNameMap, *azrtti_cast<AZ::AssImpSDKWrapper::AssImpNodeWrapper*>(node.m_node.get()));
Events::ProcessingResultCombiner nodeResult;
nodeResult += Events::Process(sourceNodeEncountered);
// If no importer created data, we still create an empty node that may eventually contain a transform
if (sourceNodeEncountered.m_createdData.empty())
{
AZ_Assert(nodeResult.GetResult() != Events::ProcessingResult::Success,
"Importers returned success but no data was created");
AZStd::shared_ptr<DataTypes::IGraphObject> nullData(nullptr);
sourceNodeEncountered.m_createdData.emplace_back(nullData);
nodeResult += Events::ProcessingResult::Success;
}
// Create single node since only one piece of graph data was created
if (sourceNodeEncountered.m_createdData.size() == 1)
{
AZ_Assert(nodeResult.GetResult() != Events::ProcessingResult::Ignored,
"An importer created data, but did not return success");
if (nodeResult.GetResult() == Events::ProcessingResult::Failure)
{
AZ_TracePrintf(Utilities::ErrorWindow, "One or more importers failed to create data.");
}
AssImpSceneDataPopulatedContext dataProcessed(sourceNodeEncountered,
sourceNodeEncountered.m_createdData[0], nodeName.c_str());
Events::ProcessingResult result = AddDataNodeWithContexts(dataProcessed);
if (result != Events::ProcessingResult::Failure)
{
newNode = dataProcessed.m_currentGraphPosition;
}
}
// Create an empty parent node and place all data under it. The remaining
// tree will be built off of this as the logical parent
else
{
AZ_Assert(nodeResult.GetResult() != Events::ProcessingResult::Ignored,
"%i importers created data, but did not return success",
sourceNodeEncountered.m_createdData.size());
if (nodeResult.GetResult() == Events::ProcessingResult::Failure)
{
AZ_TracePrintf(Utilities::ErrorWindow, "One or more importers failed to create data.");
}
size_t offset = nodeName.length();
for (size_t i = 0; i < sourceNodeEncountered.m_createdData.size(); ++i)
{
nodeName += '_';
nodeName += AZStd::to_string(aznumeric_cast<AZ::u64>(i + 1));
Containers::SceneGraph::NodeIndex subNode =
scene.GetGraph().AddChild(newNode, nodeName.c_str());
AZ_Assert(subNode.IsValid(), "Failed to create new scene sub node");
AssImpSceneDataPopulatedContext dataProcessed(sourceNodeEncountered,
sourceNodeEncountered.m_createdData[i], nodeName);
dataProcessed.m_currentGraphPosition = subNode;
AddDataNodeWithContexts(dataProcessed);
// Remove the temporary extension again.
nodeName.erase(offset, nodeName.length() - offset);
}
}
AZ_Assert(nodeResult.GetResult() == Events::ProcessingResult::Success,
"No importers successfully added processed scene data.");
AZ_Assert(newNode != node.m_parent,
"Failed to update current graph position during data processing.");
int childCount = node.m_node->GetChildCount();
for (int i = 0; i < childCount; ++i)
{
std::shared_ptr<AssImpSDKWrapper::AssImpNodeWrapper> child = std::make_shared<AssImpSDKWrapper::AssImpNodeWrapper>(node.m_node->GetChild(i)->GetAssImpNode());
if (child)
{
nodes.emplace(AZStd::move(child), newNode);
}
}
nodes.pop();
};
Events::ProcessingResult result = Events::Process<AssImpFinalizeSceneContext>(scene, *assImpSceneWrapper, *m_sceneSystem, nodeNameMap);
if (result == Events::ProcessingResult::Failure)
{
return false;
}
return true;
}
void SceneImporter::SanitizeNodeName(AZStd::string& nodeName) const
{
// Replace % with something else so it is safe for use in printfs.
AZStd::replace(nodeName.begin(), nodeName.end(), '%', '_');
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,51 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/Events/ImportEventContext.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SDKWrapper/SceneWrapper.h>
namespace AZ
{
namespace SceneAPI
{
namespace Containers
{
class Scene;
}
namespace SceneBuilder
{
class SceneImporter
: public SceneCore::LoadingComponent
{
public:
AZ_COMPONENT(SceneImporter, "{D5EE21B6-8B73-45BF-B711-31346E0BEDB3}", SceneCore::LoadingComponent);
SceneImporter();
~SceneImporter() override = default;
static void Reflect(ReflectContext* context);
Events::ProcessingResult ImportProcessing(Events::ImportEventContext& context);
protected:
bool ConvertScene(Containers::Scene& scene) const;
void SanitizeNodeName(AZStd::string& nodeName) const;
AZStd::unique_ptr<SDKScene::SceneWrapperBase> m_sceneWrapper;
AZStd::shared_ptr<SceneSystem> m_sceneSystem;
};
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,153 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Math/Vector3.h>
#include <SceneAPI/SceneBuilder/SceneSystem.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <assimp/scene.h>
namespace AZ
{
namespace SceneAPI
{
SceneSystem::SceneSystem() :
m_unitSizeInMeters(1.0f),
m_originalUnitSizeInMeters(1.0f),
m_adjustTransform(nullptr),
m_adjustTransformInverse(nullptr)
{
}
void SceneSystem::Set(const SDKScene::SceneWrapperBase* scene)
{
// Get unit conversion factor to meter.
if (!azrtti_istypeof<AssImpSDKWrapper::AssImpSceneWrapper>(scene))
{
return;
}
const AssImpSDKWrapper::AssImpSceneWrapper* assImpScene = azrtti_cast<const AssImpSDKWrapper::AssImpSceneWrapper*>(scene);
// If either meta data piece is not available, the default of 1 will be used.
assImpScene->GetAssImpScene()->mMetaData->Get("UnitScaleFactor", m_unitSizeInMeters);
assImpScene->GetAssImpScene()->mMetaData->Get("OriginalUnitScaleFactor", m_originalUnitSizeInMeters);
/* Conversion factor for converting from centimeters to meters */
m_unitSizeInMeters = m_unitSizeInMeters * .01f;
AZStd::pair<AssImpSDKWrapper::AssImpSceneWrapper::AxisVector, int32_t> upAxisAndSign = assImpScene->GetUpVectorAndSign();
if (upAxisAndSign.second <= 0)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Negative scene orientation is not a currently supported orientation.");
return;
}
AZStd::pair<AssImpSDKWrapper::AssImpSceneWrapper::AxisVector, int32_t> frontAxisAndSign = assImpScene->GetFrontVectorAndSign();
if (upAxisAndSign.first != AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::Z &&
upAxisAndSign.first != AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::Unknown)
{
AZ::Matrix4x4 currentCoordMatrix = AZ::Matrix4x4::CreateIdentity();
//(UpVector = +Z, FrontVector = +Y, CoordSystem = -X(RightHanded))
AZ::Matrix4x4 targetCoordMatrix = AZ::Matrix4x4::CreateFromColumns(
AZ::Vector4(-1, 0, 0, 0),
AZ::Vector4(0, 0, 1, 0),
AZ::Vector4(0, 1, 0, 0),
AZ::Vector4(0, 0, 0, 1));
switch (upAxisAndSign.first)
{
case AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::X: {
if (frontAxisAndSign.second == 1)
{
currentCoordMatrix = AZ::Matrix4x4::CreateFromColumns(
AZ::Vector4(0, -1, 0, 0),
AZ::Vector4(1, 0, 0, 0),
AZ::Vector4(0, 0, 1, 0),
AZ::Vector4(0, 0, 0, 1));
}
else
{
currentCoordMatrix = AZ::Matrix4x4::CreateFromColumns(
AZ::Vector4(0, 1, 0, 0),
AZ::Vector4(1, 0, 0, 0),
AZ::Vector4(0, 0, -1, 0),
AZ::Vector4(0, 0, 0, 1));
}
}
break;
case AssImpSDKWrapper::AssImpSceneWrapper::AxisVector::Y: {
if (frontAxisAndSign.second == 1)
{
currentCoordMatrix = AZ::Matrix4x4::CreateFromColumns(
AZ::Vector4(1, 0, 0, 0),
AZ::Vector4(0, 1, 0, 0),
AZ::Vector4(0, 0, 1, 0),
AZ::Vector4(0, 0, 0, 1));
}
else
{
currentCoordMatrix = AZ::Matrix4x4::CreateFromColumns(
AZ::Vector4(-1, 0, 0, 0),
AZ::Vector4(0, 1, 0, 0),
AZ::Vector4(0, 0, -1, 0),
AZ::Vector4(0, 0, 0, 1));
}
}
break;
}
AZ::Matrix4x4 inverse = currentCoordMatrix.GetInverseTransform();
AZ::Matrix4x4 adjustmatrix = targetCoordMatrix * currentCoordMatrix.GetInverseTransform();
m_adjustTransform.reset(new DataTypes::MatrixType(AssImpSDKWrapper::AssImpTypeConverter::ToTransform(adjustmatrix)));
m_adjustTransformInverse.reset(new DataTypes::MatrixType(m_adjustTransform->GetInverseFull()));
}
}
void SceneSystem::SwapVec3ForUpAxis(Vector3& swapVector) const
{
if (m_adjustTransform)
{
swapVector = *m_adjustTransform * swapVector;
}
}
void SceneSystem::SwapTransformForUpAxis(DataTypes::MatrixType& inOutTransform) const
{
if (m_adjustTransform)
{
inOutTransform = (*m_adjustTransform * inOutTransform) * *m_adjustTransformInverse;
}
}
void SceneSystem::ConvertUnit(Vector3& scaleVector) const
{
scaleVector *= m_unitSizeInMeters;
}
void SceneSystem::ConvertUnit(DataTypes::MatrixType& inOutTransform) const
{
Vector3 translation = inOutTransform.GetTranslation();
translation *= m_unitSizeInMeters;
inOutTransform.SetTranslation(translation);
}
void SceneSystem::ConvertBoneUnit(DataTypes::MatrixType& inOutTransform) const
{
// Need to scale translation explicitly as MultiplyByScale won't change the translation component
// and we need to convert to meter unit
Vector3 translation = inOutTransform.GetTranslation();
translation *= m_unitSizeInMeters;
inOutTransform.SetTranslation(translation);
}
}
}
@@ -0,0 +1,56 @@
#pragma once
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <SceneAPI/SceneBuilder/SceneBuilderConfiguration.h>
#include <SceneAPI/SceneCore/DataTypes/MatrixType.h>
namespace AZ
{
class Vector3;
namespace SDKScene
{
class SceneWrapperBase;
}
namespace SceneAPI
{
class SCENE_BUILDER_API SceneSystem
{
public:
SceneSystem();
void Set(const SDKScene::SceneWrapperBase* sceneWrapper);
void SwapVec3ForUpAxis(Vector3& swapVector) const;
void SwapTransformForUpAxis(DataTypes::MatrixType& inOutTransform) const;
void ConvertUnit(Vector3& scaleVector) const;
void ConvertUnit(DataTypes::MatrixType& inOutTransform) const;
void ConvertBoneUnit(DataTypes::MatrixType& inOutTransform) const;
//! Get effect unit size in meters of this scene, internally FBX saves it in the following manner
//! GlobalSettings: {
//! P : "UnitScaleFactor", "double", "Number", "", 2.54
float GetUnitSizeInMeters() const { return m_unitSizeInMeters; }
//! Get original unit size in meters of this scene, internally FBX saves it in the following manner
//! GlobalSettings: {
//! P : "OriginalUnitScaleFactor", "double", "Number", "", 2.54
float GetOriginalUnitSizeInMeters() const { return m_originalUnitSizeInMeters; }
protected:
float m_unitSizeInMeters = 1;
float m_originalUnitSizeInMeters = 1;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::unique_ptr<DataTypes::MatrixType> m_adjustTransform;
AZStd::unique_ptr<DataTypes::MatrixType> m_adjustTransformInverse;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
}
};
@@ -0,0 +1,132 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzTest/AzTest.h>
#include <SceneAPI/SceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/BoneData.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_EmptySceneGraphs_ReturnsTrue)
{
Containers::SceneGraph lhsGraph;
Containers::SceneGraph rhsGraph;
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_TRUE(sceneGraphsEqual);
}
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_SameNameSingleNodeBothNull_ReturnsTrue)
{
Containers::SceneGraph lhsGraph;
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild");
Containers::SceneGraph rhsGraph;
rhsGraph.AddChild(rhsGraph.GetRoot(), "testChild");
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_TRUE(sceneGraphsEqual);
}
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_SameNameSingleNodeSameType_ReturnsTrue)
{
Containers::SceneGraph lhsGraph;
AZStd::shared_ptr<DataTypes::IGraphObject> lhsData = AZStd::make_shared<SceneData::GraphData::MeshData>();
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild", AZStd::move(lhsData));
Containers::SceneGraph rhsGraph;
AZStd::shared_ptr<DataTypes::IGraphObject> rhsData = AZStd::make_shared<SceneData::GraphData::MeshData>();
rhsGraph.AddChild(rhsGraph.GetRoot(), "testChild", AZStd::move(rhsData));
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_TRUE(sceneGraphsEqual);
}
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_SameNameSingleNodeOneNull_ReturnsFalse)
{
Containers::SceneGraph lhsGraph;
AZStd::shared_ptr<DataTypes::IGraphObject> lhsData = AZStd::make_shared<SceneData::GraphData::MeshData>();
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild", AZStd::move(lhsData));
Containers::SceneGraph rhsGraph;
rhsGraph.AddChild(rhsGraph.GetRoot(), "testChild");
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_FALSE(sceneGraphsEqual);
}
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_SameNameSingleNodeDifferentTypes_ReturnsFalse)
{
Containers::SceneGraph lhsGraph;
AZStd::shared_ptr<DataTypes::IGraphObject> lhsData = AZStd::make_shared<SceneData::GraphData::MeshData>();
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild", AZStd::move(lhsData));
Containers::SceneGraph rhsGraph;
AZStd::shared_ptr<DataTypes::IGraphObject> rhsData = AZStd::make_shared<SceneData::GraphData::BoneData>();
rhsGraph.AddChild(rhsGraph.GetRoot(), "testChild", AZStd::move(rhsData));
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_FALSE(sceneGraphsEqual);
}
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_SameNameOneEmptyOneSingleNode_ReturnsFalse)
{
Containers::SceneGraph lhsGraph;
AZStd::shared_ptr<DataTypes::IGraphObject> lhsData = AZStd::make_shared<SceneData::GraphData::MeshData>();
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild", AZStd::move(lhsData));
Containers::SceneGraph rhsGraph;
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_FALSE(sceneGraphsEqual);
}
TEST(SceneImpoterUtilityTests, AreSceneGraphsEqual_DifferentNamesSingleNodeBothNull_ReturnsFalse)
{
Containers::SceneGraph lhsGraph;
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild");
Containers::SceneGraph rhsGraph;
rhsGraph.AddChild(rhsGraph.GetRoot(), "differentName");
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_FALSE(sceneGraphsEqual);
}
TEST(SceneImporterUtilityTests, AreSceneGraphsEqual_SecondGraphExtraChild_ReturnsFalse)
{
Containers::SceneGraph lhsGraph;
lhsGraph.AddChild(lhsGraph.GetRoot(), "testChild");
lhsGraph.AddChild(lhsGraph.GetRoot(), "extraTestChild");
Containers::SceneGraph rhsGraph;
rhsGraph.AddChild(rhsGraph.GetRoot(), "testChild");
bool sceneGraphsEqual =
(AreSceneGraphsEqual(lhsGraph, rhsGraph) && AreSceneGraphsEqual(rhsGraph, lhsGraph));
EXPECT_FALSE(sceneGraphsEqual);
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,84 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzTest/AzTest.h>
#include <SceneAPI/SceneBuilder/Importers/Utilities/RenamedNodesMap.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
namespace AZ
{
namespace SceneAPI
{
namespace SceneBuilder
{
TEST(RenamedNodesMapTests, SanitizeNodeName_ValidNameProvided_ReturnsFalseAndNameUnchanged)
{
Containers::SceneGraph graph;
AZStd::string name = "ValidName";
bool result = RenamedNodesMap::SanitizeNodeName(name, graph, graph.GetRoot());
EXPECT_FALSE(result);
EXPECT_STREQ("ValidName", name.c_str());
}
TEST(RenamedNodesMapTests, SanitizeNodeName_NameWithInvalidCharacter_ReturnsTrueAndNameChanged)
{
Containers::SceneGraph graph;
AZStd::string check = "Valid";
check += Containers::SceneGraph::GetNodeSeperationCharacter();
check += "Name";
AZStd::string name = check;
bool result = RenamedNodesMap::SanitizeNodeName(name, graph, graph.GetRoot());
EXPECT_TRUE(result);
EXPECT_STRNE(check.c_str(), name.c_str());
}
TEST(RenamedNodesMapTests, SanitizeNodeName_BlankName_ReturnsTrueAndNameSetToDefault)
{
Containers::SceneGraph graph;
AZStd::string name;
bool result = RenamedNodesMap::SanitizeNodeName(name, graph, graph.GetRoot(), "Default");
EXPECT_TRUE(result);
EXPECT_STREQ("Default", name.c_str());
}
TEST(RenamedNodesMapTests, SanitizeNodeName_SingleCollision_ReturnsTrueAndNameHasAppendixOf1)
{
Containers::SceneGraph graph;
graph.AddChild(graph.GetRoot(), "Child");
AZStd::string name = "Child";
bool result = RenamedNodesMap::SanitizeNodeName(name, graph, graph.GetRoot());
EXPECT_TRUE(result);
EXPECT_STREQ("Child_1", name.c_str());
}
TEST(RenamedNodesMapTests, SanitizeNodeName_MultipleCollisions_ReturnsTrueAndNameHasAppendixOf2)
{
Containers::SceneGraph graph;
auto child = graph.AddChild(graph.GetRoot(), "Child");
graph.AddSibling(child, "Child_1");
AZStd::string name = "Child";
bool result = RenamedNodesMap::SanitizeNodeName(name, graph, graph.GetRoot());
EXPECT_TRUE(result);
EXPECT_STREQ("Child_2", name.c_str());
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,180 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Casting/numeric_cast.h>
#include <SceneAPI/FbxSceneBuilder/Tests/TestFbxMesh.h>
#include <fbxsdk.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Casting/numeric_cast.h>
namespace AZ
{
namespace FbxSDKWrapper
{
TestFbxMesh::TestFbxMesh()
: m_vertexControlPoints(nullptr)
, m_vertexCount(0)
, m_polygonVertexIndices(nullptr)
, m_materialIndices(new FbxLayerElementArrayTemplate<int>(eFbxInt))
, m_uvElements(FbxGeometryElementUV::Create(nullptr, "TestElements_UV"))
, m_vertexColorElements(FbxGeometryElementVertexColor::Create(nullptr, "TestElements_VertexColors"))
, m_expectedVertexCount(0)
{
}
int TestFbxMesh::GetDeformerCount() const
{
// For current test need, only have one skin for the mesh
return m_skin ? 1 : 0;
}
AZStd::shared_ptr<const FbxSkinWrapper> TestFbxMesh::GetSkin(int index) const
{
// For current test need, only have one skin for the mesh
return m_skin;
}
bool TestFbxMesh::GetMaterialIndices(FbxLayerElementArrayTemplate<int>** lockableArray) const
{
*lockableArray = m_materialIndices;
return true;
}
int TestFbxMesh::GetControlPointsCount() const
{
return static_cast<int>(m_vertexCount);
}
AZStd::vector<Vector3> TestFbxMesh::GetControlPoints() const
{
return m_vertexControlPoints;
}
int TestFbxMesh::GetPolygonCount() const
{
return static_cast<int>(m_polygonInfo.size());
}
int TestFbxMesh::GetPolygonSize(int polygonIndex) const
{
if (m_polygonInfo.find(polygonIndex) != m_polygonInfo.end())
{
return aznumeric_caster(m_polygonInfo.find(polygonIndex)->second.m_vertexCount);
}
return -1;
}
int* TestFbxMesh::GetPolygonVertices() const
{
return m_polygonVertexIndices;
}
int TestFbxMesh::GetPolygonVertexIndex(int polygonIndex) const
{
if (m_polygonInfo.find(polygonIndex) != m_polygonInfo.end())
{
return aznumeric_caster(m_polygonInfo.find(polygonIndex)->second.m_startVertexIndex);
}
return -1;
}
FbxUVWrapper TestFbxMesh::GetElementUV(int index)
{
(void)index;
return m_uvElements;
}
int TestFbxMesh::GetElementUVCount() const
{
return 1;
}
FbxVertexColorWrapper TestFbxMesh::GetElementVertexColor(int index)
{
(void)index;
return m_vertexColorElements;
}
int TestFbxMesh::GetElementVertexColorCount() const
{
return 1;
}
bool TestFbxMesh::GetPolygonVertexNormal(int polyIndex, int vertexIndex, Vector3& normal) const
{
normal = Vector3(1.0f, 0.0f, 0.0f);
return true;
}
void TestFbxMesh::CreateMesh(std::vector<AZ::Vector3>& points, std::vector<std::vector<int> >& polygonVertexIndices)
{
m_vertexControlPoints.clear();
m_materialIndices->Clear();
m_polygonInfo.clear();
// Create fbx control point (position) data, and associated material index data
m_vertexCount = aznumeric_caster(points.size());
m_vertexControlPoints.reserve(points.size());
for (unsigned int i = 0; i < points.size(); ++i)
{
m_vertexControlPoints.push_back(Vector3(points[i].GetX(), points[i].GetY(), points[i].GetZ()));
m_materialIndices->Add(i);
}
// Create fbx face data
m_expectedVertexCount = 0;
for (const std::vector<int>& onePolygonIndices : polygonVertexIndices)
{
for (const int& index : onePolygonIndices)
{
m_expectedVertexCount++;
}
}
if (m_polygonVertexIndices)
{
delete m_polygonVertexIndices;
}
m_polygonVertexIndices = new int[m_expectedVertexCount];
size_t i = 0;
for (const std::vector<int>& onePolygonIndices : polygonVertexIndices)
{
m_polygonInfo.insert(std::make_pair<int, TestFbxPolygon>(aznumeric_caster(m_polygonInfo.size()),
TestFbxPolygon(i, aznumeric_caster(onePolygonIndices.size()))));
for (const int& index : onePolygonIndices)
{
m_polygonVertexIndices[i] = index;
i++;
}
}
}
void TestFbxMesh::SetSkin(const AZStd::shared_ptr<FbxSkinWrapper>& skin)
{
m_skin = skin;
}
void TestFbxMesh::CreateExpectMeshInfo(std::vector<std::vector<int> >& expectedFaceVertexIndices)
{
m_expectedFaceVertexIndices = expectedFaceVertexIndices;
}
size_t TestFbxMesh::GetExpectedVetexCount() const
{
return m_expectedVertexCount;
}
size_t TestFbxMesh::GetExpectedFaceCount() const
{
return m_expectedFaceVertexIndices.size();
}
AZ::Vector3 TestFbxMesh::GetExpectedFaceVertexPosition(unsigned int faceIndex, unsigned int vertexIndex) const
{
return m_vertexControlPoints[m_expectedFaceVertexIndices[faceIndex][vertexIndex]];
}
}
}
@@ -0,0 +1,85 @@
#pragma once
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <vector>
#include <unordered_map>
#include <AzCore/Math/Vector3.h>
#include <SceneAPI/FbxSDKWrapper/FbxMeshWrapper.h>
#include <SceneAPI/FbxSDKWrapper/FbxSkinWrapper.h>
namespace AZ
{
namespace FbxSDKWrapper
{
struct TestFbxPolygon
{
size_t m_startVertexIndex;
size_t m_vertexCount;
TestFbxPolygon(size_t startVertexIndex, size_t vertexCount)
: m_startVertexIndex(startVertexIndex)
, m_vertexCount(vertexCount)
{
}
};
// TestFbxMesh
// FbxMesh Test Data creation
class TestFbxMesh
: public FbxMeshWrapper
{
public:
TestFbxMesh();
~TestFbxMesh() override = default;
int GetDeformerCount() const override;
AZStd::shared_ptr<const FbxSkinWrapper> GetSkin(int index) const override;
bool GetMaterialIndices(FbxLayerElementArrayTemplate<int>** lockableArray) const override;
int GetControlPointsCount() const;
AZStd::vector<Vector3> GetControlPoints() const override;
int GetPolygonCount() const override;
int GetPolygonSize(int polygonIndex) const override;
int* GetPolygonVertices() const override;
int GetPolygonVertexIndex(int polygonIndex) const;
FbxUVWrapper GetElementUV(int index = 0) override;
int GetElementUVCount() const override;
FbxVertexColorWrapper GetElementVertexColor(int index = 0) override;
int GetElementVertexColorCount() const override;
bool GetPolygonVertexNormal(int polyIndex, int vertexIndex, Vector3& normal) const override;
// Create test data APIs
void CreateMesh(std::vector<AZ::Vector3>& points, std::vector<std::vector<int> >& polygonVertexIndices);
void CreateExpectMeshInfo(std::vector<std::vector<int> >& expectedFaceVertexIndices);
void SetSkin(const AZStd::shared_ptr<FbxSkinWrapper>& skin);
size_t GetExpectedVetexCount() const;
size_t GetExpectedFaceCount() const;
AZ::Vector3 GetExpectedFaceVertexPosition(unsigned int faceIndex, unsigned int vertexIndex) const;
protected:
AZStd::vector<Vector3> m_vertexControlPoints; // vertex positions
size_t m_vertexCount;
int* m_polygonVertexIndices; // store all polygons' vertex indices in sequence. Each index maps to a control point.
FbxLayerElementArrayTemplate<int>* m_materialIndices;
std::unordered_map<int, TestFbxPolygon> m_polygonInfo;
FbxUVWrapper m_uvElements;
FbxVertexColorWrapper m_vertexColorElements;
AZStd::shared_ptr<FbxSkinWrapper> m_skin;
// Expected converted data
unsigned int m_expectedVertexCount;
std::vector<std::vector<int> > m_expectedFaceVertexIndices;
};
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/FbxSceneBuilder/Tests/TestFbxNode.h>
namespace AZ
{
namespace FbxSDKWrapper
{
const std::shared_ptr<FbxMeshWrapper> TestFbxNode::GetMesh() const
{
return m_testFbxMesh;
}
const char* TestFbxNode::GetName() const
{
return m_name.c_str();
}
void TestFbxNode::SetMesh(std::shared_ptr<TestFbxMesh> testFbxMesh)
{
m_testFbxMesh = testFbxMesh;
}
void TestFbxNode::SetName(const char* name)
{
m_name = name;
}
}
}
@@ -0,0 +1,36 @@
#pragma once
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/FbxSDKWrapper/FbxNodeWrapper.h>
#include <SceneAPI/FbxSceneBuilder/Tests/TestFbxMesh.h>
namespace AZ
{
namespace FbxSDKWrapper
{
// TestFbxNode
// FbxNode Test Data creation
class TestFbxNode
: public FbxNodeWrapper
{
public:
~TestFbxNode() override = default;
const std::shared_ptr<FbxMeshWrapper> GetMesh() const override;
const char* GetName() const override;
void SetMesh(std::shared_ptr<TestFbxMesh> testFbxMesh);
void SetName(const char* name);
protected:
std::shared_ptr<TestFbxMesh> m_testFbxMesh;
AZStd::string m_name;
};
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <SceneAPI/FbxSceneBuilder/Tests/TestFbxSkin.h>
#include <SceneAPI/FbxSceneBuilder/Tests/TestFbxNode.h>
namespace AZ
{
namespace FbxSDKWrapper
{
const char* TestFbxSkin::GetName() const
{
return m_name.c_str();
}
int TestFbxSkin::GetClusterCount() const
{
return aznumeric_caster(m_links.size());
}
int TestFbxSkin::GetClusterControlPointIndicesCount(int index) const
{
return aznumeric_caster(m_controlPointIndices[index].size());
}
int TestFbxSkin::GetClusterControlPointIndex(int clusterIndex, int pointIndex) const
{
return m_controlPointIndices[clusterIndex][pointIndex];
}
double TestFbxSkin::GetClusterControlPointWeight(int clusterIndex, int pointIndex) const
{
return m_weights[clusterIndex][pointIndex];
}
AZStd::shared_ptr<const FbxNodeWrapper> TestFbxSkin::GetClusterLink(int index) const
{
return m_links[index];
}
void TestFbxSkin::SetName(const char* name)
{
m_name = name;
}
void TestFbxSkin::CreateSkinWeightData(AZStd::vector<AZStd::string>& boneNames, AZStd::vector<AZStd::vector<double>>& weights, AZStd::vector<AZStd::vector<int>>& controlPointIndices)
{
m_links.resize(boneNames.size());
for (size_t linkIndex = 0; linkIndex < boneNames.size(); ++linkIndex)
{
m_links[linkIndex] = AZStd::make_shared<FbxSDKWrapper::TestFbxNode>();
m_links[linkIndex]->SetName(boneNames[linkIndex].c_str());
}
m_weights = weights;
m_controlPointIndices = controlPointIndices;
}
void TestFbxSkin::CreateExpectSkinWeightData(AZStd::vector<AZStd::vector<int>>& boneIds, AZStd::vector<AZStd::vector<float>>& weights)
{
m_expectedBoneIds = boneIds;
m_expectedWeights = weights;
}
size_t TestFbxSkin::GetExpectedVertexCount() const
{
return m_expectedBoneIds.size();
}
size_t TestFbxSkin::GetExpectedLinkCount(size_t vertexIndex) const
{
return m_expectedBoneIds[vertexIndex].size();
}
int TestFbxSkin::GetExpectedSkinLinkBoneId(size_t vertexIndex, size_t linkIndex) const
{
return m_expectedBoneIds[vertexIndex][linkIndex];
}
float TestFbxSkin::GetExpectedSkinLinkWeight(size_t vertextIndex, size_t linkIndex) const
{
return m_expectedWeights[vertextIndex][linkIndex];
}
}
}
@@ -0,0 +1,49 @@
#pragma once
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <SceneAPI/FbxSDKWrapper/FbxSkinWrapper.h>
#include <SceneAPI/FbxSceneBuilder/Tests/TestFbxNode.h>
namespace AZ
{
namespace FbxSDKWrapper
{
class TestFbxSkin
: public FbxSkinWrapper
{
public:
~TestFbxSkin() override = default;
const char* GetName() const override;
int GetClusterCount() const override;
int GetClusterControlPointIndicesCount(int index) const override;
int GetClusterControlPointIndex(int clusterIndex, int pointIndex) const override;
double GetClusterControlPointWeight(int clusterIndex, int pointIndex) const override;
AZStd::shared_ptr<const FbxNodeWrapper> GetClusterLink(int index) const override;
void SetName(const char* name);
void CreateSkinWeightData(AZStd::vector<AZStd::string>& boneNames, AZStd::vector<AZStd::vector<double>>& weights, AZStd::vector<AZStd::vector<int>>& controlPointIndices);
void CreateExpectSkinWeightData(AZStd::vector<AZStd::vector<int>>& boneIds, AZStd::vector<AZStd::vector<float>>& weights);
size_t GetExpectedVertexCount() const;
size_t GetExpectedLinkCount(size_t vertexIndex) const;
int GetExpectedSkinLinkBoneId(size_t vertexIndex, size_t linkIndex) const;
float GetExpectedSkinLinkWeight(size_t vertexIndex, size_t linkIndex) const;
protected:
AZStd::string m_name;
AZStd::vector<AZStd::shared_ptr<FbxSDKWrapper::TestFbxNode>> m_links;
AZStd::vector<AZStd::vector<double>> m_weights;
AZStd::vector<AZStd::vector<int>> m_controlPointIndices;
AZStd::vector<AZStd::vector<int>> m_expectedBoneIds;
AZStd::vector<AZStd::vector<float>> m_expectedWeights;
};
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzTest/AzTest.h>
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/Memory/SystemAllocator.h>
class SceneBuilderTestEnvironment
: public AZ::Test::ITestEnvironment
{
public:
virtual ~SceneBuilderTestEnvironment()
{}
protected:
void SetupEnvironment() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
sceneCoreModule = AZ::DynamicModuleHandle::Create("SceneCore");
AZ_Assert(sceneCoreModule, "SceneBuilder unit tests failed to create SceneCore module.");
bool loaded = sceneCoreModule->Load(false);
AZ_Assert(loaded, "SceneBuilder unit tests failed to load SceneCore module.");
auto init = sceneCoreModule->GetFunction<AZ::InitializeDynamicModuleFunction>(AZ::InitializeDynamicModuleFunctionName);
AZ_Assert(init, "SceneBuilder unit tests failed to find the initialization function the SceneCore module.");
(*init)(AZ::Environment::GetInstance());
sceneDataModule = AZ::DynamicModuleHandle::Create("SceneData");
AZ_Assert(sceneDataModule, "SceneData unit tests failed to create SceneData module.");
loaded = sceneDataModule->Load(false);
AZ_Assert(loaded, "SceneBuilder unit tests failed to load SceneData module.");
init = sceneDataModule->GetFunction<AZ::InitializeDynamicModuleFunction>(AZ::InitializeDynamicModuleFunctionName);
AZ_Assert(init, "SceneBuilder unit tests failed to find the initialization function the SceneData module.");
(*init)(AZ::Environment::GetInstance());
}
void TeardownEnvironment() override
{
auto uninit = sceneDataModule->GetFunction<AZ::UninitializeDynamicModuleFunction>(AZ::UninitializeDynamicModuleFunctionName);
AZ_Assert(uninit, "SceneBuilder unit tests failed to find the uninitialization function the SceneData module.");
(*uninit)();
sceneDataModule.reset();
uninit = sceneCoreModule->GetFunction<AZ::UninitializeDynamicModuleFunction>(AZ::UninitializeDynamicModuleFunctionName);
AZ_Assert(uninit, "SceneBuilder unit tests failed to find the uninitialization function the SceneCore module.");
(*uninit)();
sceneCoreModule.reset();
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
private:
AZStd::unique_ptr<AZ::DynamicModuleHandle> sceneCoreModule;
AZStd::unique_ptr<AZ::DynamicModuleHandle> sceneDataModule;
};
AZ_UNIT_TEST_HOOK(new SceneBuilderTestEnvironment);
@@ -0,0 +1,55 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
SceneBuilderConfiguration.h
SceneImportRequestHandler.h
SceneImportRequestHandler.cpp
SceneImporter.h
SceneImporter.cpp
SceneSystem.h
SceneSystem.cpp
ImportContexts/ImportContexts.h
ImportContexts/ImportContexts.cpp
ImportContexts/AssImpImportContexts.h
ImportContexts/AssImpImportContexts.cpp
Importers/Utilities/AssImpMeshImporterUtilities.h
Importers/Utilities/AssImpMeshImporterUtilities.cpp
Importers/Utilities/RenamedNodesMap.h
Importers/Utilities/RenamedNodesMap.cpp
Importers/AssImpAnimationImporter.h
Importers/AssImpAnimationImporter.cpp
Importers/AssImpBitangentStreamImporter.h
Importers/AssImpBitangentStreamImporter.cpp
Importers/AssImpBlendShapeImporter.h
Importers/AssImpBlendShapeImporter.cpp
Importers/AssImpBoneImporter.h
Importers/AssImpBoneImporter.cpp
Importers/AssImpColorStreamImporter.h
Importers/AssImpColorStreamImporter.cpp
Importers/AssImpImporterUtilities.h
Importers/AssImpImporterUtilities.cpp
Importers/AssImpMaterialImporter.h
Importers/AssImpMaterialImporter.cpp
Importers/AssImpMeshImporter.h
Importers/AssImpMeshImporter.cpp
Importers/AssImpSkinImporter.h
Importers/AssImpSkinImporter.cpp
Importers/AssImpSkinWeightsImporter.h
Importers/AssImpSkinWeightsImporter.cpp
Importers/AssImpTangentStreamImporter.h
Importers/AssImpTangentStreamImporter.cpp
Importers/AssImpTransformImporter.h
Importers/AssImpTransformImporter.cpp
Importers/AssImpUvMapImporter.h
Importers/AssImpUvMapImporter.cpp
Importers/AssImpAnimationImporter.h
Importers/AssImpAnimationImporter.cpp
Importers/ImporterUtilities.h
Importers/ImporterUtilities.cpp
Importers/ImporterUtilities.inl
)
@@ -0,0 +1,10 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
DllMain.cpp
)
@@ -0,0 +1,12 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
Tests/TestsMain.cpp
Tests/Importers/SceneImporterUtilitiesTests.cpp
Tests/Importers/Utilities/RenamedNodesMapTests.cpp
)