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