Skip to content

Commit 00714f6

Browse files
authored
Merge pull request opensim-org#3504 from ComputationalBiomechanicsLab/wrapCylinderTestAddRotationAssertion
Wrap Cylinder Test: Adds rotation direction assertion
2 parents a6f1af5 + 84ed03b commit 00714f6

1 file changed

Lines changed: 90 additions & 2 deletions

File tree

OpenSim/Tests/Wrapping/testWrapCylinder.cpp

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
using namespace OpenSim;
3434

3535
namespace {
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

Comments
 (0)