ExportTrackingProcessor.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <SceneAPI/SceneCore/Containers/Scene.h>
  9. #include <SceneAPI/SceneCore/Containers/SceneManifest.h>
  10. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  11. #include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
  12. #include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
  13. #include <SceneAPI/SceneCore/Utilities/Reporting.h>
  14. #include <Processors/ExportTrackingProcessor.h>
  15. #include <Groups/LoggingGroup.h>
  16. namespace SceneLoggingExample
  17. {
  18. ExportTrackingProcessor::ExportTrackingProcessor()
  19. {
  20. // The scene conversion and exporting process uses the CallProcessorBus to move data and trigger additional work.
  21. // The CallProcessorBus operates differently than typical EBuses because it doesn't have a specific set of functions
  22. // that you can call. Instead, it works like a pseudo-remote procedure call, where the arguments for what would
  23. // normally be a function are stored in a context.
  24. //
  25. // The CallProcessorBus provides a single place to register and trigger the context calls. Based on the type
  26. // of context, the appropriate functionality is executed. To make it easier to work with, a binding layer
  27. // called CallProcessorBinder allows binding to a function that takes a context as an argument and performs
  28. // all the routing. One of the benefits of this approach is that it provides several places to hook custom code
  29. // into without having to update existing code. For example, you can use this approach to write additional information
  30. // to a mesh file without having to change how the .cgf exporter works.
  31. //
  32. // The example below attaches the PrepareForExport function to the PreExportEventContext so that this context
  33. // is sent to the CallProcessorBus at the start of every conversion and export process.
  34. BindToCall(&ExportTrackingProcessor::PrepareForExport);
  35. // By default, the CallProcessorBinder will only activate if the context exactly matches the argument of the
  36. // bound function. That setup is often desired to avoid receiving many unrelated events. However, this example
  37. // uses "Derived" and binds to the ICallContext so that all events are printed. Note that many events get fired
  38. // multiple times due to multiple phases (pre, active, and post).
  39. BindToCall(&ExportTrackingProcessor::ContextCallback, AZ::SceneAPI::Events::CallProcessorBinder::TypeMatch::Derived);
  40. }
  41. // Reflection is a basic requirement for components. For Exporting components, you can often keep the Reflect() function
  42. // simple because the SceneAPI just needs to be able to find the component. For more details on the Reflect() function,
  43. // see LoggingGroup.cpp.
  44. void ExportTrackingProcessor::Reflect(AZ::ReflectContext* context)
  45. {
  46. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  47. if (serializeContext)
  48. {
  49. serializeContext->Class<ExportTrackingProcessor, AZ::SceneAPI::SceneCore::ExportingComponent>()->Version(1);
  50. }
  51. }
  52. // This function is now bound to the CallProcessorBinder, so it will be called as soon as exporting starts. It is a good point
  53. // at which to look at the available groups and see if there are groups that need to log the scene graph.
  54. AZ::SceneAPI::Events::ProcessingResult ExportTrackingProcessor::PrepareForExport(AZ::SceneAPI::Events::PreExportEventContext& context)
  55. {
  56. // Before doing any work, the manifest must be searched for instructions about what needs to be done. The instructions
  57. // are in the form of groups and rules. In this example, we use this opportunity to log the scene graphs that are
  58. // listed in every logging group.
  59. //
  60. // In this example, the manifest is cached for later use. This is typically not recommended because multiple builders can
  61. // be running at the same time, resulting in callbacks from multiple exports that are in flight. In general, you should
  62. // pass in any required information as a member of the context.
  63. m_manifest = &context.GetScene().GetManifest();
  64. // The manifest is a flat list of IManifestObjects and relies on AZ_RTTI to determine its content. Content can be retrieved
  65. // through an index-based approach or an iterator approach. The index-based approach tends to be easier to understand but
  66. // it also requires you to work with more code. The iterator has more complex syntax and can produce more complicated compile
  67. // errors, but it has several utilities that make it more concise to work with and often makes code that better communicates
  68. // intention. To provide examples of both cases, the index-based approach is used below, and the iterator approach is used in
  69. // the ContextCallback function.
  70. size_t count = m_manifest->GetEntryCount();
  71. for (size_t i = 0; i < count; ++i)
  72. {
  73. AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IManifestObject> entry = m_manifest->GetValue(i);
  74. // The azrtti_cast is a run-time type-aware cast that will return a nullptr if the provided type
  75. // can't be cast to the target class. That principle is used here to filter for LoggingGroups only.
  76. const LoggingGroup* group = azrtti_cast<const LoggingGroup*>(entry.get());
  77. if (group)
  78. {
  79. if (group->DoesLogGraph())
  80. {
  81. // For every group, write out the graph information, starting at the node the user selected.
  82. LogGraph(context.GetScene().GetGraph(), group->GetGraphLogRoot());
  83. }
  84. }
  85. }
  86. return AZ::SceneAPI::Events::ProcessingResult::Success;
  87. }
  88. // In the constructor, this function was bound to accept any contexts that are derived from ICallContext, which is the base
  89. // for all CallProcessorBus events. This allows for monitoring of everything that happens during conversion and exporting.
  90. AZ::SceneAPI::Events::ProcessingResult ExportTrackingProcessor::ContextCallback([[maybe_unused]] AZ::SceneAPI::Events::ICallContext& context)
  91. {
  92. // PrepareForExport demonstrated getting data from the manifest using the index-based approach. The code below demonstrates the
  93. // iterator approach by getting a view (a begin- and end-iterator) and creating a filtered view on top of it.
  94. auto manifestValues = m_manifest->GetValueStorage();
  95. auto view = AZ::SceneAPI::Containers::MakeExactFilterView<LoggingGroup>(manifestValues);
  96. // Now that the filtered view of the manifest is constructed, the loop below will list only LoggingGroups. Groups typically
  97. // 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
  98. // for multiple groups to be individually exported to their own file. Most groups will also have rules (also called modifiers)
  99. // that add fine-grained control to the conversion process. Usually this is in one particular area such as the world matrix or physics.
  100. for (const auto& it : view)
  101. {
  102. if (it.DoesLogProcessingEvents())
  103. {
  104. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "ExportEvent (%s): %s", it.GetName().c_str(), context.RTTI_GetTypeName());
  105. }
  106. }
  107. return AZ::SceneAPI::Events::ProcessingResult::Ignored;
  108. }
  109. // With the SceneAPI, the order in which an EBus calls its listeners is mostly random. This generally isn't a problem because most work
  110. // 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
  111. // an option. For example, perhaps there is no source code available for third-party extensions or you are trying to avoid making code
  112. // 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
  113. // before or after all other listeners have done their work.
  114. //
  115. // In this example, we want the log messages to be printed before any other listeners do their work and potentially print their data.
  116. // To accomplish this, we set the priority to the highest available number.
  117. uint8_t ExportTrackingProcessor::GetPriority() const
  118. {
  119. return EarliestProcessing;
  120. }
  121. // During the loading process, an in-memory representation of the scene is stored inside the SceneGraph. The SceneCore library
  122. // provides several interfaces that you can use as a basis for data that helps establish a common vocabulary for the various parts
  123. // of the SceneAPI. The SceneData library provides an optional set of implementations of these interfaces for your convenience. Similar
  124. // to the manifest, the SceneGraph can provide its data through an index-based or an iterator-based approach.
  125. void ExportTrackingProcessor::LogGraph(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZStd::string& nodePath) const
  126. {
  127. namespace SceneViews = AZ::SceneAPI::Containers::Views;
  128. // Between runs, the source scene files (for example, .fbx) can change. Storing indices to nodes can lead to unexpected behavior,
  129. // so it is generally preferable to store the node path instead. This makes looking up nodes by name a common pattern. Rather than
  130. // doing a linear search over the names, the SceneGraph has an optimized lookup of the node name.
  131. AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(nodePath);
  132. if (!nodeIndex.IsValid())
  133. {
  134. // Any SceneGraph is guaranteed to have at least a root node, even if it is otherwise empty. Note that not all loaders
  135. // may choose to use this node. This can occasionally lead to an unexpected node at the top of the graph.
  136. nodeIndex = graph.GetRoot();
  137. }
  138. // The SceneGraph stores its data in separate containers, such as a content list and a name list. The relationship between nodes is
  139. // stored in a similar flat list. This allows iterating over the content in both a hierarchical and a linear way. Because hierarchical
  140. // traversal is much more expensive than linear traversal, questions such as "list all entries of type X" are answered much more efficiently
  141. // by using linear traversal.
  142. auto nameStorage = graph.GetNameStorage();
  143. auto contentStorage = graph.GetContentStorage();
  144. // As described previously, the name and content of the graph are stored separately. However, sometimes both are needed when traversing
  145. // the graph. To combine the two in a single iterator, you can use the pair iterator in the following way.
  146. auto nameContentView = SceneViews::MakePairView(nameStorage, contentStorage);
  147. // The SceneGraph has several iterators that help with traversing the graph in a hierarchical way:
  148. // - SceneGraphUpwardsIterator - Traverses from a given node to the root of the graph.
  149. // - SceneGraphDownwardsIterator - Traverses over all children of a given node either breadth-first or depth-first.
  150. // - SceneGraphChildIterator - Traverses over the direct children of a node only.
  151. // For this example, all nodes beneath the node that the user selected are listed so a downwards iterator is most appropriate.
  152. auto graphDownwardsView = SceneViews::MakeSceneGraphDownwardsView<SceneViews::BreadthFirst>(graph, nodeIndex, nameContentView.begin(), true);
  153. for (auto it = graphDownwardsView.begin(); it != graphDownwardsView.end(); ++it)
  154. {
  155. // While it's generally preferable to stick with either index- or iterator-based traversal, there may be times where switching between one
  156. // or the other becomes necessary. The SceneGraph provides utility functions to convert between the two approaches.
  157. [[maybe_unused]] AZ::SceneAPI::Containers::SceneGraph::NodeIndex itNodeIndex = graph.ConvertToNodeIndex(it.GetHierarchyIterator());
  158. // Nodes in the SceneGraph can be marked as endpoints. To the graph, this means that these nodes are not allowed to have children.
  159. // 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.
  160. // 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
  161. // is the root transform for the group(s) that are its children.
  162. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "'%s' '%s' contains data of type '%s'.", (graph.IsNodeEndPoint(itNodeIndex) ? "End point node" : "Node"),
  163. it->first.GetPath(),
  164. it->second ? it->second->RTTI_GetTypeName() : "No data");
  165. }
  166. }
  167. } // namespace SceneLoggingExample