opynsim
Unofficial C++ Documentation
Loading...
Searching...
No Matches
thin_plate_spline_wrap_cylinder_scaling_step.h
Go to the documentation of this file.
1#pragma once
2
9
11#include <OpenSim/Simulation/Wrap/WrapCylinder.h>
12#include <SimTKcommon/internal/Rotation.h>
13#include <SimTKcommon/internal/UnitVec.h>
14#include <SimTKcommon/SmallMatrix.h>
15
16#include <sstream>
17#include <string>
18#include <utility>
19#include <vector>
20
21namespace opyn
22{
23 // A `ThinPlateSpline` scaling step that tries to scale the origin, orientation, radius,
24 // length, and quadrant of the given `WrapCylinder`s.
27
28 OpenSim_DECLARE_LIST_PROPERTY(wrap_cylinders, std::string, "Absolute paths (e.g. `/bodyset/body/wrap_cylinder_2`) to `WrapCylinder` components in the model");
29 OpenSim_DECLARE_PROPERTY(midline_projection_distance, double, "The distance, in meters, that the `WrapCylinder`s' midlines should be projected from each of their origin points before putting them through the TPS algorithm. This is used to figure out the new orientation of the `WrapCylinder`s.");
30 OpenSim_DECLARE_PROPERTY(surface_projection_theta, double, "An angle, in radians, of a vector that originates in each `WrapCylinder`'s origin, spins around their Z axis with an angle of theta from the X axis, and has a length of each `WrapCylinder`'s `radius`, producing a surface point. Each surface point is fed through the TPS algorithm and used to recalculate each `WrapCylinder`'s `radius` and `quadrant`");
31 public:
33 ToggleableThinPlateSplineScalingStep{"Apply Thin-Plate Spline (TPS) to WrapCylinder"}
34 {
35 setDescription(R"(
36Uses the Thin-Plate Spline (TPS) warping algorithm to scale `WrapCylinder`s in the model:
37
38 - Warps the `translation` of each `WrapCylinder` by warping the source `translation` with
39 the TPS algorithm to produce a new `translation`.
40 - Warps the `xyz_body_rotation` of each `WrapCylinder` by projecting a point from its
41 source origin (`translation`) `midline_projection_distance` along the cylinder's
42 midline (+Z), warping it with the TPS algorithm, and then calculating a new
43 `xyz_body_orientation` that orients the `WrapCylinder` such that the vector between the
44 new origin (the new `translation`) and the projected point becomes the new
45 `WrapCylinder` midline. To prevent the cylinder from spinning along this vector, the
46 along-axis rotation is constrained by the location of a point produced by
47 `surface_projection_theta`.
48 - Warps the `radius` of each `WrapCylinder` by calculating a point on each of their surfaces
49 specified `surface_projection_theta`. The points are then warped with the TPS algorithm to
50 produce (presumed to be) new surface points. The `radius` of each `WrapCylinder` is equal
51 to the distance between their respective midlines and projected surface points.
52 - Does not warp `quadrant`. It is assumed that constraining the calculation of `xyz_body_rotation`
53 will cause the warped cylinder to have a like-for-like orientation with respect to whatever's
54 being wrapped.
55 - Does not warp `length`. There is (currently) no easy way to do this, because the ends of
56 `WrapCylinder`s tend to be far away from where the TPS warp is defined, which makes it hard
57 to warp+project the endcaps. It is recommended that `WrapCylinder`s are long enough to ensure
58 they are not sensitive to model scaling (very few models require muscles to fall of the end
59 of the cylinder).
60)");
61 constructProperty_wrap_cylinders();
62 constructProperty_midline_projection_distance(0.001); // 1 mm
63 constructProperty_surface_projection_theta(0.0);
64 }
65 private:
66 std::vector<ScalingStepValidationMessage> implValidate(
67 ScalingCache& cache,
68 const ScalingParameters& params,
69 const OpenSim::Model& sourceModel) const final
70 {
71 // Get base class validation messages.
72 auto messages = ToggleableThinPlateSplineScalingStep::implValidate(cache, params, sourceModel);
73
74 // Ensure every entry in `wrap_cylinders` can be found in the source model.
75 for (int i = 0; i < getProperty_wrap_cylinders().size(); ++i) {
76 const auto* offsetFrame = FindComponent<OpenSim::WrapCylinder>(sourceModel, get_wrap_cylinders(i));
77 if (not offsetFrame) {
78 std::stringstream msg;
79 msg << get_wrap_cylinders(i) << ": Cannot find a `WrapCylinder` in 'wrap_cylinders' in the source model (or it isn't a `WrapCylinder`).";
80 messages.emplace_back(ScalingStepValidationState::Error, std::move(msg).str());
81 }
82 }
83
84 return messages;
85 }
86
88 ScalingCache& cache,
89 const ScalingParameters& parameters,
90 const OpenSim::Model& sourceModel,
91 OpenSim::Model& resultModel) const final
92 {
93 // Lookup/validate warping inputs.
94 const auto commonParams = calcTPSScalingStepCommonParams(parameters, sourceModel, resultModel);
95
96 // Precalculate the surface point direction vector as "rotate a unit vector pointing
97 // along X around the Z axis by theta amount" (see property documentation).
98 const SimTK::Rotation rotateCylinderXToSurfacePointDirection{get_surface_projection_theta(), SimTK::ZAxis};
99 const SimTK::UnitVec3 surfacePointDirectionInCylinderSpace{rotateCylinderXToSurfacePointDirection * SimTK::Vec3{1.0, 0.0, 0.0}};
100
101 // Precalculate the midline projection point in the cylinder's local (not parent) space.
102 const SimTK::Vec3 midlinePointInCylinderSpace{0.0, 0.0, get_midline_projection_distance()};
103
104 // Warp each `WrapCylinder` specified by the `wrap_cylinders` property.
105 for (int i = 0; i < getProperty_wrap_cylinders().size(); ++i) {
106 const auto* sourceWrapCylinder = FindComponent<OpenSim::WrapCylinder>(sourceModel, get_wrap_cylinders(i));
107 OSC_ASSERT_ALWAYS(sourceWrapCylinder && "could not find a `WrapCylinder` in the source model");
108
109 // Find the `i`th `WrapCylinder` in the model.
110 auto* resultWrapCylinder = FindComponentMut<OpenSim::WrapCylinder>(resultModel, get_wrap_cylinders(i));
111 OSC_ASSERT_ALWAYS(resultWrapCylinder && "could not find a `WrapCylinder` in the model");
112
113 // Calculate the `WrapCylinder`'s new `translation` by warping the origin.
114 const SimTK::Vec3 newOriginPointInParent = cache.lookupTPSWarpedRigidPoint(
115 sourceModel,
116 resultModel,
117 sourceWrapCylinder->get_translation(),
118 resultWrapCylinder->get_translation(),
119 sourceWrapCylinder->getFrame(),
120 resultWrapCylinder->getFrame(),
121 *commonParams.sourceLandmarksFrame,
122 *commonParams.resultLandmarksFrame,
123 commonParams.tpsInputs,
124 commonParams.compensateForFrameChanges
125 );
126
127 // Calculate the `WrapCylinder`'s new projected midline point by warping it.
128 const SimTK::Vec3 sourceMidlinePointInParent = sourceWrapCylinder->getTransform() * midlinePointInCylinderSpace;
129 const SimTK::Vec3 resultMidlinePointInParent = resultWrapCylinder->getTransform() * midlinePointInCylinderSpace;
130 const SimTK::Vec3 newMidlinePointInParent = cache.lookupTPSWarpedRigidPoint(
131 sourceModel,
132 resultModel,
133 sourceMidlinePointInParent,
134 resultMidlinePointInParent,
135 sourceWrapCylinder->getFrame(),
136 resultWrapCylinder->getFrame(),
137 *commonParams.sourceLandmarksFrame,
138 *commonParams.resultLandmarksFrame,
139 commonParams.tpsInputs,
140 commonParams.compensateForFrameChanges
141 );
142
143 // Calculate the source surface point by projecting the direction onto the `WrapCylinder`'s surface.
144 const SimTK::Vec3 sourceSurfacePointInCylinderSpace = sourceWrapCylinder->get_radius() * surfacePointDirectionInCylinderSpace;
145 const SimTK::Vec3 resultSurfacePointInCylinderSpace = resultWrapCylinder->get_radius() * surfacePointDirectionInCylinderSpace;
146 const SimTK::Vec3 sourceSurfacePointInParent = sourceWrapCylinder->getTransform() * sourceSurfacePointInCylinderSpace;
147 const SimTK::Vec3 resultSurfacePointInParent = resultWrapCylinder->getTransform() * resultSurfacePointInCylinderSpace;
148 const SimTK::Vec3 newSurfacePointInParent = cache.lookupTPSWarpedRigidPoint(
149 sourceModel,
150 resultModel,
151 sourceSurfacePointInParent,
152 resultSurfacePointInParent,
153 sourceWrapCylinder->getFrame(),
154 resultWrapCylinder->getFrame(),
155 *commonParams.sourceLandmarksFrame,
156 *commonParams.resultLandmarksFrame,
157 commonParams.tpsInputs,
158 commonParams.compensateForFrameChanges
159 );
160
161 // The `WrapCylinder`'s new Z axis within the parent frame is a unit vector that
162 // points from its new origin to the projected midline point.
163 const SimTK::UnitVec3 newZAxisDirectionInParent{newMidlinePointInParent - newOriginPointInParent};
164
165 // We can now use the warped surface point to figure out what the orientation _around_
166 // Z should be (i.e. to constrain it, rather than letting the orientation spin along
167 // the Z axis).
168 //
169 // Due to the non-linearity of TPS, the warped surface point may require
170 // reorthogonalization with respect to the `WrapCylinder`'s new midline. Here, we use
171 // vector rejection (the opposite of vector projection) to compute an orthogonal vector
172 // that we can un-spin by `surface_projection_theta` in order to figure out exactly where
173 // the X axis should point.
174 const SimTK::Vec3 newSurfacePointVectorInParent{newSurfacePointInParent - newOriginPointInParent};
175 const SimTK::UnitVec3 newSurfacePointDirectionInParent{newSurfacePointVectorInParent};
176 OSC_ASSERT((SimTK::dot(newZAxisDirectionInParent, newSurfacePointDirectionInParent) < 1.0 - SimTK::Eps) && "cylinder surface point somehow points along Z axis - warping is too strong?");
177 const SimTK::Vec3 newSurfacePointProjectionAlongZVector = newZAxisDirectionInParent * SimTK::dot(newZAxisDirectionInParent, newSurfacePointDirectionInParent);
178 const SimTK::Vec3 newSurfacePointRejectionVector = newSurfacePointVectorInParent - newSurfacePointProjectionAlongZVector;
179 const SimTK::UnitVec3 newSurfacePointRejectionDirection{newSurfacePointRejectionVector};
180
181 // The new surface point direction is assumed to be `surface_projection_theta` rotated along
182 // the new Z axis from where the X axis should be (that's how it was initially generated), so
183 // un-rotate it to figure out where the new X axis should be.
184 const SimTK::UnitVec3 newXAxisDirectionInParent{rotateCylinderXToSurfacePointDirection.invert() * newSurfacePointRejectionDirection};
185
186 // With two vectors pointing along known axes, we can compute a new cylinder rotation
187 const SimTK::Rotation newCylinderRotation{newZAxisDirectionInParent, SimTK::ZAxis, newXAxisDirectionInParent, SimTK::XAxis};
188
189 // The new radius is the length of the rejection vector: i.e. the length of a vector
190 // orthogonal to Z (rejection) that points toward the surface point. Another way of
191 // thinking about this is that it's the shortest distance between the midline and
192 // the point
193 const double newRadius = newSurfacePointVectorInParent.norm();
194
195 // (finally) Update the cylinder
196 resultWrapCylinder->set_translation(newOriginPointInParent);
197 resultWrapCylinder->set_xyz_body_rotation(newCylinderRotation.convertRotationToBodyFixedXYZ());
198 resultWrapCylinder->set_radius(newRadius);
199 }
200 InitializeModel(resultModel);
201 InitializeState(resultModel);
202 }
203 };
204}
#define OSC_ASSERT(expr)
Definition assertions.h:26
#define OSC_ASSERT_ALWAYS(expr)
Definition assertions.h:20
Definition scaling_cache.h:30
Definition scaling_parameters.h:14
virtual std::vector< ScalingStepValidationMessage > implValidate(ScalingCache &, const ScalingParameters &, const OpenSim::Model &) const
Definition scaling_step.h:92
Definition thin_plate_spline_wrap_cylinder_scaling_step.h:25
std::vector< ScalingStepValidationMessage > implValidate(ScalingCache &cache, const ScalingParameters &params, const OpenSim::Model &sourceModel) const final
Definition thin_plate_spline_wrap_cylinder_scaling_step.h:66
ThinPlateSplineWrapCylinderScalingStep()
Definition thin_plate_spline_wrap_cylinder_scaling_step.h:32
void implApplyScalingStep(ScalingCache &cache, const ScalingParameters &parameters, const OpenSim::Model &sourceModel, OpenSim::Model &resultModel) const final
Definition thin_plate_spline_wrap_cylinder_scaling_step.h:87
Definition toggleable_thin_plate_spline_scaling_step.h:14
CommonParameters calcTPSScalingStepCommonParams(const ScalingParameters &parameters, const OpenSim::Model &sourceModel, const OpenSim::Model &resultModel) const
Definition toggleable_thin_plate_spline_scaling_step.h:32
Definition component_registry.h:14
void InitializeModel(OpenSim::Model &)
SimTK::State & InitializeState(OpenSim::Model &)