summaryrefslogtreecommitdiffstats
path: root/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp')
-rw-r--r--src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp813
1 files changed, 813 insertions, 0 deletions
diff --git a/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp
new file mode 100644
index 00000000..7fb4ab9f
--- /dev/null
+++ b/src/Runtime/Source/stateapplication/editor/Qt3DSStateEditorTransitionPath.cpp
@@ -0,0 +1,813 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "Qt3DSStateEditorTransitionPath.h"
+
+namespace {
+
+using namespace qt3ds::state::editor;
+using namespace qt3ds::state;
+
+bool inBounds(QT3DSF32 item, QT3DSF32 lower, QT3DSF32 upper)
+{
+ if (item >= lower && item <= upper)
+ return true;
+ return false;
+}
+
+DirectionTypes::Enum EdgeTypeToDirectionType(EdgeTypes::Enum inEdgeType)
+{
+ switch (inEdgeType) {
+ case EdgeTypes::Bottom:
+ case EdgeTypes::Top:
+ return DirectionTypes::Vertical;
+ default:
+ return DirectionTypes::Horizontal;
+ }
+}
+
+static inline SEndPoint DoGetActualEndPoint(const SEndPoint &inPoint, const SRect &inMyRect,
+ const QT3DSVec2 &inOtherCenter)
+{
+ SEndPoint theEndPoint = inPoint;
+ if (inPoint.m_EdgeType == EdgeTypes::UnsetEdgeType)
+ theEndPoint = CEditorTransitionPath::CalculateDefaultEndPoint(inMyRect, inOtherCenter);
+ return theEndPoint;
+}
+
+static inline eastl::pair<QT3DSVec2, EdgeTypes::Enum>
+GetEndpointPoint(const SEndPoint &inPoint, const SRect &inMyRect, const QT3DSVec2 &inOtherCenter)
+{
+ SEndPoint theEndPoint = DoGetActualEndPoint(inPoint, inMyRect, inOtherCenter);
+ SLine theRectLine;
+ switch (theEndPoint.m_EdgeType) {
+ case EdgeTypes::Top:
+ theRectLine = inMyRect.topEdge();
+ break;
+ case EdgeTypes::Bottom:
+ theRectLine = inMyRect.bottomEdge();
+ break;
+ case EdgeTypes::Left:
+ theRectLine = inMyRect.leftEdge();
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ // fallthrough intentional
+ case EdgeTypes::Right:
+ theRectLine = inMyRect.rightEdge();
+ break;
+ }
+
+ return eastl::make_pair(theRectLine.toPoint(theEndPoint.m_Interp), theEndPoint.m_EdgeType);
+}
+
+inline bool RectDiffers(const Option<SRect> &inLhs, const SRect &inRhs)
+{
+ if (inLhs.isEmpty())
+ return true;
+ SRect lhs = *inLhs;
+ return CEditorTransitionPath::AreAlmostEqual(lhs.m_TopLeft, inRhs.m_TopLeft) == false
+ || CEditorTransitionPath::AreAlmostEqual(lhs.m_WidthHeight, inRhs.m_WidthHeight) == false;
+}
+}
+
+namespace qt3ds {
+namespace state {
+ namespace editor {
+
+ bool SRect::contains(const QT3DSVec2 &inPoint) const
+ {
+ if (inPoint.x >= left() && inPoint.x <= right() && inPoint.y >= top()
+ && inPoint.y <= bottom())
+ return true;
+ return false;
+ }
+
+ void CEditorTransitionPath::SetPathType(TransitionPathTypes::Enum inType)
+ {
+ m_TransitionPathType = inType;
+ if (m_TransitionPathType == TransitionPathTypes::BeginToBegin
+ || m_TransitionPathType == TransitionPathTypes::Targetless)
+ m_ControlPoints.clear();
+ MarkDirty();
+ }
+
+ bool CEditorTransitionPath::UpdateBeginEndRects(const SRect &inStartRect,
+ const SRect &inEndRect)
+ {
+ bool dataChanged = false;
+
+ if (m_BeginRect.hasValue() && m_EndRect.hasValue()) {
+ QT3DSVec2 beginDiff = inStartRect.center() - m_BeginRect->center();
+ QT3DSVec2 endDiff = inEndRect.center() - m_EndRect->center();
+
+ if (AreAlmostEqual(beginDiff, QT3DSVec2(0, 0)) == false
+ && AreAlmostEqual(beginDiff, endDiff, 1)) {
+ dataChanged = m_ControlPoints.empty() == false;
+ // Move all the control points.
+ for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) {
+ SControlPoint &thePoint(m_ControlPoints[idx]);
+ QT3DSF32 diffComponent =
+ SControlPoint::GetComponent(beginDiff, thePoint.m_Direction);
+ thePoint.m_Position += diffComponent;
+ }
+ }
+ }
+ bool rectDiffers =
+ RectDiffers(m_BeginRect, inStartRect) || RectDiffers(m_EndRect, inEndRect);
+ m_BeginRect = inStartRect;
+ m_EndRect = inEndRect;
+ if (rectDiffers)
+ MarkDirty();
+ m_TransitionPathType = TransitionPathTypes::BeginToEnd;
+ return dataChanged;
+ }
+
+ bool CEditorTransitionPath::UpdateBeginEndRects(const SRect &inStartRect,
+ TransitionPathTypes::Enum inPathType)
+ {
+ bool dataChanged = m_ControlPoints.empty() == false;
+ QT3DS_ASSERT(inPathType == TransitionPathTypes::BeginToBegin
+ || inPathType == TransitionPathTypes::Targetless);
+ bool rectDiffers = m_EndRect.hasValue() || RectDiffers(m_BeginRect, inStartRect);
+ m_BeginRect = inStartRect;
+ m_EndRect = Empty();
+ if (rectDiffers || dataChanged)
+ MarkDirty();
+ m_TransitionPathType = inPathType;
+ return dataChanged;
+ }
+
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> CEditorTransitionPath::GetBeginPointAndEdge() const
+ {
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint(QT3DSVec2(0, 0),
+ EdgeTypes::UnsetEdgeType);
+ if (m_BeginRect.hasValue()) {
+ QT3DSVec2 center(m_BeginRect->center());
+ center.x += 1;
+ if (m_EndRect.hasValue())
+ center = m_EndRect->center();
+ theStartPoint = GetEndpointPoint(m_Begin, *m_BeginRect, center);
+ }
+ return theStartPoint;
+ }
+
+ eastl::pair<QT3DSVec2, QT3DSVec2> CEditorTransitionPath::GetBeginEndPoints() const
+ {
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint(GetBeginPointAndEdge());
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theEndPoint(QT3DSVec2(0, 0),
+ EdgeTypes::UnsetEdgeType);
+ if (m_EndRect.hasValue())
+ theEndPoint = GetEndpointPoint(m_End, *m_EndRect, m_BeginRect->center());
+
+ return eastl::make_pair(theStartPoint.first, theEndPoint.first);
+ }
+
+ SEndPoint CEditorTransitionPath::CalculateEndPoint(const SRect &inRect,
+ const QT3DSVec2 &inPoint,
+ QT3DSF32 inEdgeBoundary)
+ {
+ if (inRect.width() == 0 || inRect.height() == 0) {
+ QT3DS_ASSERT(false);
+ return SEndPoint();
+ }
+
+ SLine centerToPoint = SLine(inRect.center(), inPoint);
+ SLine leftOrRight;
+ SLine topOrBottom;
+ Option<QT3DSF32> isect;
+ EdgeTypes::Enum theEdge = EdgeTypes::UnsetEdgeType;
+ // If line runs right, test against right edge
+ QT3DSF32 distance = 0;
+ SLine theRectLine;
+ if (centerToPoint.dx() > 0) {
+ theRectLine = inRect.rightEdge();
+ isect = theRectLine.intersect(centerToPoint);
+ // If we are out of range for the right edge
+ if (isect.hasValue() && inBounds(*isect, 0.0f, 1.0f)) {
+ distance = theRectLine.dy();
+ theEdge = EdgeTypes::Right;
+ }
+ } else {
+ theRectLine = inRect.leftEdge();
+ isect = theRectLine.intersect(centerToPoint);
+ if (isect.hasValue() && inBounds(*isect, 0.0f, 1.0f)) {
+ distance = theRectLine.dy();
+ theEdge = EdgeTypes::Left;
+ }
+ }
+ // If we haven't resolved the edge type
+ if (theEdge == EdgeTypes::UnsetEdgeType) {
+ if (centerToPoint.dy() < 0) {
+ theRectLine = inRect.topEdge();
+ isect = theRectLine.intersect(centerToPoint);
+ theEdge = EdgeTypes::Top;
+ distance = theRectLine.dx();
+ } else {
+ theRectLine = inRect.bottomEdge();
+ isect = theRectLine.intersect(centerToPoint);
+ theEdge = EdgeTypes::Bottom;
+ distance = theRectLine.dx();
+ }
+ }
+ // Now drop a perpendicular from the point to the rect line.
+ SLine normalLine(inPoint, inPoint + QT3DSVec2(theRectLine.dy(), -theRectLine.dx()));
+ isect = theRectLine.intersect(normalLine);
+ if (isect.isEmpty()) {
+ theEdge = EdgeTypes::Right;
+ isect = .5f;
+ }
+ SEndPoint retval(theEdge, *isect);
+ QT3DSF32 normalizedBoundary =
+ NVMax(0.0f, inEdgeBoundary / static_cast<float>(fabs(distance)));
+
+ QT3DSF32 edgeLowerBound = NVMax(0.0f, normalizedBoundary);
+ QT3DSF32 edgeUpperBound = NVMin(1.0f, 1.0f - normalizedBoundary);
+ retval.m_Interp = NVMax(edgeLowerBound, retval.m_Interp);
+ retval.m_Interp = NVMin(edgeUpperBound, retval.m_Interp);
+ return retval;
+ }
+
+ // Default end points always are in the middle of the rect edge
+ SEndPoint CEditorTransitionPath::CalculateDefaultEndPoint(const SRect &inRect,
+ const QT3DSVec2 &inPoint)
+ {
+ SEndPoint ep = CalculateEndPoint(inRect, inPoint, 0.0f);
+ return SEndPoint(ep.m_EdgeType, .5f);
+ }
+
+ void CEditorTransitionPath::SetEndPoint(const QT3DSVec2 &inWorldPoint)
+ {
+ QT3DS_ASSERT(m_EndRect.hasValue());
+ m_End = CalculateEndPoint(*m_EndRect, inWorldPoint, m_StateEdgeBuffer);
+ MarkDirty();
+ }
+
+ void CEditorTransitionPath::SetBeginPoint(const QT3DSVec2 &inWorldPoint)
+ {
+ QT3DS_ASSERT(m_BeginRect.hasValue());
+ m_Begin = CalculateEndPoint(*m_BeginRect, inWorldPoint, m_StateEdgeBuffer);
+ MarkDirty();
+ }
+
+ SEndPoint CEditorTransitionPath::GetActualBeginPoint() const
+ {
+ return DoGetActualEndPoint(m_Begin, *m_BeginRect, m_EndRect->center());
+ }
+
+ SEndPoint CEditorTransitionPath::GetActualEndPoint() const
+ {
+ return DoGetActualEndPoint(m_End, *m_EndRect, m_BeginRect->center());
+ }
+
+ void CEditorTransitionPath::SetControlPoint(QT3DSI32 idx, const QT3DSVec2 &inPosition,
+ bool inMoveAdjacentEndPoint)
+ {
+ if (idx < 0 || idx >= (QT3DSI32)m_ControlPoints.size()) {
+ QT3DS_ASSERT(false);
+ return;
+ }
+ m_ControlPoints[idx].Set(inPosition);
+ if (inMoveAdjacentEndPoint) {
+ // Move the end points adjacent to the handle.
+ QT3DSI32 possiblePoint = MapControlPointToPossibleControlPoint(idx);
+ size_t numPossible = m_PossibleControlPoints.size();
+ if (possiblePoint == 0)
+ SetBeginPoint(inPosition);
+ if (possiblePoint == ((QT3DSI32)numPossible - 1))
+ SetEndPoint(inPosition);
+ }
+
+ MarkDirty();
+ }
+
+ void CEditorTransitionPath::SetControlPoints(const TPointList &inList)
+ {
+ QT3DS_ASSERT(inList.size() == m_Path.size());
+ for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) {
+ SControlPoint &theControlPoint(m_ControlPoints[idx]);
+ if (theControlPoint.m_PathIndex >= 0
+ && theControlPoint.m_PathIndex < (QT3DSI32)inList.size())
+ theControlPoint.Set(inList[theControlPoint.m_PathIndex]);
+ else {
+ QT3DS_ASSERT(false);
+ }
+ }
+ }
+
+ TPointList CEditorTransitionPath::GetPath() const
+ {
+ MaybeRegeneratePath();
+ return m_Path;
+ }
+
+ TControlPointList CEditorTransitionPath::GetPossibleControlPoints() const
+ {
+ MaybeRegeneratePath();
+ return m_PossibleControlPoints;
+ }
+
+ SControlPoint CEditorTransitionPath::GetPossibleControlPoint(QT3DSI32 inIdx) const
+ {
+ MaybeRegeneratePath();
+ if (inIdx >= 0 && inIdx < (QT3DSI32)m_PossibleControlPoints.size())
+ return m_PossibleControlPoints[inIdx];
+ QT3DS_ASSERT(false);
+ return SControlPoint();
+ }
+
+ // This may create a new control point thus invalidating the path and possible control
+ // points.
+ QT3DSI32
+ CEditorTransitionPath::MapPossibleControlPointToControlPoint(QT3DSI32 inPossiblePointIndex)
+ {
+ if (inPossiblePointIndex < 0
+ || inPossiblePointIndex >= (QT3DSI32)m_PossibleControlPoints.size()) {
+ QT3DS_ASSERT(false);
+ return -1;
+ }
+ const SControlPoint &thePossiblePoint(m_PossibleControlPoints[inPossiblePointIndex]);
+ TControlPointList::iterator iter = eastl::lower_bound(
+ m_ControlPoints.begin(), m_ControlPoints.end(), thePossiblePoint);
+ QT3DSI32 retval = (QT3DSI32)m_ControlPoints.size();
+ if (iter != m_ControlPoints.end()) {
+ retval = (QT3DSI32)(iter - m_ControlPoints.begin());
+ if (iter->m_PathIndex == thePossiblePoint.m_PathIndex)
+ return retval;
+ }
+
+ m_ControlPoints.insert(iter, thePossiblePoint);
+ return retval;
+ }
+
+ QT3DSI32 CEditorTransitionPath::MapControlPointToPossibleControlPoint(QT3DSI32 inPointIndex)
+ {
+ if (inPointIndex < 0 || inPointIndex >= (QT3DSI32)m_ControlPoints.size()) {
+ QT3DS_ASSERT(false);
+ return -1;
+ }
+ const SControlPoint &theControlPoint(m_ControlPoints[inPointIndex]);
+ TControlPointList::iterator iter = eastl::lower_bound(
+ m_PossibleControlPoints.begin(), m_PossibleControlPoints.end(), theControlPoint);
+ if (iter != m_PossibleControlPoints.end()) {
+ QT3DSI32 retval = (QT3DSI32)(iter - m_PossibleControlPoints.begin());
+ if (iter->m_PathIndex == theControlPoint.m_PathIndex)
+ return retval;
+ }
+ QT3DS_ASSERT(false);
+ return -1;
+ }
+
+ bool CEditorTransitionPath::DoesControlPointExist(QT3DSI32 inPossiblePointIndex) const
+ {
+ if (inPossiblePointIndex < 0
+ || inPossiblePointIndex >= (QT3DSI32)m_PossibleControlPoints.size()) {
+ QT3DS_ASSERT(false);
+ return false;
+ }
+ const SControlPoint &thePossiblePoint(m_PossibleControlPoints[inPossiblePointIndex]);
+ TControlPointList::const_iterator iter = eastl::lower_bound(
+ m_ControlPoints.begin(), m_ControlPoints.end(), thePossiblePoint);
+ if (iter != m_ControlPoints.end() && iter->m_PathIndex == thePossiblePoint.m_PathIndex)
+ return true;
+ return false;
+ }
+
+ // Run through the control point list and if you find another control point on the line,
+ // return it's index. Else
+ // bail.
+ // If you are finding the remove algorithm is too specific or hard to use increase the 2.0f
+ // numbers below.
+ inline Option<size_t> NextParallelControlPointOnLine(const SControlPoint &inItem,
+ const SControlPoint &inEndPoint,
+ const TControlPointList &inList,
+ size_t inStartIdx)
+ {
+ for (size_t idx = inStartIdx, end = inList.size(); idx < end; ++idx) {
+ const SControlPoint &theTestPoint(inList[idx]);
+ if (theTestPoint.m_Direction == inItem.m_Direction) {
+ if (CEditorTransitionPath::AreAlmostEqual(inItem.m_Position,
+ theTestPoint.m_Position, 2.0f))
+ return idx + 1;
+ else
+ return Empty();
+ }
+ }
+
+ // Check if beginning and end lie in the same path, or within just a couple pixels.
+ if (inItem.m_Direction == inEndPoint.m_Direction
+ && CEditorTransitionPath::AreAlmostEqual(inItem.m_Position, inEndPoint.m_Position,
+ 2.0f))
+ return inList.size();
+
+ return Empty();
+ }
+
+ // We try to find control point that point the same direction and lie on the same line with
+ // only control points with
+ // orthogonal directions in between. If we find points that fullfill this criteria, we know
+ // we can remove all intermediate
+ // points because the transition path will end up making a straight line.
+ bool CEditorTransitionPath::RemoveRedundantControlPoints()
+ {
+ if (m_ControlPoints.empty())
+ return false;
+
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint = GetBeginPointAndEdge();
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theEndPoint;
+ if (m_EndRect.hasValue())
+ theEndPoint = GetEndpointPoint(m_End, *m_EndRect, m_BeginRect->center());
+ else
+ theEndPoint = theStartPoint;
+ // Find runs of control points in the same line. Remove the points in the middle of the
+ // line.
+ SControlPoint theLastControlPoint(EdgeTypeToDirectionType(theStartPoint.second));
+ theLastControlPoint.Set(theStartPoint.first);
+ SControlPoint theEndControlPoint(EdgeTypeToDirectionType(theEndPoint.second));
+ theEndControlPoint.Set(theEndPoint.first);
+ size_t numControlPoints(m_ControlPoints.size());
+ for (size_t idx = 0, end = numControlPoints; idx < end; ++idx) {
+ Option<size_t> removeEnd = NextParallelControlPointOnLine(
+ theLastControlPoint, theEndControlPoint, m_ControlPoints, idx);
+ if (removeEnd.isEmpty() == false) {
+ size_t lastItem = *removeEnd;
+ m_ControlPoints.erase(m_ControlPoints.begin() + idx,
+ m_ControlPoints.begin() + lastItem);
+ --idx;
+ end = m_ControlPoints.size();
+ } else
+ theLastControlPoint = m_ControlPoints[idx];
+ }
+ if (m_ControlPoints.size() != numControlPoints) {
+ MarkDirty();
+ return true;
+ }
+ return false;
+ }
+
+ void CEditorTransitionPath::RestoreAutoTransition()
+ {
+ m_ControlPoints.clear();
+ m_Begin = SEndPoint();
+ m_End = SEndPoint();
+ MarkDirty();
+ }
+
+ bool CEditorTransitionPath::IsManualMode() const
+ {
+ return m_Begin.m_EdgeType != EdgeTypes::UnsetEdgeType
+ || m_End.m_EdgeType != EdgeTypes::UnsetEdgeType || m_ControlPoints.empty() == false;
+ }
+
+ SPointQueryResult CEditorTransitionPath::Pick(QT3DSVec2 inPoint, QT3DSVec2 inControlBoxDims,
+ QT3DSVec2 inEndBoxDims)
+ {
+ MaybeRegeneratePath();
+
+ if (inEndBoxDims.x && inEndBoxDims.y) {
+ SRect endBox(QT3DSVec2(-1.0f * inEndBoxDims.x / 2, -1.0f * inEndBoxDims.y / 2),
+ inEndBoxDims);
+ SRect testRect(endBox);
+ testRect.translate(m_Path.front());
+ if (testRect.contains(inPoint))
+ return SPointQueryResult(PointQueryResultType::Begin);
+ testRect = SRect(endBox);
+ testRect.translate(m_Path.back());
+ if (testRect.contains(inPoint))
+ return SPointQueryResult(PointQueryResultType::End);
+ }
+ if (inControlBoxDims.x && inControlBoxDims.y) {
+ SRect theControlBox(
+ QT3DSVec2(-1.0f * inControlBoxDims.x / 2.0f, -1.0f * inControlBoxDims.y / 2.0f),
+ inControlBoxDims);
+ for (size_t idx = 0, end = m_PossibleControlPoints.size(); idx < end; ++idx) {
+ const SControlPoint &thePoint(m_PossibleControlPoints[idx]);
+ QT3DSVec2 startPoint = m_Path[thePoint.m_PathIndex];
+ QT3DSVec2 endPoint = m_Path[thePoint.m_PathIndex + 1];
+ // We stretch the rect to contain the entire line, not just where we display the
+ // point.
+ QT3DSVec2 lineDims = endPoint - startPoint;
+ QT3DSF32 lineRectHeight = SControlPoint::GetComponent(theControlBox.m_WidthHeight,
+ thePoint.m_Direction);
+ QT3DSF32 lineRectLength =
+ fabs(SControlPoint::GetOrthogonalComponent(lineDims, thePoint.m_Direction));
+ QT3DSVec2 rectDims = SControlPoint::FromComponentToVector(
+ lineRectHeight, lineRectLength, thePoint.m_Direction);
+ QT3DSVec2 rectTopLeft =
+ QT3DSVec2(NVMin(startPoint.x, endPoint.x), NVMin(startPoint.y, endPoint.y));
+ QT3DSF32 rectComponent =
+ SControlPoint::GetComponent(rectTopLeft, thePoint.m_Direction);
+ rectComponent -= lineRectHeight / 2.0f; // Center the box about the line.
+ rectTopLeft = SControlPoint::SetComponent(rectTopLeft, rectComponent,
+ thePoint.m_Direction);
+ SRect testRect(rectTopLeft, rectDims);
+ if (testRect.contains(inPoint))
+ return SPointQueryResult(PointQueryResultType::Control, (QT3DSI32)idx);
+ }
+ }
+ return PointQueryResultType::NoPoint;
+ }
+
+ SPointQueryResult
+ CEditorTransitionPath::PickClosestControlPoint(QT3DSVec2 inPoint,
+ DirectionTypes::Enum inDirectionType)
+ {
+ MaybeRegeneratePath();
+ QT3DSI32 closestIdx = -1;
+ QT3DSF32 minDistance = QT3DS_MAX_F32;
+
+ for (size_t idx = 0, end = m_PossibleControlPoints.size(); idx < end; ++idx) {
+ const SControlPoint &thePoint(m_PossibleControlPoints[idx]);
+ QT3DSVec2 startPoint = m_Path[thePoint.m_PathIndex];
+ QT3DSVec2 endPoint = m_Path[thePoint.m_PathIndex + 1];
+ SLine theLine(startPoint, endPoint);
+ QT3DSF32 distance = theLine.distance(inPoint);
+
+ if (distance < minDistance && thePoint.m_Direction == inDirectionType) {
+ closestIdx = idx;
+ minDistance = distance;
+ }
+ }
+ if (closestIdx == -1)
+ return SPointQueryResult();
+ return SPointQueryResult(PointQueryResultType::Control, closestIdx);
+ }
+
+ // The output functions are both setup under these assumptions:
+ // 1. The current point does *not* have representation in the possible control points list.
+ // 2. The last point *does* have representation in the possible control points list.
+ // 3. The algorithm should output all path elements up and to but not including the
+ // current point.
+ // So, given straight line do not output any possible points.
+ // Given zig-zag, output two points.
+ SControlPoint
+ CEditorTransitionPath::OutputParallelPoints(const SControlPoint &inLastPoint,
+ const SControlPoint &inCurrentPoint,
+ QT3DSF32 runWidth) const
+ {
+ const SControlPoint &theRunPoint(inCurrentPoint);
+ const SControlPoint theLastControlPoint(inLastPoint);
+ DirectionTypes::Enum runDirection(inLastPoint.m_Direction);
+ if (AreAlmostEqual(theRunPoint.m_Position, theLastControlPoint.m_Position, 1.0)) {
+ // Straigh line. Perhaps remove this point?
+ theRunPoint.m_PathIndex = theLastControlPoint.m_PathIndex;
+ return theRunPoint;
+ } else {
+ // First, output a possible control point inline with inLastPoint
+ SControlPoint possiblePoint(inLastPoint);
+ possiblePoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1);
+ m_PossibleControlPoints.push_back(possiblePoint);
+ // Output zig-zag, we zig zag from last control point to theRunPoint. We need to
+ // push two points and two control points.
+ QT3DSVec2 startPos(m_Path.back());
+ QT3DSF32 startComponent = SControlPoint::GetComponent(startPos, runDirection);
+ QT3DSF32 orthoStartComponent =
+ SControlPoint::GetOrthogonalComponent(startPos, runDirection);
+ QT3DSF32 endComponent = theRunPoint.m_Position;
+ QT3DSF32 orthoEndComponent = orthoStartComponent + runWidth;
+ QT3DSVec2 endPos = SControlPoint::FromComponentToVector(
+ endComponent, orthoEndComponent, runDirection);
+ QT3DSF32 zigZagOrthoPos = orthoStartComponent + runWidth / 2;
+ QT3DSI32 crossbarIndex = (QT3DSI32)m_Path.size();
+ QT3DSVec2 crossbarStart = SControlPoint::FromComponentToVector(
+ startComponent, zigZagOrthoPos, runDirection);
+ m_Path.push_back(crossbarStart);
+ m_PossibleControlPoints.push_back(
+ SControlPoint(SControlPoint::OppositeDirection(theRunPoint.m_Direction),
+ orthoStartComponent, crossbarIndex));
+ QT3DSVec2 crossbarEnd = SControlPoint::FromComponentToVector(
+ endComponent, zigZagOrthoPos, theRunPoint.m_Direction);
+ m_Path.push_back(crossbarEnd);
+ theRunPoint.m_PathIndex = crossbarIndex + 1;
+ // Do not, however, output the run point. This will happen in the next step.
+ return inCurrentPoint;
+ }
+ }
+
+ // Given right angle, output 1 point.
+ SControlPoint
+ CEditorTransitionPath::OutputOrthogonalPoints(const SControlPoint &inLastPoint,
+ const SControlPoint &inCurrentPoint) const
+ {
+ SLine lastLine = inLastPoint.ToLine();
+ SLine currentLine = inCurrentPoint.ToLine();
+ Option<QT3DSF32> isect = lastLine.intersect(currentLine);
+ QT3DS_ASSERT(isect.hasValue());
+ inLastPoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1);
+ m_PossibleControlPoints.push_back(inLastPoint);
+ if (isect.hasValue()) {
+ QT3DSVec2 theIsectPoint = lastLine.toPoint(*isect);
+ m_Path.push_back(theIsectPoint);
+ }
+ inCurrentPoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1);
+ return inCurrentPoint;
+ }
+
+ void CEditorTransitionPath::MaybeRegeneratePath() const
+ {
+ if (IsDirty() == false)
+ return;
+
+ if (m_TransitionPathType == TransitionPathTypes::BeginToEnd) {
+ // Ensure intermediate information is cleared.
+ const_cast<CEditorTransitionPath *>(this)->MarkDirty();
+ // We don't have the begin and end states.
+ if (m_BeginRect.isEmpty() || m_EndRect.isEmpty()) {
+ QT3DS_ASSERT(false);
+ return;
+ }
+ // Find the start and end points.
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint =
+ GetEndpointPoint(m_Begin, *m_BeginRect, m_EndRect->center());
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theEndPoint =
+ GetEndpointPoint(m_End, *m_EndRect, m_BeginRect->center());
+ m_Path.push_back(theStartPoint.first);
+ SControlPoint theLastControlPoint(EdgeTypeToDirectionType(theStartPoint.second));
+ theLastControlPoint.Set(theStartPoint.first);
+ theLastControlPoint.m_PathIndex = 0;
+ SControlPoint theEndControlPoint(EdgeTypeToDirectionType(theEndPoint.second));
+ theEndControlPoint.Set(theEndPoint.first);
+ for (size_t idx = 0, end = m_ControlPoints.size(); idx < end; ++idx) {
+ const SControlPoint &thePoint(m_ControlPoints[idx]);
+ if (thePoint.m_Direction == theLastControlPoint.m_Direction) {
+ // zig zag. Requires us to find the first point that *isn't a zig zag in
+ // order to
+ // calculate where the zig zag should be. We could have a section composed
+ // of only
+ // parallel directions and we can't lay then out until we find how much
+ // distance we have
+ // to space each on out.
+ // Image you get to this point and you find you have a set of control points
+ // with vertical direction.
+ // Their positions will tell us how far left each one should sit. But we
+ // don't have enough information
+ // to lay them out without knowing how much vertical space this section
+ // should fill. So we would have to
+ // search forward until we can figure this out.
+ QT3DSVec2 runStart = m_Path.back();
+ // Search forward until either we run out of points or until we find a point
+ // who's direction
+ // does not match the current direction. We call a contiguous set of
+ // control points who all
+ // have the same direction a 'run'.
+ size_t runEnd;
+ size_t zigzagCount = 1;
+ DirectionTypes::Enum runDirection = theLastControlPoint.m_Direction;
+ // Search forward till we find a point that is different.
+ for (runEnd = idx + 1; runEnd < end
+ && m_ControlPoints[runEnd].m_Direction == thePoint.m_Direction;
+ ++runEnd) {
+ // Skip items that are in line. They shouldn't be counted towards our
+ // zigzag count.
+ if (AreAlmostEqual(m_ControlPoints[runEnd].m_Position,
+ m_ControlPoints[runEnd - 1].m_Position)
+ == false)
+ ++zigzagCount;
+ }
+ // Two possible cases. Either we find a control point that has a different
+ // direction in which case we then figure out
+ // how much space we need overall *or* we ran out of control points in which
+ // case we use the end point.
+ QT3DSVec2 runEndPoint(0, 0);
+ if (runEnd == end) {
+ // check if the end point direction is the same. This could be the
+ // final zig zag. Else it will be a righthand turn
+ if (EdgeTypeToDirectionType(theEndPoint.second) == runDirection
+ && AreAlmostEqual(theEndControlPoint.m_Position,
+ thePoint.m_Position)
+ == false)
+ ++zigzagCount;
+
+ runEndPoint = theEndPoint.first;
+ } else {
+ SLine thePointLine(thePoint.ToLine());
+ Option<QT3DSF32> isect =
+ thePointLine.intersect(m_ControlPoints[runEnd].ToLine());
+ if (isect.hasValue())
+ runEndPoint = thePointLine.toPoint(*isect);
+ else {
+ QT3DS_ASSERT(false);
+ }
+ }
+ QT3DSF32 runOrthoStart =
+ SControlPoint::GetOrthogonalComponent(runStart, runDirection);
+ QT3DSF32 runOrthoEnd =
+ SControlPoint::GetOrthogonalComponent(runEndPoint, runDirection);
+ QT3DSF32 runRange = runOrthoEnd - runOrthoStart;
+ QT3DSF32 runWidth = runRange / (QT3DSF32)zigzagCount;
+ // Now we iterate through the run itself and output path elements.
+ for (; idx < runEnd; ++idx) {
+ theLastControlPoint = OutputParallelPoints(
+ theLastControlPoint, m_ControlPoints[idx], runWidth);
+ }
+ // Subtract one to account for the loop upate that happens next
+ --idx;
+ } else // right angle
+ {
+ theLastControlPoint =
+ OutputOrthogonalPoints(theLastControlPoint, m_ControlPoints[idx]);
+ }
+ }
+ // Finished iterating through the control points. Now we have the sticky situation
+ // of the very last point
+ // and how it joins with the end point.
+ QT3DSVec2 lastPoint(m_Path.back());
+ if (theEndControlPoint.m_Direction == theLastControlPoint.m_Direction) {
+ QT3DSF32 lastPointOrthoComponent = SControlPoint::GetOrthogonalComponent(
+ lastPoint, theLastControlPoint.m_Direction);
+ QT3DSF32 endOrthoComponent = SControlPoint::GetOrthogonalComponent(
+ theEndPoint.first, theLastControlPoint.m_Direction);
+ QT3DSF32 runWidth = endOrthoComponent - lastPointOrthoComponent;
+ OutputParallelPoints(theLastControlPoint, theEndControlPoint, runWidth);
+ } else {
+ theLastControlPoint =
+ OutputOrthogonalPoints(theLastControlPoint, theEndControlPoint);
+ }
+ // Now output the last possible point which matches the end control point's
+ // direction and such.
+ theEndControlPoint.m_PathIndex = (QT3DSI32)(m_Path.size() - 1);
+#ifdef _DEBUG
+ // The directly previous possible point in the list should not match this point. In
+ // fact, it really should be orthogonal.
+ if (m_PossibleControlPoints.size()) {
+ QT3DS_ASSERT(m_PossibleControlPoints.back().m_Direction
+ != theEndControlPoint.m_Direction
+ || AreAlmostEqual(m_PossibleControlPoints.back().m_Position,
+ theEndControlPoint.m_Position)
+ == false);
+ }
+#endif
+ m_PossibleControlPoints.push_back(theEndControlPoint);
+ // Finally push back the last item.
+ m_Path.push_back(theEndPoint.first);
+ } else if (m_TransitionPathType == TransitionPathTypes::BeginToBegin
+ || m_TransitionPathType == TransitionPathTypes::Targetless) {
+ QT3DSVec2 beginCenter(m_BeginRect->center());
+ beginCenter.x += 1;
+ eastl::pair<QT3DSVec2, EdgeTypes::Enum> theStartPoint =
+ GetEndpointPoint(m_Begin, *m_BeginRect, beginCenter);
+ QT3DSVec2 lineDir;
+ m_Path.push_back(theStartPoint.first);
+ if (m_TransitionPathType == TransitionPathTypes::BeginToBegin) {
+ switch (theStartPoint.second) {
+ case EdgeTypes::Top:
+ lineDir = QT3DSVec2(0, -1);
+ break;
+ case EdgeTypes::Bottom:
+ lineDir = QT3DSVec2(0, 1);
+ break;
+ case EdgeTypes::Left:
+ lineDir = QT3DSVec2(-1, 0);
+ break;
+ case EdgeTypes::Right:
+ lineDir = QT3DSVec2(1, 0);
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+ QT3DSF32 squareDiagLen = 30;
+ QT3DSF32 halfDiag = squareDiagLen / 2.0f;
+ QT3DSVec2 theOppPoint = theStartPoint.first + lineDir * squareDiagLen;
+ QT3DSVec2 middle = (theStartPoint.first + theOppPoint) / 2.0f;
+ QT3DSVec2 orthoLineDir = QT3DSVec2(lineDir.y, lineDir.x);
+ QT3DSVec2 loopTop = middle + orthoLineDir * halfDiag;
+ QT3DSVec2 loopBottom = middle - orthoLineDir * halfDiag;
+ m_Path.push_back(loopTop);
+ m_Path.push_back(theOppPoint);
+ m_Path.push_back(loopBottom);
+ m_Path.push_back(theStartPoint.first);
+ }
+ } else {
+ QT3DS_ASSERT(false);
+ }
+ }
+ }
+}
+} \ No newline at end of file