EditorAttachmentComponent.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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 "EditorAttachmentComponent.h"
  9. #include <AzCore/Serialization/SerializeContext.h>
  10. #include <AzCore/Serialization/EditContext.h>
  11. #include <AzCore/Math/Quaternion.h>
  12. #include <AzCore/Math/Transform.h>
  13. #include <LmbrCentral/Animation/SkeletalHierarchyRequestBus.h>
  14. #include <Atom/RPI.Reflect/Model/ModelAsset.h>
  15. namespace AZ
  16. {
  17. namespace Render
  18. {
  19. bool EditorAttachmentComponentVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
  20. {
  21. if (classElement.GetVersion() < 2)
  22. {
  23. float uniformScaleOffset = 1.0f;
  24. int scaleElementIndex = classElement.FindElement(AZ_CRC_CE("Scale Offset"));
  25. if (scaleElementIndex != -1)
  26. {
  27. AZ::Vector3 oldScaleValue = AZ::Vector3::CreateOne();
  28. AZ::SerializeContext::DataElementNode& dataElementNode = classElement.GetSubElement(scaleElementIndex);
  29. if (dataElementNode.GetData<AZ::Vector3>(oldScaleValue))
  30. {
  31. uniformScaleOffset = oldScaleValue.GetMaxElement();
  32. }
  33. classElement.RemoveElement(scaleElementIndex);
  34. }
  35. classElement.AddElementWithData(context, "Uniform Scale Offset", uniformScaleOffset);
  36. }
  37. return true;
  38. }
  39. void EditorAttachmentComponent::Reflect(AZ::ReflectContext* context)
  40. {
  41. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  42. if (serializeContext)
  43. {
  44. serializeContext->Class<EditorAttachmentComponent, EditorComponentBase>()
  45. ->Version(2, &EditorAttachmentComponentVersionConverter)
  46. ->Field("Target ID", &EditorAttachmentComponent::m_targetId)
  47. ->Field("Target Bone Name", &EditorAttachmentComponent::m_targetBoneName)
  48. ->Field("Position Offset", &EditorAttachmentComponent::m_positionOffset)
  49. ->Field("Rotation Offset", &EditorAttachmentComponent::m_rotationOffset)
  50. ->Field("Uniform Scale Offset", &EditorAttachmentComponent::m_uniformScaleOffset)
  51. ->Field("Attached Initially", &EditorAttachmentComponent::m_attachedInitially)
  52. ->Field("Scale Source", &EditorAttachmentComponent::m_scaleSource);
  53. AZ::EditContext* editContext = serializeContext->GetEditContext();
  54. if (editContext)
  55. {
  56. editContext
  57. ->Class<EditorAttachmentComponent>(
  58. "Attachment", "The Attachment component lets an entity attach to a bone on the skeleton of another entity")
  59. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  60. ->Attribute(AZ::Edit::Attributes::Category, "Animation")
  61. ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Attachment.svg")
  62. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Attachment.svg")
  63. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
  64. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  65. ->Attribute(
  66. AZ::Edit::Attributes::HelpPageURL,
  67. "https://o3de.org/docs/user-guide/components/reference/animation/attachment/")
  68. ->DataElement(0, &EditorAttachmentComponent::m_targetId, "Target entity", "Attach to this entity.")
  69. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnTargetIdChanged)
  70. ->DataElement(
  71. AZ::Edit::UIHandlers::ComboBox, &EditorAttachmentComponent::m_targetBoneName, "Joint name",
  72. "Attach to this joint on target entity.")
  73. ->Attribute(AZ::Edit::Attributes::StringList, &EditorAttachmentComponent::GetTargetBoneOptions)
  74. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnTargetBoneChanged)
  75. ->DataElement(
  76. 0, &EditorAttachmentComponent::m_positionOffset, "Position offset", "Local position offset from target bone")
  77. ->Attribute(AZ::Edit::Attributes::Suffix, "m")
  78. ->Attribute(AZ::Edit::Attributes::Step, 0.01f)
  79. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnTargetOffsetChanged)
  80. ->DataElement(
  81. 0, &EditorAttachmentComponent::m_rotationOffset, "Rotation offset", "Local rotation offset from target bone")
  82. ->Attribute(AZ::Edit::Attributes::Suffix, "deg")
  83. ->Attribute(AZ::Edit::Attributes::Step, 0.01f)
  84. ->Attribute(AZ::Edit::Attributes::Min, -AZ::RadToDeg(AZ::Constants::TwoPi))
  85. ->Attribute(AZ::Edit::Attributes::Max, AZ::RadToDeg(AZ::Constants::TwoPi))
  86. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnTargetOffsetChanged)
  87. ->DataElement(0, &EditorAttachmentComponent::m_uniformScaleOffset, "Scale offset", "Local scale offset from target entity")
  88. ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
  89. ->Attribute(AZ::Edit::Attributes::Min, 0.001f)
  90. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnTargetOffsetChanged)
  91. ->DataElement(
  92. 0, &EditorAttachmentComponent::m_attachedInitially, "Attached initially",
  93. "Whether to attach to target upon activation.")
  94. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnAttachedInitiallyChanged)
  95. ->DataElement(
  96. AZ::Edit::UIHandlers::ComboBox, &EditorAttachmentComponent::m_scaleSource, "Scaling",
  97. "How object scale should be determined. "
  98. "Use world scale = Attached object is scaled in world space, Use target entity scale = Attached object adopts "
  99. "scale of target entity., Use target bone scale = Attached object adopts scale of target entity/joint.")
  100. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnScaleSourceChanged)
  101. ->EnumAttribute(AttachmentConfiguration::ScaleSource::WorldScale, "Use world scale")
  102. ->EnumAttribute(AttachmentConfiguration::ScaleSource::TargetEntityScale, "Use target entity scale")
  103. ->EnumAttribute(AttachmentConfiguration::ScaleSource::TargetBoneScale, "Use target bone scale");
  104. }
  105. }
  106. }
  107. void EditorAttachmentComponent::Activate()
  108. {
  109. Base::Activate();
  110. m_boneFollower.Activate(GetEntity(), CreateAttachmentConfiguration(), /*targetCanAnimate=*/true);
  111. }
  112. void EditorAttachmentComponent::Deactivate()
  113. {
  114. m_boneFollower.Deactivate();
  115. Base::Deactivate();
  116. }
  117. void EditorAttachmentComponent::BuildGameEntity(AZ::Entity* gameEntity)
  118. {
  119. AttachmentComponent* component = gameEntity->CreateComponent<AttachmentComponent>();
  120. if (component)
  121. {
  122. component->m_initialConfiguration = CreateAttachmentConfiguration();
  123. }
  124. }
  125. AttachmentConfiguration EditorAttachmentComponent::CreateAttachmentConfiguration() const
  126. {
  127. AttachmentConfiguration configuration;
  128. configuration.m_targetId = m_targetId;
  129. configuration.m_targetBoneName = m_targetBoneName;
  130. configuration.m_targetOffset = GetTargetOffset();
  131. configuration.m_attachedInitially = m_attachedInitially;
  132. configuration.m_scaleSource = m_scaleSource;
  133. return configuration;
  134. }
  135. AZ::Transform EditorAttachmentComponent::GetTargetOffset() const
  136. {
  137. AZ::Transform offset = AZ::ConvertEulerDegreesToTransform(m_rotationOffset);
  138. offset.SetTranslation(m_positionOffset);
  139. offset.MultiplyByUniformScale(m_uniformScaleOffset);
  140. return offset;
  141. }
  142. AZStd::vector<AZStd::string> EditorAttachmentComponent::GetTargetBoneOptions() const
  143. {
  144. AZStd::vector<AZStd::string> names;
  145. // insert blank entry, so user may choose to bind to NO bone.
  146. names.push_back("");
  147. // track whether currently-set bone is found
  148. bool currentTargetBoneFound = false;
  149. // Get character and iterate over bones
  150. AZ::u32 jointCount = 0;
  151. LmbrCentral::SkeletalHierarchyRequestBus::EventResult(jointCount, m_targetId, &LmbrCentral::SkeletalHierarchyRequests::GetJointCount);
  152. for (AZ::u32 jointIndex = 0; jointIndex < jointCount; ++jointIndex)
  153. {
  154. const char* name = nullptr;
  155. LmbrCentral::SkeletalHierarchyRequestBus::EventResult(name, m_targetId, &LmbrCentral::SkeletalHierarchyRequests::GetJointNameByIndex, jointIndex);
  156. if (name)
  157. {
  158. names.push_back(name);
  159. if (!currentTargetBoneFound)
  160. {
  161. currentTargetBoneFound = (m_targetBoneName == names.back());
  162. }
  163. }
  164. }
  165. // If we never found currently-set bone name,
  166. // stick it at top of list, just in case user wants to keep it anyway
  167. if (!currentTargetBoneFound && !m_targetBoneName.empty())
  168. {
  169. names.insert(names.begin(), m_targetBoneName);
  170. }
  171. return names;
  172. }
  173. AZ::u32 EditorAttachmentComponent::OnTargetIdChanged()
  174. {
  175. // Warn about bad setups (it won't crash, but it's nice to handle this early)
  176. if (m_targetId == GetEntityId())
  177. {
  178. AZ_Warning(GetEntity()->GetName().c_str(), false, "AttachmentComponent cannot target self.") m_targetId.SetInvalid();
  179. }
  180. // Warn about children attaching to a parent
  181. AZ::EntityId parentOfTarget;
  182. AZ::TransformBus::EventResult(parentOfTarget, m_targetId, &AZ::TransformBus::Events::GetParentId);
  183. while (parentOfTarget.IsValid())
  184. {
  185. if (parentOfTarget == GetEntityId())
  186. {
  187. AZ_Warning(
  188. GetEntity()->GetName().c_str(), parentOfTarget != GetEntityId(), "AttachmentComponent cannot target child entity");
  189. m_targetId.SetInvalid();
  190. break;
  191. }
  192. AZ::EntityId currentParentId = parentOfTarget;
  193. parentOfTarget.SetInvalid();
  194. AZ::TransformBus::EventResult(parentOfTarget, currentParentId, &AZ::TransformBus::Events::GetParentId);
  195. }
  196. AttachOrDetachAsNecessary();
  197. return AZ::Edit::PropertyRefreshLevels::AttributesAndValues; // refresh bone options
  198. }
  199. AZ::u32 EditorAttachmentComponent::OnTargetBoneChanged()
  200. {
  201. AttachOrDetachAsNecessary();
  202. return AZ::Edit::PropertyRefreshLevels::None;
  203. }
  204. AZ::u32 EditorAttachmentComponent::OnTargetOffsetChanged()
  205. {
  206. LmbrCentral::AttachmentComponentRequestBus::Event(
  207. GetEntityId(), &LmbrCentral::AttachmentComponentRequestBus::Events::SetAttachmentOffset, GetTargetOffset());
  208. return AZ::Edit::PropertyRefreshLevels::None;
  209. }
  210. AZ::u32 EditorAttachmentComponent::OnAttachedInitiallyChanged()
  211. {
  212. AttachOrDetachAsNecessary();
  213. return AZ::Edit::PropertyRefreshLevels::None;
  214. }
  215. AZ::u32 EditorAttachmentComponent::OnScaleSourceChanged()
  216. {
  217. m_boneFollower.Deactivate();
  218. m_boneFollower.Activate(GetEntity(), CreateAttachmentConfiguration(), false);
  219. return AZ::Edit::PropertyRefreshLevels::None;
  220. }
  221. void EditorAttachmentComponent::AttachOrDetachAsNecessary()
  222. {
  223. if (m_attachedInitially && m_targetId.IsValid())
  224. {
  225. LmbrCentral::AttachmentComponentRequestBus::Event(
  226. GetEntityId(),
  227. &LmbrCentral::AttachmentComponentRequestBus::Events::Attach,
  228. m_targetId,
  229. m_targetBoneName.c_str(),
  230. GetTargetOffset());
  231. }
  232. else
  233. {
  234. LmbrCentral::AttachmentComponentRequestBus::Event(
  235. GetEntityId(), &LmbrCentral::AttachmentComponentRequestBus::Events::Detach);
  236. }
  237. }
  238. } // namespace Render
  239. } // namespace AZ