Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,153 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <Behaviors/LoggingGroupBehavior.h>
#include <Groups/LoggingGroup.h>
namespace SceneLoggingExample
{
// Reflection is a basic requirement for components. For behaviors, you can often keep the Reflect()
// function simple because the SceneAPI just needs to be able to find the component. For more details
// on the Reflect() function, see LoggingGroup.cpp.
void LoggingGroupBehavior::Reflect(AZ::ReflectContext* context)
{
// The data and UI elements used in the SceneAPI are not components, but they need to be reflected
// for serialization and the Scene Settings to work. This can done at any point in the gem, but the
// behavior that controls the data is a good place for this. Because the LoggingGroupBehavior controls
// the LoggingGroup, we will register it here.
LoggingGroup::Reflect(context);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<LoggingGroupBehavior, AZ::SceneAPI::SceneCore::BehaviorComponent>()->Version(1);
}
}
// Later in this example, messages that deal with manifest changes and loading files will be used
// to create the various ways that the behavior controls settings. Before any events can be sent
// to the behavior, it first needs to be connected to the EBuses that it monitors.
void LoggingGroupBehavior::Activate()
{
AZ::SceneAPI::Events::ManifestMetaInfoBus::Handler::BusConnect();
AZ::SceneAPI::Events::AssetImportRequestBus::Handler::BusConnect();
}
// Disconnect from the EBuses when this behavior is no longer active.
void LoggingGroupBehavior::Deactivate()
{
AZ::SceneAPI::Events::ManifestMetaInfoBus::Handler::BusDisconnect();
AZ::SceneAPI::Events::AssetImportRequestBus::Handler::BusDisconnect();
}
// This behavior will control the logging for the UI, so let's begin by registering the LoggingGroup with the UI under a new
// "Logging" tab and ignore the position of the tab for now. This will add a new tab to the Scene Settings window. The tab
// will have a single button to add a LoggingGroup. If additional groups are registered under the same tab name, the button
// will be changed to a drop-down button and allow the registered groups to be added.
//
// The scene is passed as one of the arguments so that the manifest and/or the graph can be inspected to determine if a group
// should be added. For example, if the graph doesn't contain any meshes, the mesh group can be left out. This helps prevent
// users from adding groups that have no effect.
void LoggingGroupBehavior::GetCategoryAssignments(CategoryRegistrationList& categories, [[maybe_unused]] const AZ::SceneAPI::Containers::Scene& scene)
{
categories.emplace_back("Logging", LoggingGroup::TYPEINFO_Uuid());
}
// When a scene is loaded for the first time (for example, from an .fbx file), there won't be a manifest (.assetinfo file).
// If the scene was loaded previously, there might be a manifest that requires updates because it contains values that no
// longer match the graph. This EBus call gives a one-time opportunity right after loading has completed to update the manifest
// or to add data to a new one.
//
// In this example, let's add a LoggingGroup to a new manifest only. Don't forget to remove the manifest (.assetinfo file)
// for your test scene file. Otherwise, the following code won't trigger.
AZ::SceneAPI::Events::ProcessingResult LoggingGroupBehavior::UpdateManifest(AZ::SceneAPI::Containers::Scene& scene,
ManifestAction action, [[maybe_unused]] RequestingApplication requester)
{
if (action == ManifestAction::ConstructDefault)
{
AZStd::shared_ptr<LoggingGroup> group = AZStd::make_shared<LoggingGroup>();
// This might not be the only behavior that wants to make modifications to the new group. An example is a material
// behavior that wants to add a material rule when a mesh group is created. By calling the EBus below, other behaviors
// get a chance to change or add their own values. Listening to this EBus is also a good place to add any settings to
// the new group instead of doing it here. This is because this EBus is also called when tools such as the UI create
// a new group, which keeps initialization in one place.
AZ::SceneAPI::Events::ManifestMetaInfoBus::Broadcast(
&AZ::SceneAPI::Events::ManifestMetaInfoBus::Events::InitializeObject, scene, *group);
if (scene.GetManifest().AddEntry(AZStd::move(group)))
{
// Let the SceneAPI know that a LoggingGroup has been successfully added.
return AZ::SceneAPI::Events::ProcessingResult::Success;
}
else
{
// It wasn't possible to add the new logging group, so let the SceneAPI know that
// a problem was encountered. Don't forget to also tell the user what is going on,
// because this will cause the loading to fail.
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Unable to add a new logging group.");
return AZ::SceneAPI::Events::ProcessingResult::Failure;
}
}
// In any other situation, there's no plan to do anything so tell the SceneAPI to ignore this behavior.
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
// When a new manifest object is created, the caller can choose to allow other behaviors to change or add their own data, such
// as rules to a group. The EBus call in the above function shows a typical use case. Using InitializeObject() provides a more
// powerful alternative to default values. It allows domain logic to be spread to appropriate behaviors, but also allows general
// awareness of the manifest and graph to select default values that are more appropriate to the user.
//
// For this example, let's use the passed-in manifest to look for the last LoggingGroup in the manifest and use the log setting that
// is its opposite. When viewing this in the Scene Settings window, "Log processing events" will be off when adding a new logging group.
// The one directly above it is on, and vice versa.
void LoggingGroupBehavior::InitializeObject(const AZ::SceneAPI::Containers::Scene& scene, AZ::SceneAPI::DataTypes::IManifestObject& target)
{
// If the item being added isn't a LoggingGroup, ignore it.
if (!target.RTTI_IsTypeOf(LoggingGroup::TYPEINFO_Uuid()))
{
return;
}
LoggingGroup* newGroup = azrtti_cast<LoggingGroup*>(&target);
AZ_Assert(newGroup, "Manifest object has been identified as LoggingGroup, but failed to cast to it.");
// First create a view that only contains instances that exactly match LoggingGroups. Use MakeDerivedFilterView() to do
// the same for any instances that implement a specific interface and/or base class. For more details on using iterators
// to get data from the manifest and graph, see ExportTrackingProcessor.cpp.
auto values = scene.GetManifest().GetValueStorage();
auto view = AZ::SceneAPI::Containers::MakeExactFilterView<LoggingGroup>(values);
// Find the last LoggingGroup in the manifest.
auto last = view.begin();
while (AZStd::next(last) != view.end())
{
++last;
}
// Only take the values if there's actually another LoggingGroup in the manifest.
if (last != view.end())
{
newGroup->ShouldLogProcessingEvents(!last->DoesLogProcessingEvents());
}
// Let's also set a default name for this group. Groups often match one-to-one with the file that they output.
// For example, a Mesh Group will produce a .cgf file with the same name. If the name is used as a file name,
// it is important to check whether it's a valid path name and isn't duplicating another name.
const size_t size = AZStd::distance(view.begin(), view.end());
newGroup->SetName(AZStd::string::format("Logger_%zu", size));
}
} // namespace SceneLoggingExample
@@ -0,0 +1,44 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SceneAPI/SceneCore/Components/BehaviorComponent.h>
#include <SceneAPI/SceneCore/Events/ManifestMetaInfoBus.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
namespace SceneLoggingExample
{
// The LoggingGroupBehavior shows how a behavior can be written that monitors
// manifest activity and reacts to it in order to setup default values for
// manifest entries. It also demonstrates how to register new UI elements with
// the SceneAPI.
class LoggingGroupBehavior
: public AZ::SceneAPI::SceneCore::BehaviorComponent
, public AZ::SceneAPI::Events::ManifestMetaInfoBus::Handler
, public AZ::SceneAPI::Events::AssetImportRequestBus::Handler
{
public:
AZ_COMPONENT(LoggingGroupBehavior, "{4DE18DD7-5C40-4A14-8CD7-67162171DCAA}", AZ::SceneAPI::SceneCore::BehaviorComponent);
~LoggingGroupBehavior() override = default;
void Activate();
void Deactivate();
static void Reflect(AZ::ReflectContext* context);
void GetCategoryAssignments(CategoryRegistrationList& categories, const AZ::SceneAPI::Containers::Scene& scene) override;
AZ::SceneAPI::Events::ProcessingResult UpdateManifest(AZ::SceneAPI::Containers::Scene& scene,
ManifestAction action, RequestingApplication requester) override;
void InitializeObject(const AZ::SceneAPI::Containers::Scene& scene, AZ::SceneAPI::DataTypes::IManifestObject& target) override;
};
} // namespace SceneLoggingExample
@@ -0,0 +1,47 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
if (NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
ly_add_target(
NAME SceneLoggingExample.Static STATIC
NAMESPACE Gem
FILES_CMAKE
sceneloggingexample_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
PUBLIC
Include
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::SceneCore
Legacy::CryCommon
)
ly_add_target(
NAME SceneLoggingExample ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
OUTPUT_NAME Gem.SceneLoggingExample.35d8f6e49ae04c9382c61a42d4355c2f.v0.1.0
FILES_CMAKE
sceneloggingexample_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Gem::SceneLoggingExample.Static
)
@@ -0,0 +1,147 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <Groups/LoggingGroup.h>
namespace SceneLoggingExample
{
const char* LoggingGroup::s_disabledOption = "No logging";
LoggingGroup::LoggingGroup()
: m_logProcessingEvents(true)
{
}
// The data in groups will be saved to the manifest file and will be reflected in the Scene Settings
// window. For those systems to do their work, the LoggingGroup needs to tell a bit more about itself
// than the other classes in this example.
void LoggingGroup::Reflect(AZ::ReflectContext* context)
{
// There are different kind of contexts, but for groups and rules, the only one that's
// interesting is the SerializeContext. Check if the provided context is one.
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (!serializeContext)
{
return;
}
// Next, specify the fields that need to be serialized to and from a manifest. This allows new
// fields to be stored and loaded from the manifest (.assetinfo file). These are also needed
// for the edit context below.
serializeContext->Class<LoggingGroup, AZ::SceneAPI::DataTypes::IManifestObject>()->Version(1)
->Field("groupName", &LoggingGroup::m_groupName)
->Field("graphLogRoot", &LoggingGroup::m_graphLogRoot)
->Field("logProcessingEvents", &LoggingGroup::m_logProcessingEvents);
// The EditContext allows you to add additional meta information to the previously registered fields.
// This meta information will be used in the Scene Settings, which uses the Reflected Property Editor.
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<LoggingGroup>("Logger", "Add additional logging to the SceneAPI.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute("AutoExpand", true)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
->DataElement(AZ::Edit::UIHandlers::Default, &LoggingGroup::m_groupName,
"Name", "The name of the group will be used in the log")
// The Reflected Property Editor can pick a default editor for many types. However, for the string
// that will store the selected node, a more specialized editor is needed. NodeListSelection is
// one such editor and it is SceneGraph-aware. It allows the selection of a specific node from the
// graph and the selectable items can be filtered. You can find other available editors in the
// "RowWidgets"-folder of the SceneUI.
->DataElement(AZ_CRC("NodeListSelection", 0x45c54909), &LoggingGroup::m_graphLogRoot,
"Graph log root", "Select the node in the graph to list children of to the log, or disable logging.")
->Attribute(AZ_CRC("DisabledOption", 0x6cd17278), s_disabledOption)
// Nodes in the SceneGraph can be marked as endpoints. To the graph, this means that these nodes
// are not allowed to have children. While not a true one-to-one mapping, endpoints often act as
// attributes to a node. For example, a transform can be marked as an endpoint. This means that
// it applies its transform to the parent object like an attribute. If the transform is not marked
// as an endpoint, then it is the root transform for the group(s) that are its children.
->Attribute(AZ_CRC("ExcludeEndPoints", 0x53bd29cc), true)
->DataElement(AZ::Edit::UIHandlers::Default, &LoggingGroup::m_logProcessingEvents,
"Log processing events", "Log processing events as they happen.");
}
}
void LoggingGroup::SetName(const AZStd::string& name)
{
m_groupName = name;
}
void LoggingGroup::SetName(AZStd::string&& name)
{
m_groupName = AZStd::move(name);
}
const AZStd::string& LoggingGroup::GetName() const
{
return m_groupName;
}
// Groups need to provide a unique id that will be used to create the final sub id for the product
// build using this group. While new groups created through the UI can remain fully random, it's
// important that ids used for defaults are recreated the same way every time. It's recommended this
// is done by using the source guid of the file and calling DataTypes::Utilities::CreateStableUuid.
// If the id doesn't remain stable between updates this will cause the sub id to change which will in
// turn cause the objects links to those products to break.
//
// As this example doesn't have a product, the id is not important so just always return the randomly
// generated id.
const AZ::Uuid& LoggingGroup::GetId() const
{
return m_id;
}
// Groups have the minimal amount of options to generate a working product in the cache and nothing more.
// A group might not be perfect or contain all the data the user would expect, but it will load in the
// engine and not crash. You can add additional settings to fine tune the exporting process in the form of
// rules (or "modifiers" in the Scene Settings UI). Rules usually group a subset of settings together,
// such as control of physics or level of detail. This approach keeps UI clutter to a minimum by only
// presenting options that are relevant for the user's file, while still providing access to all available
// settings.
//
// By using the "GetAvailableModifiers" in the ManifestMetaInfoHandler EBus, it's possible to filter out
// any options that are not relevant to the group. For example, if a group only allows for a single instance
// of a rule, it would no longer be added to this call if there is already one. Because the logging doesn't
// require any rules, empty defaults are provided.
AZ::SceneAPI::Containers::RuleContainer& LoggingGroup::GetRuleContainer()
{
return m_ruleContainer;
}
const AZ::SceneAPI::Containers::RuleContainer& LoggingGroup::GetRuleContainerConst() const
{
return m_ruleContainer;
}
const AZStd::string& LoggingGroup::GetGraphLogRoot() const
{
return m_graphLogRoot;
}
bool LoggingGroup::DoesLogGraph() const
{
return m_graphLogRoot.compare(s_disabledOption) != 0;
}
bool LoggingGroup::DoesLogProcessingEvents() const
{
return m_logProcessingEvents;
}
void LoggingGroup::ShouldLogProcessingEvents(bool state)
{
m_logProcessingEvents = state;
}
} // namespace SceneLoggingExample
@@ -0,0 +1,70 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IGroup.h>
namespace AZ
{
class ReflectContext;
}
namespace SceneLoggingExample
{
// The LoggingGroup class contains the settings that will be interpreted by the exporter.
// Groups typically contain settings and information only. They rarely implement any advanced logic.
//
// Groups tend to have a one-to-one relationship to the target format. For example, every mesh group
// will produce a .cgf file in the cache. Groups also aim to be the most basic form of the required data,
// providing the minimum information that is needed to create a valid product in the cache.
//
// To further fine tune the group, you can add rules (also called modifiers). For example, you can add a rule to
// control the world matrix.
class LoggingGroup : public AZ::SceneAPI::DataTypes::IGroup
{
public:
LoggingGroup();
~LoggingGroup() override = default;
AZ_RTTI(LoggingGroup, "{A5ECF95D-2E84-4574-BF93-09E469E2BA4E}", AZ::SceneAPI::DataTypes::IGroup);
AZ_CLASS_ALLOCATOR(LoggingGroup, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
void SetName(const AZStd::string& name);
void SetName(AZStd::string&& name);
const AZStd::string& GetName() const override;
const AZ::Uuid& GetId() const override;
AZ::SceneAPI::Containers::RuleContainer& GetRuleContainer() override;
const AZ::SceneAPI::Containers::RuleContainer& GetRuleContainerConst() const override;
const AZStd::string& GetGraphLogRoot() const;
bool DoesLogGraph() const;
bool DoesLogProcessingEvents() const;
void ShouldLogProcessingEvents(bool state);
protected:
AZ::SceneAPI::Containers::RuleContainer m_ruleContainer;
AZStd::string m_groupName;
AZStd::string m_graphLogRoot;
AZ::Uuid m_id;
bool m_logProcessingEvents;
static const char* s_disabledOption;
};
} // namespace SceneLoggingExample
@@ -0,0 +1,192 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <Processors/ExportTrackingProcessor.h>
#include <Groups/LoggingGroup.h>
namespace SceneLoggingExample
{
ExportTrackingProcessor::ExportTrackingProcessor()
{
// The scene conversion and exporting process uses the CallProcessorBus to move data and trigger additional work.
// The CallProcessorBus operates differently than typical EBuses because it doesn't have a specific set of functions
// that you can call. Instead, it works like a pseudo-remote procedure call, where the arguments for what would
// normally be a function are stored in a context.
//
// The CallProcessorBus provides a single place to register and trigger the context calls. Based on the type
// of context, the appropriate functionality is executed. To make it easier to work with, a binding layer
// called CallProcessorBinder allows binding to a function that takes a context as an argument and performs
// all the routing. One of the benefits of this approach is that it provides several places to hook custom code
// into without having to update existing code. For example, you can use this approach to write additional information
// to a mesh file without having to change how the .cgf exporter works.
//
// The example below attaches the PrepareForExport function to the PreExportEventContext so that this context
// is sent to the CallProcessorBus at the start of every conversion and export process.
BindToCall(&ExportTrackingProcessor::PrepareForExport);
// By default, the CallProcessorBinder will only activate if the context exactly matches the argument of the
// bound function. That setup is often desired to avoid receiving many unrelated events. However, this example
// uses "Derived" and binds to the ICallContext so that all events are printed. Note that many events get fired
// multiple times due to multiple phases (pre, active, and post).
BindToCall(&ExportTrackingProcessor::ContextCallback, AZ::SceneAPI::Events::CallProcessorBinder::TypeMatch::Derived);
}
// Reflection is a basic requirement for components. For Exporting components, you can often keep the Reflect() function
// simple because the SceneAPI just needs to be able to find the component. For more details on the Reflect() function,
// see LoggingGroup.cpp.
void ExportTrackingProcessor::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<ExportTrackingProcessor, AZ::SceneAPI::SceneCore::ExportingComponent>()->Version(1);
}
}
// This function is now bound to the CallProcessorBinder, so it will be called as soon as exporting starts. It is a good point
// at which to look at the available groups and see if there are groups that need to log the scene graph.
AZ::SceneAPI::Events::ProcessingResult ExportTrackingProcessor::PrepareForExport(AZ::SceneAPI::Events::PreExportEventContext& context)
{
// Before doing any work, the manifest must be searched for instructions about what needs to be done. The instructions
// are in the form of groups and rules. In this example, we use this opportunity to log the scene graphs that are
// listed in every logging group.
//
// In this example, the manifest is cached for later use. This is typically not recommended because multiple builders can
// be running at the same time, resulting in callbacks from multiple exports that are in flight. In general, you should
// pass in any required information as a member of the context.
m_manifest = &context.GetScene().GetManifest();
// The manifest is a flat list of IManifestObjects and relies on AZ_RTTI to determine its content. Content can be retrieved
// through an index-based approach or an iterator approach. The index-based approach tends to be easier to understand but
// it also requires you to work with more code. The iterator has more complex syntax and can produce more complicated compile
// errors, but it has several utilities that make it more concise to work with and often makes code that better communicates
// intention. To provide examples of both cases, the index-based approach is used below, and the iterator approach is used in
// the ContextCallback function.
size_t count = m_manifest->GetEntryCount();
for (size_t i = 0; i < count; ++i)
{
AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IManifestObject> entry = m_manifest->GetValue(i);
// The azrtti_cast is a run-time type-aware cast that will return a nullptr if the provided type
// can't be cast to the target class. That principle is used here to filter for LoggingGroups only.
const LoggingGroup* group = azrtti_cast<const LoggingGroup*>(entry.get());
if (group)
{
if (group->DoesLogGraph())
{
// For every group, write out the graph information, starting at the node the user selected.
LogGraph(context.GetScene().GetGraph(), group->GetGraphLogRoot());
}
}
}
return AZ::SceneAPI::Events::ProcessingResult::Success;
}
// In the constructor, this function was bound to accept any contexts that are derived from ICallContext, which is the base
// for all CallProcessorBus events. This allows for monitoring of everything that happens during conversion and exporting.
AZ::SceneAPI::Events::ProcessingResult ExportTrackingProcessor::ContextCallback([[maybe_unused]] AZ::SceneAPI::Events::ICallContext& context)
{
// PrepareForExport demonstrated getting data from the manifest using the index-based approach. The code below demonstrates the
// iterator approach by getting a view (a begin- and end-iterator) and creating a filtered view on top of it.
auto manifestValues = m_manifest->GetValueStorage();
auto view = AZ::SceneAPI::Containers::MakeExactFilterView<LoggingGroup>(manifestValues);
// Now that the filtered view of the manifest is constructed, the loop below will list only LoggingGroups. Groups typically
// map one-to-one to an output file. This is not a hard requirement, but it is most often the case. In that case, it is typical
// for multiple groups to be individually exported to their own file. Most groups will also have rules (also called modifiers)
// that add fine-grained control to the conversion process. Usually this is in one particular area such as the world matrix or physics.
for (const auto& it : view)
{
if (it.DoesLogProcessingEvents())
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "ExportEvent (%s): %s", it.GetName().c_str(), context.RTTI_GetTypeName());
}
}
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
// With the SceneAPI, the order in which an EBus calls its listeners is mostly random. This generally isn't a problem because most work
// is done in isolation. If there is a dependency, we recommend that you break a call into multiple smaller calls, but this isn't always
// an option. For example, perhaps there is no source code available for third-party extensions or you are trying to avoid making code
// changes to the engine/editor. For those situations, the Call Processor allows you to specify a priority to make sure that a call is made
// before or after all other listeners have done their work.
//
// In this example, we want the log messages to be printed before any other listeners do their work and potentially print their data.
// To accomplish this, we set the priority to the highest available number.
uint8_t ExportTrackingProcessor::GetPriority() const
{
return EarliestProcessing;
}
// During the loading process, an in-memory representation of the scene is stored inside the SceneGraph. The SceneCore library
// provides several interfaces that you can use as a basis for data that helps establish a common vocabulary for the various parts
// of the SceneAPI. The SceneData library provides an optional set of implementations of these interfaces for your convenience. Similar
// to the manifest, the SceneGraph can provide its data through an index-based or an iterator-based approach.
void ExportTrackingProcessor::LogGraph(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZStd::string& nodePath) const
{
namespace SceneViews = AZ::SceneAPI::Containers::Views;
// Between runs, the source scene files (for example, .fbx) can change. Storing indices to nodes can lead to unexpected behavior,
// so it is generally preferable to store the node path instead. This makes looking up nodes by name a common pattern. Rather than
// doing a linear search over the names, the SceneGraph has an optimized lookup of the node name.
AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(nodePath);
if (!nodeIndex.IsValid())
{
// Any SceneGraph is guaranteed to have at least a root node, even if it is otherwise empty. Note that not all loaders
// may choose to use this node. This can occasionally lead to an unexpected node at the top of the graph.
nodeIndex = graph.GetRoot();
}
// The SceneGraph stores its data in separate containers, such as a content list and a name list. The relationship between nodes is
// stored in a similar flat list. This allows iterating over the content in both a hierarchical and a linear way. Because hierarchical
// traversal is much more expensive than linear traversal, questions such as "list all entries of type X" are answered much more efficiently
// by using linear traversal.
auto nameStorage = graph.GetNameStorage();
auto contentStorage = graph.GetContentStorage();
// As described previously, the name and content of the graph are stored separately. However, sometimes both are needed when traversing
// the graph. To combine the two in a single iterator, you can use the pair iterator in the following way.
auto nameContentView = SceneViews::MakePairView(nameStorage, contentStorage);
// The SceneGraph has several iterators that help with traversing the graph in a hierarchical way:
// - SceneGraphUpwardsIterator - Traverses from a given node to the root of the graph.
// - SceneGraphDownwardsIterator - Traverses over all children of a given node either breadth-first or depth-first.
// - SceneGraphChildIterator - Traverses over the direct children of a node only.
// For this example, all nodes beneath the node that the user selected are listed so a downwards iterator is most appropriate.
auto graphDownwardsView = SceneViews::MakeSceneGraphDownwardsView<SceneViews::BreadthFirst>(graph, nodeIndex, nameContentView.begin(), true);
for (auto it = graphDownwardsView.begin(); it != graphDownwardsView.end(); ++it)
{
const char* path = it->first.GetPath();
const char* type = it->second ? it->second->RTTI_GetTypeName() : "No data";
// While it's generally preferable to stick with either index- or iterator-based traversal, there may be times where switching between one
// or the other becomes necessary. The SceneGraph provides utility functions to convert between the two approaches.
AZ::SceneAPI::Containers::SceneGraph::NodeIndex itNodeIndex = graph.ConvertToNodeIndex(it.GetHierarchyIterator());
// Nodes in the SceneGraph can be marked as endpoints. To the graph, this means that these nodes are not allowed to have children.
// While not a true one-to-one mapping, endpoints often act as attributes to a node. For example, a transform can be marked as an endpoint.
// This means that it applies its transform to the parent object like an attribute. If the transform is not marked as an endpoint, then it
// is the root transform for the group(s) that are its children.
bool isEndPoint = graph.IsNodeEndPoint(itNodeIndex);
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "'%s' '%s' contains data of type '%s'.", (isEndPoint ? "End point node" : "Node"), path, type);
}
}
} // namespace SceneLoggingExample
@@ -0,0 +1,57 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
#include <SceneAPI/SceneCore/Components/ExportingComponent.h>
#include <SceneAPI/SceneCore/Events/ExportEventContext.h>
namespace AZ
{
namespace SceneAPI
{
namespace Containers
{
class Scene;
class SceneGraph;
class SceneManifest;
}
}
}
namespace SceneLoggingExample
{
// The ExportTrackingProcessor class demonstrates how to use the ExportingComponent to listen to scene export events.
// It also shows how to collect data from a graph by traversing the graph in a hierarchical way.
class ExportTrackingProcessor
: public AZ::SceneAPI::SceneCore::ExportingComponent
{
public:
AZ_COMPONENT(ExportTrackingProcessor, "{EAD9C07A-60D5-4E48-8465-72034D326368}", AZ::SceneAPI::SceneCore::ExportingComponent);
ExportTrackingProcessor();
~ExportTrackingProcessor() override = default;
static void Reflect(AZ::ReflectContext* context);
protected:
AZ::SceneAPI::Events::ProcessingResult PrepareForExport(AZ::SceneAPI::Events::PreExportEventContext& context);
AZ::SceneAPI::Events::ProcessingResult ContextCallback(AZ::SceneAPI::Events::ICallContext& context);
uint8_t GetPriority() const override;
void LogGraph(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZStd::string& nodePath) const;
const AZ::SceneAPI::Containers::SceneManifest* m_manifest = nullptr;
};
} // namespace SceneLoggingExample
@@ -0,0 +1,134 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <Processors/LoadingTrackingProcessor.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
namespace SceneLoggingExample
{
LoadingTrackingProcessor::LoadingTrackingProcessor()
{
// For details about the CallProcessorBus and CallProcessorBinder, see ExportTrackingProcessor.cpp.
BindToCall(&LoadingTrackingProcessor::ContextCallback, AZ::SceneAPI::Events::CallProcessorBinder::TypeMatch::Derived);
}
// Reflection is a basic requirement for components. For Loading components, you can often keep the Reflect() function
// simple because the SceneAPI just needs to be able to find the component. For more details on the Reflect() function,
// see LoggingGroup.cpp.
void LoadingTrackingProcessor::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<LoadingTrackingProcessor, AZ::SceneAPI::SceneCore::LoadingComponent>()->Version(1);
}
}
// Later in this example, we will listen to and log messages that relate to file loading.
// Before this can happen, we must connect to the bus that sends the messages.
void LoadingTrackingProcessor::Activate()
{
AZ::SceneAPI::Events::AssetImportRequestBus::Handler::BusConnect();
// Forward the call to the LoadingComponent so that the call bindings get activated.
AZ::SceneAPI::SceneCore::LoadingComponent::Activate();
}
// Disconnect from the bus upon deactivation.
void LoadingTrackingProcessor::Deactivate()
{
AZ::SceneAPI::SceneCore::LoadingComponent::Deactivate();
// Forward the call to the LoadingComponent so that the call bindings get deactivated.
AZ::SceneAPI::Events::CallProcessorBus::Handler::BusDisconnect();
AZ::SceneAPI::Events::AssetImportRequestBus::Handler::BusDisconnect();
}
// Loading starts by announcing that loading will begin shortly. This provides an opportunity to prepare
// caches or to take any additional steps that are required before loading.
AZ::SceneAPI::Events::ProcessingResult LoadingTrackingProcessor::PrepareForAssetLoading(AZ::SceneAPI::Containers::Scene& /*scene*/,
RequestingApplication /*requester*/)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Preparing to load a scene.");
// This function doesn't contribute anything to the loading, so let the SceneAPI know that it can ignore its contributions.
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
// After a call to PrepareForAssetLoading has been dispatched, the scene file (for example, .fbx) will be loaded.
// This is normally what scene builders will be looking for. If the file has an extension that a scene builder
// understands, it will start reading the source file, convert the data, and store it in the scene. This is also
// true for loading the manifest file, which happens in this same pass.
//
// For this example, nothing is done because there's no data to read. We just echo the steps that are taken.
AZ::SceneAPI::Events::LoadingResult LoadingTrackingProcessor::LoadAsset(AZ::SceneAPI::Containers::Scene& /*scene*/,
[[maybe_unused]] const AZStd::string& path, const AZ::Uuid& /*guid*/, RequestingApplication /*requester*/)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Loading scene from '%s'.", path.c_str());
return AZ::SceneAPI::Events::LoadingResult::Ignored;
}
// After the scene file and manifest are loaded, we finalize the loading by making two calls: first to FinalizeAssetLoading()
// and then to UpdateManifest().
//
// FinalizeAssetLoading() is the best time to close out any temporary buffers, clear cache, patch pointers, and any
// other final steps that are required to put the graph in a valid state and perform any necessary cleanup.
// We also disconnect from the Call Processor bus so that we won't receive export events later. It is possible to
// make updates to the manifest in FinalizeAssetLoading(), but UpdateManifest() is a better place to do this.
void LoadingTrackingProcessor::FinalizeAssetLoading(AZ::SceneAPI::Containers::Scene& /*scene*/, RequestingApplication /*requester*/)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Finished loading scene.");
}
// UpdateManifest() provides additional information about the state of the manifest, such as if a default manifest is being
// built or an existing one is being updated. The SceneGraph is ready at this point, so this function can be used to create a
// new manifest or make corrections to an existing one.
AZ::SceneAPI::Events::ProcessingResult LoadingTrackingProcessor::UpdateManifest(AZ::SceneAPI::Containers::Scene& /*scene*/, ManifestAction action,
RequestingApplication /*requester*/)
{
switch (action)
{
case ManifestAction::ConstructDefault:
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Constructing a new manifest.");
break;
case ManifestAction::Update:
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Updating the manifest.");
break;
default:
AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow, "Unknown manifest update action.");
break;
}
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
// With the SceneAPI, the order in which an EBus calls its listeners is mostly random. This generally isn't a problem because most work
// is done in isolation. If there is a dependency, we recommend that you break a call into multiple smaller calls, but this isn't always
// an option. For example, perhaps there is no source code available for third-party extensions or you are trying to avoid making code
// changes to the engine/editor. For those situations, the Call Processor allows you to specify a priority to make sure that a call is made
// before or after all other listeners have done their work.
//
// In this example, we want the log messages to be printed before any other listeners do their work and potentially print their data.
// To accomplish this, we set the priority to the highest available number.
uint8_t LoadingTrackingProcessor::GetPriority() const
{
return EarliestProcessing;
}
// In the constructor, this function was bound to accept any contexts that are derived from ICallContext, which is the base
// for all CallProcessorBus events. This allows for monitoring of everything that happens during the loading process.
AZ::SceneAPI::Events::ProcessingResult LoadingTrackingProcessor::ContextCallback([[maybe_unused]] AZ::SceneAPI::Events::ICallContext& context)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "LoadEvent: %s", context.RTTI_GetTypeName());
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
} // namespace SceneLoggingExample
@@ -0,0 +1,49 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <SceneAPI/SceneCore/Components/LoadingComponent.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
namespace SceneLoggingExample
{
// The LoadingTrackingProcessor class demonstrates how to listen to EBus events that start
// and finalize the loading of scene files (such as .fbx files) and the manifest (.assetinfo
// file). It also shows the Call Processor events that can be fired during loading.
class LoadingTrackingProcessor
: public AZ::SceneAPI::SceneCore::LoadingComponent
, public AZ::SceneAPI::Events::AssetImportRequestBus::Handler
{
public:
AZ_COMPONENT(LoadingTrackingProcessor, "{E5E65E21-0BCD-4874-84B8-22E10CCAEE94}", AZ::SceneAPI::SceneCore::LoadingComponent);
LoadingTrackingProcessor();
~LoadingTrackingProcessor() override = default;
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
AZ::SceneAPI::Events::ProcessingResult PrepareForAssetLoading(AZ::SceneAPI::Containers::Scene& scene,
RequestingApplication requester) override;
AZ::SceneAPI::Events::LoadingResult LoadAsset(AZ::SceneAPI::Containers::Scene& scene,
const AZStd::string& path, const AZ::Uuid& guid, RequestingApplication requester) override;
void FinalizeAssetLoading(AZ::SceneAPI::Containers::Scene& scene, RequestingApplication requester);
AZ::SceneAPI::Events::ProcessingResult UpdateManifest(AZ::SceneAPI::Containers::Scene& scene, ManifestAction action,
RequestingApplication requester) override;
uint8_t GetPriority() const override;
AZ::SceneAPI::Events::ProcessingResult ContextCallback(AZ::SceneAPI::Events::ICallContext& context);
};
} // namespace SceneLoggingExample
@@ -0,0 +1,65 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <IGem.h>
#include <AzCore/Module/DynamicModuleHandle.h>
#include <Behaviors/LoggingGroupBehavior.h>
#include <Processors/LoadingTrackingProcessor.h>
#include <Processors/ExportTrackingProcessor.h>
namespace SceneLoggingExample
{
// The SceneLoggingExampleModule is the entry point for gems. To extend the SceneAPI, the
// logging, loading, and export components must be registered here.
//
// NOTE: The gem system currently does not support registering file extensions through the
// AssetImportRequest EBus.
class SceneLoggingExampleModule
: public CryHooksModule
{
public:
AZ_RTTI(SceneLoggingExampleModule, "{36AA9C0F-7976-40C7-AF54-C492AC5B16F6}", CryHooksModule);
SceneLoggingExampleModule()
: CryHooksModule()
{
// The SceneAPI libraries require specialized initialization. As early as possible, be
// sure to repeat the following two lines for any SceneAPI you want to use. Omitting these
// calls or making them too late can cause problems such as missing EBus events.
m_sceneCoreModule = AZ::DynamicModuleHandle::Create("SceneCore");
m_sceneCoreModule->Load(true);
m_descriptors.insert(m_descriptors.end(),
{
LoggingGroupBehavior::CreateDescriptor(),
LoadingTrackingProcessor::CreateDescriptor(),
ExportTrackingProcessor::CreateDescriptor()
});
}
// In this example, no system components are added. You can use system components
// to set global settings for this gem from the Project Configurator.
// For functionality that should always be available to the SceneAPI, we recommend
// that you use a BehaviorComponent instead.
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList {};
}
private:
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_sceneCoreModule;
};
} // namespace SceneLoggingExample
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM.
// The first parameter should be GemName_GemIdLower.
// The second should be the fully qualified name of the class above.
AZ_DECLARE_MODULE_CLASS(Gem_SceneLoggingExample, SceneLoggingExample::SceneLoggingExampleModule)
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Module/Module.h>
AZ_DECLARE_MODULE_CLASS(Gem_SceneLoggingExample, AZ::Module)
@@ -0,0 +1,22 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
../ReadMe.txt
Behaviors/LoggingGroupBehavior.h
Behaviors/LoggingGroupBehavior.cpp
Groups/LoggingGroup.h
Groups/LoggingGroup.cpp
Processors/ExportTrackingProcessor.h
Processors/ExportTrackingProcessor.cpp
Processors/LoadingTrackingProcessor.h
Processors/LoadingTrackingProcessor.cpp
)
@@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
SceneLoggingExampleModule.cpp
)