TubeShape.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  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 "TubeShape.h"
  9. #include <AzCore/Math/Transform.h>
  10. #include <Shape/ShapeGeometryUtil.h>
  11. #if LMBR_CENTRAL_EDITOR
  12. #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
  13. #endif
  14. namespace LmbrCentral
  15. {
  16. float Lerpf(const float from, const float to, const float fraction)
  17. {
  18. return AZ::Lerp(from, to, fraction);
  19. }
  20. AZ::Vector3 CalculateNormal(
  21. const AZ::Vector3& previousNormal, const AZ::Vector3& previousTangent, const AZ::Vector3& currentTangent)
  22. {
  23. // Rotates the previous normal by the angle difference between two tangent segments. Ensures
  24. // The normal is continuous along the tube.
  25. AZ::Vector3 normal = previousNormal;
  26. auto cosAngleBetweenTangentSegments = currentTangent.Dot(previousTangent);
  27. if (std::fabs(cosAngleBetweenTangentSegments) < 1.0f)
  28. {
  29. AZ::Vector3 axis = previousTangent.Cross(currentTangent);
  30. if (!axis.IsZero())
  31. {
  32. axis.Normalize();
  33. float angle = acosf(cosAngleBetweenTangentSegments);
  34. AZ::Quaternion rotationTangentDelta = AZ::Quaternion::CreateFromAxisAngle(axis, angle);
  35. normal = rotationTangentDelta.TransformVector(normal);
  36. normal.Normalize();
  37. }
  38. }
  39. return normal;
  40. }
  41. void TubeShape::Reflect(AZ::SerializeContext& context)
  42. {
  43. context.Class <TubeShape>()
  44. ->Version(1)
  45. ->Field("Radius", &TubeShape::m_radius)
  46. ->Field("VariableRadius", &TubeShape::m_variableRadius)
  47. ;
  48. if (auto editContext = context.GetEditContext())
  49. {
  50. editContext->Class<TubeShape>(
  51. "Tube Shape", "")
  52. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  53. ->DataElement(AZ::Edit::UIHandlers::Default, &TubeShape::m_radius, "Radius", "Radius of the tube")
  54. ->Attribute(AZ::Edit::Attributes::Min, 0.1f)
  55. ->Attribute(AZ::Edit::Attributes::Step, 0.5f)
  56. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &TubeShape::BaseRadiusChanged)
  57. ->DataElement(AZ::Edit::UIHandlers::Default, &TubeShape::m_variableRadius, "Variable Radius", "Variable radius along the tube")
  58. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &TubeShape::VariableRadiusChanged)
  59. ;
  60. }
  61. }
  62. TubeShape::TubeShape(const TubeShape& rhs)
  63. {
  64. m_spline = rhs.m_spline;
  65. m_variableRadius = rhs.m_variableRadius;
  66. m_currentTransform = rhs.m_currentTransform;
  67. m_entityId = rhs.m_entityId;
  68. m_radius = rhs.m_radius;
  69. }
  70. void TubeShape::Activate(AZ::EntityId entityId)
  71. {
  72. m_entityId = entityId;
  73. AZ::TransformNotificationBus::Handler::BusConnect(m_entityId);
  74. TubeShapeComponentRequestsBus::Handler::BusConnect(m_entityId);
  75. SplineComponentNotificationBus::Handler::BusConnect(m_entityId);
  76. AZ::TransformBus::EventResult(m_currentTransform, m_entityId, &AZ::TransformBus::Events::GetWorldTM);
  77. SplineComponentRequestBus::EventResult(m_spline, m_entityId, &SplineComponentRequests::GetSpline);
  78. m_variableRadius.Activate(entityId);
  79. ShapeComponentRequestsBus::Handler::BusConnect(m_entityId);
  80. }
  81. void TubeShape::Deactivate()
  82. {
  83. ShapeComponentRequestsBus::Handler::BusDisconnect();
  84. m_variableRadius.Deactivate();
  85. SplineComponentNotificationBus::Handler::BusDisconnect();
  86. TubeShapeComponentRequestsBus::Handler::BusDisconnect();
  87. AZ::TransformNotificationBus::Handler::BusDisconnect();
  88. }
  89. void TubeShape::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
  90. {
  91. {
  92. AZStd::unique_lock lock(m_mutex);
  93. m_currentTransform = world;
  94. }
  95. ShapeComponentNotificationsBus::Event(
  96. m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged,
  97. ShapeComponentNotifications::ShapeChangeReasons::TransformChanged);
  98. }
  99. void TubeShape::SetRadius(float radius)
  100. {
  101. {
  102. AZStd::unique_lock lock(m_mutex);
  103. m_radius = radius;
  104. ValidateAllVariableRadii();
  105. }
  106. ShapeComponentNotificationsBus::Event(
  107. m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged,
  108. ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
  109. }
  110. float TubeShape::GetRadius() const
  111. {
  112. AZStd::shared_lock lock(m_mutex);
  113. return m_radius;
  114. }
  115. void TubeShape::SetVariableRadius(int vertIndex, float radius)
  116. {
  117. {
  118. AZStd::unique_lock lock(m_mutex);
  119. m_variableRadius.SetElement(vertIndex, radius);
  120. ValidateVariableRadius(vertIndex);
  121. }
  122. ShapeComponentNotificationsBus::Event(
  123. m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged,
  124. ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
  125. }
  126. void TubeShape::SetAllVariableRadii(float radius)
  127. {
  128. {
  129. AZStd::unique_lock lock(m_mutex);
  130. for (size_t vertIndex = 0; vertIndex < m_variableRadius.Size(); ++vertIndex)
  131. {
  132. m_variableRadius.SetElement(vertIndex, radius);
  133. ValidateVariableRadius(vertIndex);
  134. }
  135. }
  136. ShapeComponentNotificationsBus::Event(
  137. m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged,
  138. ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
  139. }
  140. float TubeShape::GetVariableRadius(int vertIndex) const
  141. {
  142. AZStd::shared_lock lock(m_mutex);
  143. return m_variableRadius.GetElement(vertIndex);
  144. }
  145. float TubeShape::GetTotalRadius(const AZ::SplineAddress& address) const
  146. {
  147. AZStd::shared_lock lock(m_mutex);
  148. return m_radius + m_variableRadius.GetElementInterpolated(address, Lerpf);
  149. }
  150. const SplineAttribute<float>& TubeShape::GetRadiusAttribute() const
  151. {
  152. AZStd::shared_lock lock(m_mutex);
  153. return m_variableRadius;
  154. }
  155. void TubeShape::OnSplineChanged()
  156. {
  157. SplineComponentRequestBus::EventResult(m_spline, m_entityId, &SplineComponentRequests::GetSpline);
  158. ShapeComponentNotificationsBus::Event(m_entityId, &ShapeComponentNotificationsBus::Events::OnShapeChanged, ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged);
  159. }
  160. AZ::SplinePtr TubeShape::GetSpline()
  161. {
  162. AZStd::shared_lock lock(m_mutex);
  163. if (!m_spline)
  164. {
  165. SplineComponentRequestBus::EventResult(m_spline, m_entityId, &SplineComponentRequests::GetSpline);
  166. AZ_Assert(m_spline, "A TubeShape must have a Spline to work");
  167. }
  168. return m_spline;
  169. }
  170. AZ::ConstSplinePtr TubeShape::GetConstSpline() const
  171. {
  172. AZStd::shared_lock lock(m_mutex);
  173. if (!m_spline)
  174. {
  175. SplineComponentRequestBus::EventResult(m_spline, m_entityId, &SplineComponentRequests::GetSpline);
  176. AZ_Assert(m_spline, "A TubeShape must have a Spline to work");
  177. }
  178. return m_spline;
  179. }
  180. static AZ::Aabb CalculateTubeBounds(const TubeShape& tubeShape, const AZ::Transform& transform)
  181. {
  182. const auto maxScale = transform.GetUniformScale();
  183. const auto scaledRadiusFn =
  184. [&tubeShape, maxScale](const AZ::SplineAddress& splineAddress)
  185. {
  186. return tubeShape.GetTotalRadius(splineAddress) * maxScale;
  187. };
  188. const auto& spline = tubeShape.GetConstSpline();
  189. // approximate aabb - not exact but is guaranteed to encompass tube
  190. float maxRadius = scaledRadiusFn(AZ::SplineAddress(0, 0.0f));
  191. for (size_t i = 0; i < spline->GetVertexCount(); ++i)
  192. {
  193. maxRadius = AZ::GetMax(maxRadius, scaledRadiusFn(AZ::SplineAddress(i, 1.0f)));
  194. }
  195. AZ::Aabb aabb;
  196. spline->GetAabb(aabb, transform);
  197. aabb.Expand(AZ::Vector3(maxRadius));
  198. return aabb;
  199. }
  200. AZ::Aabb TubeShape::GetEncompassingAabb() const
  201. {
  202. AZStd::shared_lock lock(m_mutex);
  203. if (m_spline == nullptr)
  204. {
  205. return AZ::Aabb::CreateNull();
  206. }
  207. AZ::Transform worldFromLocalUniformScale = m_currentTransform;
  208. worldFromLocalUniformScale.SetUniformScale(worldFromLocalUniformScale.GetUniformScale());
  209. return CalculateTubeBounds(*this, worldFromLocalUniformScale);
  210. }
  211. void TubeShape::GetTransformAndLocalBounds(AZ::Transform& transform, AZ::Aabb& bounds) const
  212. {
  213. AZStd::shared_lock lock(m_mutex);
  214. bounds = CalculateTubeBounds(*this, AZ::Transform::CreateIdentity());
  215. transform = m_currentTransform;
  216. }
  217. bool TubeShape::IsPointInside(const AZ::Vector3& point) const
  218. {
  219. AZStd::shared_lock lock(m_mutex);
  220. if (m_spline == nullptr)
  221. {
  222. return false;
  223. }
  224. AZ::Transform worldFromLocalNormalized = m_currentTransform;
  225. const float scale = worldFromLocalNormalized.ExtractUniformScale();
  226. const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse();
  227. const AZ::Vector3 localPoint = localFromWorldNormalized.TransformPoint(point) / scale;
  228. const auto address = m_spline->GetNearestAddressPosition(localPoint).m_splineAddress;
  229. const float radiusSq = powf(m_radius, 2.0f);
  230. const float variableRadiusSq =
  231. powf(m_variableRadius.GetElementInterpolated(address, Lerpf), 2.0f);
  232. return (m_spline->GetPosition(address) - localPoint).GetLengthSq() < (radiusSq + variableRadiusSq) * scale;
  233. }
  234. float TubeShape::DistanceFromPoint(const AZ::Vector3& point) const
  235. {
  236. AZStd::shared_lock lock(m_mutex);
  237. AZ::Transform worldFromLocalNormalized = m_currentTransform;
  238. const float uniformScale = worldFromLocalNormalized.ExtractUniformScale();
  239. const AZ::Transform localFromWorldNormalized = worldFromLocalNormalized.GetInverse();
  240. const AZ::Vector3 localPoint = localFromWorldNormalized.TransformPoint(point) / uniformScale;
  241. const auto splineQueryResult = m_spline->GetNearestAddressPosition(localPoint);
  242. const float variableRadius =
  243. m_variableRadius.GetElementInterpolated(splineQueryResult.m_splineAddress, Lerpf);
  244. // Make sure the distance is clamped to 0 for all points that exist within the tube.
  245. return AZStd::max(0.0f, (sqrtf(splineQueryResult.m_distanceSq) - (m_radius + variableRadius)) * uniformScale);
  246. }
  247. float TubeShape::DistanceSquaredFromPoint(const AZ::Vector3& point) const
  248. {
  249. float distance = DistanceFromPoint(point);
  250. return powf(distance, 2.0f);
  251. }
  252. bool TubeShape::IntersectRay(const AZ::Vector3& src, const AZ::Vector3& dir, float& distance) const
  253. {
  254. AZStd::shared_lock lock(m_mutex);
  255. const auto splineQueryResult = IntersectSpline(m_currentTransform, src, dir, *m_spline);
  256. const float variableRadius = m_variableRadius.GetElementInterpolated(
  257. splineQueryResult.m_splineAddress, Lerpf);
  258. const float totalRadius = m_radius + variableRadius;
  259. distance = (splineQueryResult.m_rayDistance - totalRadius) * m_currentTransform.GetUniformScale();
  260. return static_cast<float>(sqrtf(splineQueryResult.m_distanceSq)) < totalRadius;
  261. }
  262. /// Generates all vertex positions. Assumes the vertex pointer is valid
  263. static void GenerateSolidTubeMeshVertices(
  264. const AZ::SplinePtr& spline, const SplineAttribute<float>& variableRadius,
  265. const float radius, const AZ::u32 sides, const AZ::u32 capSegments, AZ::Vector3* vertices)
  266. {
  267. // start cap
  268. auto address = spline->GetAddressByFraction(0.0f);
  269. AZ::Vector3 normal = spline->GetNormal(address);
  270. AZ::Vector3 previousTangent = spline->GetTangent(address);
  271. if (capSegments > 0)
  272. {
  273. vertices = CapsuleTubeUtil::GenerateSolidStartCap(
  274. spline->GetPosition(address),
  275. previousTangent,
  276. normal,
  277. radius + variableRadius.GetElementInterpolated(address, Lerpf),
  278. sides, capSegments, vertices);
  279. }
  280. // middle segments (body)
  281. const float stepDelta = 1.0f / static_cast<float>(spline->GetSegmentGranularity());
  282. const auto endIndex = address.m_segmentIndex + spline->GetSegmentCount();
  283. while (address.m_segmentIndex < endIndex)
  284. {
  285. for (auto step = 0; step <= spline->GetSegmentGranularity(); ++step)
  286. {
  287. const AZ::Vector3 currentTangent = spline->GetTangent(address);
  288. normal = CalculateNormal(normal, previousTangent, currentTangent);
  289. vertices = CapsuleTubeUtil::GenerateSegmentVertices(
  290. spline->GetPosition(address),
  291. currentTangent,
  292. normal,
  293. radius + variableRadius.GetElementInterpolated(address, Lerpf),
  294. sides, vertices);
  295. address.m_segmentFraction += stepDelta;
  296. previousTangent = currentTangent;
  297. }
  298. address.m_segmentIndex++;
  299. address.m_segmentFraction = 0.f;
  300. }
  301. // end cap
  302. if (capSegments > 0)
  303. {
  304. const auto endAddress = spline->GetAddressByFraction(1.0f);
  305. CapsuleTubeUtil::GenerateSolidEndCap(
  306. spline->GetPosition(endAddress),
  307. spline->GetTangent(endAddress),
  308. normal,
  309. radius + variableRadius.GetElementInterpolated(endAddress, Lerpf),
  310. sides, capSegments, vertices);
  311. }
  312. }
  313. /*
  314. Generates vertices and indices for a tube shape
  315. Split into two stages:
  316. - Generate vertex positions
  317. - Generate indices (faces)
  318. Heres a rough diagram of how it is built:
  319. ____________
  320. /_|__|__|__|_\
  321. \_|__|__|__|_/
  322. - A single vertex at each end of the tube
  323. - Angled end cap segments
  324. - Middle segments
  325. */
  326. void GenerateSolidTubeMesh(
  327. const AZ::SplinePtr& spline, const SplineAttribute<float>& variableRadius,
  328. const float radius, const AZ::u32 capSegments, const AZ::u32 sides,
  329. AZStd::vector<AZ::Vector3>& vertexBufferOut,
  330. AZStd::vector<AZ::u32>& indexBufferOut)
  331. {
  332. const size_t segmentCount = spline->GetSegmentCount();
  333. if (segmentCount == 0)
  334. {
  335. // clear the buffers so we no longer draw anything
  336. vertexBufferOut.clear();
  337. indexBufferOut.clear();
  338. return;
  339. }
  340. const AZ::u32 segments = static_cast<AZ::u32>(segmentCount * spline->GetSegmentGranularity() + segmentCount - 1);
  341. const AZ::u32 totalSegments = segments + capSegments * 2;
  342. const AZ::u32 capSegmentTipVerts = capSegments > 0 ? 2 : 0;
  343. const size_t numVerts = sides * (totalSegments + 1) + capSegmentTipVerts;
  344. const size_t numTriangles = (sides * totalSegments) * 2 + (sides * capSegmentTipVerts);
  345. vertexBufferOut.resize(numVerts);
  346. indexBufferOut.resize(numTriangles * 3);
  347. GenerateSolidTubeMeshVertices(
  348. spline, variableRadius, radius,
  349. sides, capSegments, &vertexBufferOut[0]);
  350. CapsuleTubeUtil::GenerateSolidMeshIndices(
  351. sides, segments, capSegments, &indexBufferOut[0]);
  352. }
  353. /// Compose Caps, Lines and Loops to produce a final wire mesh matching the style of other
  354. /// debug draw components.
  355. void GenerateWireTubeMesh(
  356. const AZ::SplinePtr& spline, const SplineAttribute<float>& variableRadius,
  357. const float radius, const AZ::u32 capSegments, const AZ::u32 sides,
  358. AZStd::vector<AZ::Vector3>& vertexBufferOut)
  359. {
  360. const size_t segmentCount = spline->GetSegmentCount();
  361. if (segmentCount == 0)
  362. {
  363. // clear the buffer so we no longer draw anything
  364. vertexBufferOut.clear();
  365. return;
  366. }
  367. // notes on vert buffer size
  368. // total end segments
  369. // 2 verts for each segment
  370. // 2 * capSegments for one full half arc
  371. // 2 arcs per end
  372. // 2 ends
  373. // total segments
  374. // 2 verts for each segment
  375. // 2 lines - top and bottom
  376. // 2 lines - left and right
  377. // total loops
  378. // 2 verts for each segment
  379. // loops == sides
  380. // 2 loops per segment
  381. const AZ::u32 segments = static_cast<AZ::u32>(segmentCount * spline->GetSegmentGranularity());
  382. const AZ::u32 totalEndSegments = capSegments * 2 * 2 * 2 * 2;
  383. const AZ::u32 totalSegments = segments * 2 * 2 * 2;
  384. const AZ::u32 totalLoops = 2 * sides * segments * 2;
  385. const bool hasEnds = capSegments > 0;
  386. const size_t numVerts = totalEndSegments + totalSegments + totalLoops;
  387. vertexBufferOut.resize(numVerts);
  388. AZ::Vector3* vertices = vertexBufferOut.data();
  389. // start cap
  390. auto address = spline->GetAddressByFraction(0.0f);
  391. AZ::Vector3 side = spline->GetNormal(address);
  392. AZ::Vector3 nextSide = spline->GetNormal(address);
  393. AZ::Vector3 previousDirection = spline->GetTangent(address);
  394. if (hasEnds)
  395. {
  396. vertices = CapsuleTubeUtil::GenerateWireCap(
  397. spline->GetPosition(address),
  398. -previousDirection,
  399. side,
  400. radius + variableRadius.GetElementInterpolated(address, Lerpf),
  401. capSegments,
  402. vertices);
  403. }
  404. // body
  405. const float stepDelta = 1.0f / static_cast<float>(spline->GetSegmentGranularity());
  406. auto nextAddress = address;
  407. const auto endIndex = address.m_segmentIndex + segmentCount;
  408. while (address.m_segmentIndex < endIndex)
  409. {
  410. address.m_segmentFraction = 0.f;
  411. nextAddress.m_segmentFraction = stepDelta;
  412. for (auto step = 0; step < spline->GetSegmentGranularity(); ++step)
  413. {
  414. const auto position = spline->GetPosition(address);
  415. const auto nextPosition = spline->GetPosition(nextAddress);
  416. const auto direction = spline->GetTangent(address);
  417. const auto nextDirection = spline->GetTangent(nextAddress);
  418. side = spline->GetNormal(address);
  419. nextSide = spline->GetNormal(nextAddress);
  420. const auto up = side.Cross(direction);
  421. const auto nextUp = nextSide.Cross(nextDirection);
  422. const auto finalRadius = radius + variableRadius.GetElementInterpolated(address, Lerpf);
  423. const auto nextFinalRadius = radius + variableRadius.GetElementInterpolated(nextAddress, Lerpf);
  424. // left line
  425. vertices = WriteVertex(
  426. CapsuleTubeUtil::CalculatePositionOnSphere(
  427. position, direction, side, finalRadius, 0.0f),
  428. vertices);
  429. vertices = WriteVertex(
  430. CapsuleTubeUtil::CalculatePositionOnSphere(
  431. nextPosition, nextDirection, nextSide, nextFinalRadius, 0.0f),
  432. vertices);
  433. // right line
  434. vertices = WriteVertex(
  435. CapsuleTubeUtil::CalculatePositionOnSphere(
  436. position, -direction, side, finalRadius, AZ::Constants::Pi),
  437. vertices);
  438. vertices = WriteVertex(
  439. CapsuleTubeUtil::CalculatePositionOnSphere(
  440. nextPosition, -nextDirection, nextSide, nextFinalRadius, AZ::Constants::Pi),
  441. vertices);
  442. // top line
  443. vertices = WriteVertex(
  444. CapsuleTubeUtil::CalculatePositionOnSphere(
  445. position, direction, up, finalRadius, 0.0f),
  446. vertices);
  447. vertices = WriteVertex(
  448. CapsuleTubeUtil::CalculatePositionOnSphere(
  449. nextPosition, nextDirection, nextUp, nextFinalRadius, 0.0f),
  450. vertices);
  451. // bottom line
  452. vertices = WriteVertex(
  453. CapsuleTubeUtil::CalculatePositionOnSphere(
  454. position, -direction, up, finalRadius, AZ::Constants::Pi),
  455. vertices);
  456. vertices = WriteVertex(
  457. CapsuleTubeUtil::CalculatePositionOnSphere(
  458. nextPosition, -nextDirection, nextUp, nextFinalRadius, AZ::Constants::Pi),
  459. vertices);
  460. // loops along each segment
  461. vertices = CapsuleTubeUtil::GenerateWireLoop(
  462. position, direction, side, sides, finalRadius, vertices);
  463. vertices = CapsuleTubeUtil::GenerateWireLoop(
  464. nextPosition, nextDirection, nextSide, sides, nextFinalRadius, vertices);
  465. address.m_segmentFraction += stepDelta;
  466. nextAddress.m_segmentFraction += stepDelta;
  467. previousDirection = direction;
  468. }
  469. address.m_segmentIndex++;
  470. nextAddress.m_segmentIndex++;
  471. }
  472. if (hasEnds)
  473. {
  474. const auto endAddress = spline->GetAddressByFraction(1.0f);
  475. const auto endPosition = spline->GetPosition(endAddress);
  476. const auto endDirection = spline->GetTangent(endAddress);
  477. const auto endRadius =
  478. radius + variableRadius.GetElementInterpolated(endAddress, Lerpf);
  479. // end cap
  480. CapsuleTubeUtil::GenerateWireCap(
  481. endPosition, endDirection,
  482. nextSide, endRadius, capSegments,
  483. vertices);
  484. }
  485. }
  486. void GenerateTubeMesh(
  487. const AZ::SplinePtr& spline, const SplineAttribute<float>& variableRadius,
  488. const float radius, const AZ::u32 capSegments, const AZ::u32 sides,
  489. AZStd::vector<AZ::Vector3>& vertexBufferOut, AZStd::vector<AZ::u32>& indexBufferOut,
  490. AZStd::vector<AZ::Vector3>& lineBufferOut)
  491. {
  492. GenerateSolidTubeMesh(
  493. spline, variableRadius, radius,
  494. capSegments, sides, vertexBufferOut,
  495. indexBufferOut);
  496. GenerateWireTubeMesh(
  497. spline, variableRadius, radius,
  498. capSegments, sides, lineBufferOut);
  499. }
  500. void TubeShapeMeshConfig::Reflect(AZ::ReflectContext* context)
  501. {
  502. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  503. {
  504. serializeContext->Class<TubeShapeMeshConfig>()
  505. ->Version(2)
  506. ->Field("EndSegments", &TubeShapeMeshConfig::m_endSegments)
  507. ->Field("Sides", &TubeShapeMeshConfig::m_sides)
  508. ->Field("ShapeConfig", &TubeShapeMeshConfig::m_shapeComponentConfig)
  509. ;
  510. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  511. {
  512. editContext->Class<TubeShapeMeshConfig>("Configuration", "Tube Shape Mesh Configuration")
  513. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  514. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  515. ->DataElement(AZ::Edit::UIHandlers::Slider, &TubeShapeMeshConfig::m_endSegments, "End Segments",
  516. "Number Of segments at each end of the tube in the editor")
  517. ->Attribute(AZ::Edit::Attributes::Min, 1)
  518. ->Attribute(AZ::Edit::Attributes::Max, 10)
  519. ->Attribute(AZ::Edit::Attributes::Step, 1)
  520. ->DataElement(AZ::Edit::UIHandlers::Slider, &TubeShapeMeshConfig::m_sides, "Sides",
  521. "Number of Sides of the tube in the editor")
  522. ->Attribute(AZ::Edit::Attributes::Min, 3)
  523. ->Attribute(AZ::Edit::Attributes::Max, 32)
  524. ->Attribute(AZ::Edit::Attributes::Step, 1)
  525. ;
  526. }
  527. }
  528. }
  529. void TubeShape::BaseRadiusChanged()
  530. {
  531. // ensure all variable radii stay in bounds should the base radius
  532. // change and cause the resulting total radius to be negative
  533. ValidateAllVariableRadii();
  534. }
  535. void TubeShape::VariableRadiusChanged(size_t vertIndex)
  536. {
  537. ValidateVariableRadius(vertIndex);
  538. }
  539. void TubeShape::ValidateVariableRadius(const size_t vertIndex)
  540. {
  541. // if the total radius is less than 0, adjust the variable radius
  542. // to ensure the total radius stays positive
  543. float totalRadius = m_radius + m_variableRadius.GetElementInterpolated(AZ::SplineAddress(vertIndex), Lerpf);
  544. if (totalRadius < 0.0f)
  545. {
  546. m_variableRadius.SetElement(vertIndex, -m_radius);
  547. }
  548. }
  549. void TubeShape::ValidateAllVariableRadii()
  550. {
  551. for (size_t vertIndex = 0; vertIndex < m_spline->GetVertexCount(); ++vertIndex)
  552. {
  553. ValidateVariableRadius(vertIndex);
  554. }
  555. }
  556. } // namespace LmbrCentral