3333using namespace OpenSim ;
3434
3535namespace {
36+ constexpr double c_TAU = 2 . * SimTK_PI;
3637
3738 // A path segment determined in terms of the start and end point.
3839 struct PathSegment final {
@@ -49,6 +50,11 @@ namespace {
4950 SimTK::Vec3 end{SimTK::NaN};
5051 };
5152
53+ // Returns PathSegment with start and end swapped.
54+ PathSegment Reversed (const PathSegment& path) {
55+ return PathSegment{path.end , path.start };
56+ }
57+
5258 std::ostream& operator <<(
5359 std::ostream& os,
5460 const PathSegment& path)
@@ -57,6 +63,71 @@ namespace {
5763 " PathSegment{start: " << path.start << " , end: " << path.end << " }" ;
5864 }
5965
66+ PathSegment operator *(
67+ const SimTK::Rotation& rot,
68+ const PathSegment& path)
69+ {
70+ return PathSegment{
71+ rot * path.start ,
72+ rot * path.end ,
73+ };
74+ }
75+
76+ enum class RotationDirection {
77+ Positive,
78+ Negative,
79+ };
80+
81+ std::ostream& operator <<(
82+ std::ostream& os,
83+ const RotationDirection& direction)
84+ {
85+ return os << " RotationDirection::" << (
86+ (direction == RotationDirection::Positive)? " Positive" : " Negative" );
87+ }
88+
89+ // Angular distance from start- to end-angle in either positive or negative
90+ // direction.
91+ double AngularDistance (
92+ double startAngle,
93+ double endAngle,
94+ RotationDirection direction)
95+ {
96+ double distance = std::fmod (endAngle - startAngle, c_TAU);
97+ while (distance < 0 . && direction == RotationDirection::Positive) {
98+ distance += c_TAU;
99+ }
100+ while (distance > 0 . && direction == RotationDirection::Negative) {
101+ distance -= c_TAU;
102+ }
103+ return distance;
104+ }
105+
106+ // Returns direction of shortest angular distance for a vector aligned with the
107+ // start point to become aligned with the end point (true if positive).
108+ RotationDirection DirectionOfShortestAngularDistance (
109+ SimTK::Vec2 start,
110+ SimTK::Vec2 end)
111+ {
112+ // Compute angular distance assuming positive rotation.
113+ double distance = AngularDistance (
114+ std::atan2 (start[1 ], start[0 ]),
115+ std::atan2 (end[1 ], end[0 ]),
116+ RotationDirection::Positive);
117+ // Check if positive direction was the shortest path.
118+ return distance <= SimTK::Pi?
119+ RotationDirection::Positive:
120+ RotationDirection::Negative;
121+ }
122+
123+ RotationDirection DirectionOfShortestAngularDistanceAboutZAxis (
124+ const PathSegment& path)
125+ {
126+ return DirectionOfShortestAngularDistance (
127+ path.start .getSubVec <2 >(0 ),
128+ path.end .getSubVec <2 >(0 ));
129+ }
130+
60131}
61132
62133// Section with helpers for evaluating the wrapping result in the upcoming test.
@@ -75,6 +146,8 @@ namespace {
75146 PathSegment path;
76147 // Path segment length.
77148 double length = SimTK::NaN;
149+ // Direction of wrapping wrt cylinder axis.
150+ RotationDirection direction = RotationDirection::Positive;
78151 // True if there is no wrapping (the other fields don't matter).
79152 bool noWrap = false ;
80153 };
@@ -89,7 +162,8 @@ namespace {
89162 return os <<
90163 " WrapTestResult{" <<
91164 " path: " << result.path << " , " <<
92- " length: " << result.length << " }" ;
165+ " length: " << result.length << " , " <<
166+ " direction: " << result.direction << " }" ;
93167 }
94168
95169 // Struct holding the tolerances when asserting the wrapping result.
@@ -142,6 +216,7 @@ namespace {
142216 }
143217 return IsEqualWithinTolerance (lhs.path , rhs.path , tolerance.position )
144218 && IsEqualWithinTolerance (lhs.length , rhs.length , tolerance.length )
219+ && lhs.direction == rhs.direction
145220 && lhs.noWrap == rhs.noWrap ;
146221 }
147222
@@ -165,7 +240,7 @@ namespace {
165240 }
166241
167242 // Endpoints of the total path:
168- PathSegment path = {{}, {}} ;
243+ PathSegment path;
169244
170245 // Wrapping cylinder parameters:
171246 double radius = 1 .;
@@ -244,6 +319,12 @@ namespace {
244319 wrapResult.r2 ,
245320 };
246321 result.length = wrapResult.wrap_path_length ;
322+ // Determine the wrapping sign based on the first path segment.
323+ result.direction = DirectionOfShortestAngularDistanceAboutZAxis (
324+ input.cylinderOrientation ().invert () * PathSegment{
325+ input.path .start ,
326+ wrapResult.r1 ,
327+ });
247328 result.noWrap = wrapResult.wrap_pts .size () == 0 ;
248329
249330 return result;
@@ -320,6 +401,13 @@ int main()
320401
321402 failLog.push_back (TestWrapping (input, expected, tolerance, name));
322403
404+ // Swapping start and end should not change the path:
405+ input.path = Reversed (input.path );
406+ expected.path = Reversed (expected.path );
407+ expected.direction = RotationDirection::Negative;
408+
409+ failLog.push_back (TestWrapping (input, expected, tolerance, name));
410+
323411 // =========================================================================
324412 // ====================== Handling of Test Results =========================
325413 // =========================================================================
0 commit comments