1 /* -------------------------------------------------------------------------- *
2 * Simbody(tm): SimTKmath *
3 * -------------------------------------------------------------------------- *
4 * This is part of the SimTK biosimulation toolkit originating from *
5 * Simbios, the NIH National Center for Physics-Based Simulation of *
6 * Biological Structures at Stanford, funded under the NIH Roadmap for *
7 * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. *
8 * *
9 * Portions copyright (c) 2012 Stanford University and the Authors. *
10 * Authors: Ian Stavness *
11 * Contributors: Michael Sherman, Andreas Scholz *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24 #include "SimTKcommon.h"
25 #include "simmath/internal/common.h"
26 #include "simmath/internal/Geo.h"
27 #include "simmath/internal/Geo_Point.h"
28 #include "simmath/internal/Geo_Sphere.h"
29 #include "simmath/internal/ContactGeometry.h"
30
31 #include "ContactGeometryImpl.h"
32
33 #include <iostream>
34 #include <cmath>
35 #include <map>
36 #include <set>
37
38 using namespace SimTK;
39 using std::map;
40 using std::pair;
41 using std::set;
42 using std::string;
43 using std::cout; using std::endl;
44
45 //==============================================================================
46 // CONTACT GEOMETRY :: CYLINDER & IMPL
47 //==============================================================================
48
Cylinder(Real radius)49 ContactGeometry::Cylinder::Cylinder(Real radius)
50 : ContactGeometry(new Cylinder::Impl(radius)) {}
51
classTypeId()52 /*static*/ ContactGeometryTypeId ContactGeometry::Cylinder::classTypeId()
53 { return ContactGeometry::Cylinder::Impl::classTypeId(); }
54
getRadius() const55 Real ContactGeometry::Cylinder::getRadius() const {
56 return getImpl().getRadius();
57 }
58
setRadius(Real radius)59 void ContactGeometry::Cylinder::setRadius(Real radius) {
60 updImpl().setRadius(radius);
61 }
62
getImpl() const63 const ContactGeometry::Cylinder::Impl& ContactGeometry::Cylinder::getImpl() const {
64 assert(impl);
65 return static_cast<const Cylinder::Impl&>(*impl);
66 }
67
updImpl()68 ContactGeometry::Cylinder::Impl& ContactGeometry::Cylinder::updImpl() {
69 assert(impl);
70 return static_cast<Cylinder::Impl&>(*impl);
71 }
72
createDecorativeGeometry() const73 DecorativeGeometry ContactGeometry::Cylinder::Impl::createDecorativeGeometry() const {
74 DecorativeCylinder cyl(radius, radius*2);
75 // DecorativeCylinder's axis is defined as the y-axis,
76 // whereas ContactGeometry::Cylinder axis is defined as the z-axis
77 cyl.setTransform(Rotation(Pi/2, XAxis));
78 return cyl;
79 }
80
findNearestPoint(const Vec3 & position,bool & inside,UnitVec3 & normal) const81 Vec3 ContactGeometry::Cylinder::Impl::findNearestPoint(const Vec3& position, bool& inside, UnitVec3& normal) const {
82
83 normal = calcSurfaceUnitNormal(position);
84
85 // long axis is z-axis, project to x-y plane
86 Vec2 xy_position(position(0), position(1));
87 inside = (xy_position.normSqr() <= radius*radius);
88
89 // nearestPoint = point_on_surface_in_xy_plane + height_in_z
90 Vec3 nearestPoint = normal*radius + Vec3(0,0,position(2));
91
92 return nearestPoint;
93 }
94
intersectsRay(const Vec3 & origin,const UnitVec3 & direction,Real & distance,UnitVec3 & normal) const95 bool ContactGeometry::Cylinder::Impl::intersectsRay
96 (const Vec3& origin, const UnitVec3& direction,
97 Real& distance, UnitVec3& normal) const
98 {
99 // cylinder axis is z-axis, project to x-y plane
100 const Vec3 xy_vec(direction(0),direction(1),0);
101 const Real xy_vec_norm = xy_vec.norm();
102 const UnitVec3 xy_direction(xy_vec/xy_vec_norm, true); // don't renormalize
103 const Vec3 xy_origin(origin(0),origin(1),0);
104 Real xy_distance;
105 Real b = -~xy_direction*xy_origin;
106 Real c = xy_origin.normSqr() - radius*radius;
107 if (c > 0) {
108 // Ray origin is outside cylinder.
109
110 if (b <= 0)
111 return false; // Ray points away from axis of cylinder.
112 Real d = b*b - c;
113 if (d < 0)
114 return false;
115 Real root = std::sqrt(d);
116 xy_distance = b - root;
117 }
118 else {
119 // Ray origin is inside cylinder.
120
121 Real d = b*b - c;
122 if (d < 0)
123 return false;
124 xy_distance = b + std::sqrt(d);
125 }
126 distance = xy_distance/xy_vec_norm;
127 normal = UnitVec3(xy_origin+xy_distance*xy_direction);
128 return true;
129 }
130
getBoundingSphere(Vec3 & center,Real & radius) const131 void ContactGeometry::Cylinder::Impl::getBoundingSphere
132 (Vec3& center, Real& radius) const {
133 center = Vec3(0);
134 radius = Infinity;
135 }
136
137 void ContactGeometry::Cylinder::Impl::
calcCurvature(const Vec3 & point,Vec2 & curvature,Rotation & orientation) const138 calcCurvature(const Vec3& point, Vec2& curvature, Rotation& orientation) const {
139
140 // long axis (direction of min curvature) points in <0,0,1>
141 orientation = Rotation(calcSurfaceUnitNormal(point), ZAxis, Vec3(0, 0, 1), YAxis);
142 curvature[0] = 1/radius;
143 curvature[1] = 0;
144
145 }
146
147 // Sample geodesic between two points P and Q on a cylinder analytically.
setGeodesicToHelicalArc(Real R,Real phiP,Real angle,Real m,Real c,Geodesic & geod)148 static void setGeodesicToHelicalArc(Real R, Real phiP, Real angle, Real m, Real c, Geodesic& geod)
149 {
150 // Clear current geodesic.
151 geod.clear();
152
153 const Real sqrt1m2 = sqrt(1+m*m); // Avoid repeated calculation.
154 const Real kappa = 1 / (R*(1+m*m)); // Curvature in tangent direction.
155 const Real kb = 1 / (R*(1+1/(m*m))); // Curvature in binormal direction
156 // (slope is 1/m).
157 const Real tau = m*kappa; // Torsion (signed).
158
159 // Arc length of the helix. Always
160 const Real L = R * sqrt1m2 * std::abs(angle);
161
162 // Orientation of helix.
163 const Real orientation = angle < 0 ? Real(-1) : Real(1);
164
165 // TODO: Make this generic, so long geodesics are sampled more than short ones.
166 const int numGeodesicSamples = 12;
167 const Real deltaPhi = std::abs(angle / Real(numGeodesicSamples-1));
168
169 for (int i = 0; i < numGeodesicSamples; ++i)
170 {
171 // Watch out: Angle phi has an offset phiP
172 Real phi = Real(i)*angle/Real(numGeodesicSamples-1) + phiP;
173 const Real sphi = sin(phi), cphi = cos(phi);
174
175 // Evaluate helix.
176 Vec3 p( R*cphi, R*sphi, R*m*(phi - phiP) + c);
177
178 // We'll normalize so UnitVec3 doesn't have to do it.
179 UnitVec3 t((orientation/sqrt1m2)*Vec3(-sphi, cphi, m), true);
180 UnitVec3 n(Vec3(cphi, sphi, 0), true);
181
182 // Though not needed, we use an orthogonalizing constructor for the rotation.
183 geod.addFrenetFrame(Transform(Rotation(n, ZAxis, t, YAxis), p));
184
185 // Current arc length s.
186 Real s = R * sqrt1m2 * (Real(i)*deltaPhi);
187 geod.addArcLength(s);
188 geod.addCurvature(kappa);
189
190 // Solve the scalar Jacobi equation
191 //
192 // j''(s) + K(s)*j(s) = 0 , (1)
193 //
194 // where K is the Gaussian curvature and (.)' := d(.)/ds denotes differentiation
195 // with respect to the arc length s. Then, j is the directional sensitivity and
196 // we obtain the corresponding variational vector field by multiplying b*j. For
197 // a cylinder, K = 0 and the solution of equation (1) becomes
198 //
199 // j = s (2)
200 // j' = 1 , (3)
201 //
202 // so the Jacobi field increases linearly in s.
203
204 // Forward directional sensitivity from P to Q
205 Vec2 jPQ(s, 1);
206 geod.addDirectionalSensitivityPtoQ(jPQ);
207
208 // Backwards directional sensitivity from Q to P
209 Vec2 jQP(L-s, 1);
210 geod.addDirectionalSensitivityQtoP(jQP);
211
212
213 // TODO: positional sensitivity
214 geod.addPositionalSensitivityPtoQ(Vec2(NaN));
215 geod.addPositionalSensitivityQtoP(Vec2(NaN));
216 }
217
218 // Only compute torsion and binormal curvature at the end points.
219 geod.setTorsionAtP(tau); geod.setTorsionAtQ(tau);
220 geod.setBinormalCurvatureAtP(kb); geod.setBinormalCurvatureAtQ(kb);
221
222 geod.setIsConvex(true); // Curve on cylinder is always convex.
223
224 geod.setIsShortest(false); // TODO
225 geod.setAchievedAccuracy(SignificantReal); // TODO: accuracy of length?
226 // geod.setInitialStepSizeHint(integ.getActualInitialStepSizeTaken()); // TODO
227 }
228
229 // Compute geodesic between two points P and Q on a cylinder analytically. Since a geodesic on a
230 // cylinder is a helix it is parameterized by
231 //
232 // [ R * cos(phi) ]
233 // p(phi) = [ R * sin(phi) ]
234 // [ R * m * phi + c ]
235 //
236 // where R is the radius of the cylinder, phi parameterizes the opening angle of the helix, m is
237 // the slope and c is an offset. We define the geodesic from P to Q, hence c = Pz.
238 void ContactGeometry::Cylinder::Impl::
calcGeodesicAnalytical(const Vec3 & xP,const Vec3 & xQ,const Vec3 & tPhint,const Vec3 & tQhint,Geodesic & geod) const239 calcGeodesicAnalytical(const Vec3& xP, const Vec3& xQ,
240 const Vec3& tPhint, const Vec3& tQhint,
241 Geodesic& geod) const
242 {
243 // Compute angle between P and Q. Save both the positive (right handed) and the negative
244 // (left handed) angle.
245 Real phiP = atan2(xP[1], xP[0]);
246 Real phiQ = atan2(xQ[1], xQ[0]);
247
248 Real temp = phiQ - phiP;
249 Real angleRightHanded, angleLeftHanded;
250
251 // Left-handed angle will always be negative, right-handed angle will be positive.
252 if (temp >= 0) {
253 angleRightHanded = temp;
254 angleLeftHanded = temp - 2*Pi;
255 }
256
257 else {
258 angleLeftHanded = temp;
259 angleRightHanded = temp + 2*Pi;
260 }
261
262 // Compute "moment" of tPhint at P and tQhint at Q around z-Axis.
263 // Make sure tPhint and tQhint are unit vectors, otherwise moments are scaled.
264 Real MP = xP[0]*tPhint[1] - xP[1]*tPhint[0];
265 Real MQ = xQ[0]*tQhint[1] - xQ[1]*tQhint[0];
266
267 // Average moment.
268 Real M = (MP + MQ) / 2;
269
270 // Decide whether helix is right or left handed. The sign of angle stores the
271 // information about the orientation (right handed, if positive)
272 Real angle;
273 if (M >= 0) {
274 angle = angleRightHanded;
275 }
276
277 else {
278 angle = angleLeftHanded;
279 }
280
281 // Offset and slope.
282 Real c = xP[2];
283 Real m = (xQ[2] - xP[2]) / (angle * radius);
284
285 setGeodesicToHelicalArc(radius, phiP, angle, m, c, geod);
286 }
287
shootGeodesicInDirectionUntilLengthReachedAnalytical(const Vec3 & xP,const UnitVec3 & tP,const Real & terminatingLength,const GeodesicOptions & options,Geodesic & geod) const288 void ContactGeometry::Cylinder::Impl::shootGeodesicInDirectionUntilLengthReachedAnalytical(const Vec3& xP, const UnitVec3& tP,
289 const Real& terminatingLength, const GeodesicOptions& options, Geodesic& geod) const {
290
291 //TODO for Andreas :)
292 }
293
shootGeodesicInDirectionUntilPlaneHitAnalytical(const Vec3 & xP,const UnitVec3 & tP,const Plane & terminatingPlane,const GeodesicOptions & options,Geodesic & geod) const294 void ContactGeometry::Cylinder::Impl::shootGeodesicInDirectionUntilPlaneHitAnalytical(const Vec3& xP, const UnitVec3& tP,
295 const Plane& terminatingPlane, const GeodesicOptions& options,
296 Geodesic& geod) const {
297
298 //TODO for Andreas :)
299 }
300
301 Real CylinderImplicitFunction::
calcValue(const Vector & x) const302 calcValue(const Vector& x) const {
303 return 1-(x[0]*x[0]+x[1]*x[1])/square(ownerp->getRadius());
304 }
305
306 Real CylinderImplicitFunction::
calcDerivative(const Array_<int> & derivComponents,const Vector & x) const307 calcDerivative(const Array_<int>& derivComponents, const Vector& x) const {
308 if (derivComponents.size() == 1 && derivComponents[0] < 2)
309 return -2*x[derivComponents[0]]/square(ownerp->getRadius());
310 if (derivComponents.size() == 2 &&
311 derivComponents[0] == derivComponents[1] &&
312 derivComponents[0] < 2 )
313 return -2/square(ownerp->getRadius());
314 return 0;
315 }
316