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) 2011-12 Stanford University and the Authors. *
10 * Authors: Matthew Millard, Michael Sherman *
11 * Contributors: *
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 /**@file
25 This file contains the library-side implementations of classes
26 BicubicSurface, BicubicSurface::Guts, and BicubicSurface::PatchHint. **/
27
28 #include "SimTKcommon.h"
29 #include "simmath/internal/common.h"
30 #include "simmath/internal/BicubicSurface.h"
31 #include "simmath/internal/SplineFitter.h"
32 #include "simmath/internal/ContactGeometry.h"
33
34 #include "BicubicSurface_Guts.h"
35
36 #include <algorithm>
37
38 using namespace SimTK;
39 using namespace std;
40
41 //==============================================================================
42 // BICUBIC SURFACE
43 //==============================================================================
44
45 // This is just a handle for BicubicSurface::Guts, which is the shared,
46 // underlying surface representation. Here we just manage the reference
47 // count and otherwise forward all requests to the Guts class.
48
49 // Default constructor is inline; just sets guts pointer to null.
50
51 // Destructor: delete guts if this is the last referencing handle.
~BicubicSurface()52 BicubicSurface::~BicubicSurface() {clear();}
53
54 // Copy constructor is shallow.
BicubicSurface(const BicubicSurface & source)55 BicubicSurface::BicubicSurface(const BicubicSurface& source) {
56 guts = source.guts; // copy pointer
57 if (guts) guts->incrReferenceCount();
58 }
59
60 // Copy assignment is shallow.
operator =(const BicubicSurface & source)61 BicubicSurface& BicubicSurface::operator=(const BicubicSurface& source) {
62 if (guts != source.guts) {
63 clear();
64 guts = source.guts; // copy pointer
65 if (guts) guts->incrReferenceCount();
66 }
67 return *this;
68 }
69
70 // Return handle to its default-constructed state, deleting guts if this was
71 // the last reference.
clear()72 void BicubicSurface::clear() {
73 if (guts && guts->decrReferenceCount()==0)
74 delete guts;
75 guts = 0;
76 }
77
78 // Constructor for irregularly spaced samples.
BicubicSurface(const Vector & x,const Vector & y,const Matrix & f,Real smoothness)79 BicubicSurface::BicubicSurface
80 (const Vector& x, const Vector& y, const Matrix& f, Real smoothness)
81 : guts(0) {
82 guts = new BicubicSurface::Guts(x,y,f,smoothness);
83 guts->incrReferenceCount(); // will be 1
84 }
85
86 // Constructor for regularly spaced samples.
BicubicSurface(const Vec2 & XY,const Vec2 & spacing,const Matrix & f,Real smoothness)87 BicubicSurface::BicubicSurface
88 (const Vec2& XY, const Vec2& spacing, const Matrix& f, Real smoothness)
89 : guts(0) {
90 guts = new BicubicSurface::Guts(XY,spacing,f,smoothness);
91 guts->incrReferenceCount(); // will be 1
92 }
93
94 // Constructor from known patch derivatives.
BicubicSurface(const Vector & x,const Vector & y,const Matrix & f,const Matrix & fx,const Matrix & fy,const Matrix & fxy)95 BicubicSurface::BicubicSurface
96 (const Vector& x, const Vector& y, const Matrix& f,
97 const Matrix& fx, const Matrix& fy, const Matrix& fxy)
98 : guts(0) {
99 guts = new BicubicSurface::Guts(x,y,f,fx,fy,fxy);
100 guts->incrReferenceCount(); // will be 1
101 }
102
103 // Constructor from known patch derivatives with regular spacing.
BicubicSurface(const Vec2 & XY,const Vec2 & spacing,const Matrix & f,const Matrix & fx,const Matrix & fy,const Matrix & fxy)104 BicubicSurface::BicubicSurface
105 (const Vec2& XY, const Vec2& spacing, const Matrix& f,
106 const Matrix& fx, const Matrix& fy, const Matrix& fxy)
107 : guts(0) {
108 guts = new BicubicSurface::Guts(XY,spacing,f,fx,fy,fxy);
109 guts->incrReferenceCount(); // will be 1
110 }
111
112 // calcValue(), the fast version.
calcValue(const Vec2 & XY,PatchHint & hint) const113 Real BicubicSurface::calcValue(const Vec2& XY, PatchHint& hint) const {
114 SimTK_ERRCHK_ALWAYS(!isEmpty(), "BicubicSurface::calcValue()",
115 "This method can't be called on an empty handle.");
116 return guts->calcValue(XY, hint);
117 }
118
119 // calcValue(), the slow version.
calcValue(const Vec2 & XY) const120 Real BicubicSurface::calcValue(const Vec2& XY) const {
121 PatchHint hint; // create an empty hint
122 return calcValue(XY, hint);
123 }
124
125 // calcDerivative(), the fast version.
calcDerivative(const Array_<int> & components,const Vec2 & XY,PatchHint & hint) const126 Real BicubicSurface::calcDerivative
127 (const Array_<int>& components, const Vec2& XY, PatchHint& hint) const {
128 SimTK_ERRCHK_ALWAYS(!isEmpty(), "BicubicSurface::calcDerivative()",
129 "This method can't be called on an empty handle.");
130 return guts->calcDerivative(components, XY, hint);
131 }
132
133 // calcDerivative(), the slow version.
calcDerivative(const Array_<int> & components,const Vec2 & XY) const134 Real BicubicSurface::calcDerivative
135 (const Array_<int>& components, const Vec2& XY) const {
136 PatchHint hint; // create an empty hint
137 return calcDerivative(components, XY, hint);
138 }
139
calcUnitNormal(const Vec2 & XY,PatchHint & hint) const140 UnitVec3 BicubicSurface::calcUnitNormal(const Vec2& XY, PatchHint& hint) const {
141 const Array_<int> dx(1,0), dy(1,1);
142 const Real fx = calcDerivative(dx,XY,hint);
143 const Real fy = calcDerivative(dy,XY,hint);
144 return UnitVec3(-fx, -fy, 1); // (1,0,fx) X (0,1,fy)
145 }
calcUnitNormal(const Vec2 & XY) const146 UnitVec3 BicubicSurface::calcUnitNormal(const Vec2& XY) const {
147 PatchHint hint;
148 return calcUnitNormal(XY,hint);
149 }
150
calcParaboloid(const Vec2 & XY,PatchHint & hint,Transform & X_SP,Vec2 & k) const151 void BicubicSurface::calcParaboloid
152 (const Vec2& XY, PatchHint& hint, Transform& X_SP, Vec2& k) const
153 {
154 SimTK_ERRCHK_ALWAYS(!isEmpty(), "BicubicSurface::calcParaboloid()",
155 "This method can't be called on an empty handle.");
156 guts->calcParaboloid(XY, hint, X_SP, k);
157 }
158
calcParaboloid(const Vec2 & XY,Transform & X_SP,Vec2 & k) const159 void BicubicSurface::calcParaboloid
160 (const Vec2& XY, Transform& X_SP, Vec2& k) const
161 {
162 PatchHint hint;
163 calcParaboloid(XY, hint, X_SP, k);
164 }
165
getNumPatches(int & nx,int & ny) const166 void BicubicSurface::getNumPatches(int& nx, int& ny) const
167 { guts->getNumPatches(nx,ny); }
168
169 Geo::BicubicHermitePatch BicubicSurface::
calcHermitePatch(int x,int y) const170 calcHermitePatch(int x, int y) const
171 { PatchHint hint; return guts->calcHermitePatch(x,y, hint); }
172
173 Geo::BicubicBezierPatch BicubicSurface::
calcBezierPatch(int x,int y) const174 calcBezierPatch(int x, int y) const
175 { PatchHint hint; return guts->calcBezierPatch(x,y, hint); }
176
isSurfaceDefined(const Vec2 & XY) const177 bool BicubicSurface::isSurfaceDefined(const Vec2& XY) const
178 { return getGuts().isSurfaceDefined(XY); }
179
getMinXY() const180 Vec2 BicubicSurface::getMinXY() const {
181 const BicubicSurface::Guts& guts = getGuts();
182 return Vec2(guts._x[0],guts._y[0]);
183 }
184
getMaxXY() const185 Vec2 BicubicSurface::getMaxXY() const {
186 const BicubicSurface::Guts& guts = getGuts();
187 return Vec2(guts._x[guts._x.size()-1],guts._y[guts._y.size()-1]);
188 }
189
createPolygonalMesh(Real resolution) const190 PolygonalMesh BicubicSurface::createPolygonalMesh(Real resolution) const {
191 PolygonalMesh mesh;
192 getGuts().createPolygonalMesh(resolution, mesh);
193 return mesh;
194 }
195
getNumAccesses() const196 int BicubicSurface::getNumAccesses() const
197 { return getGuts().numAccesses; }
198
getNumAccessesSamePoint() const199 int BicubicSurface::getNumAccessesSamePoint() const
200 { return getGuts().numAccessesSamePoint; }
201
getNumAccessesSamePatch() const202 int BicubicSurface::getNumAccessesSamePatch() const
203 { return getGuts().numAccessesSamePatch; }
204
getNumAccessesNearbyPatch() const205 int BicubicSurface::getNumAccessesNearbyPatch() const
206 { return getGuts().numAccessesNearbyPatch; }
207
resetStatistics() const208 void BicubicSurface::resetStatistics() const
209 { return getGuts().resetStatistics(); }
210
211
212
213 //==============================================================================
214 // BICUBIC SURFACE :: GUTS
215 //==============================================================================
216
217 // This is the constructor for irregularly-spaced samples.
Guts(const Vector & aX,const Vector & aY,const Matrix & af,Real smoothness)218 BicubicSurface::Guts::Guts(const Vector& aX, const Vector& aY,
219 const Matrix& af, Real smoothness)
220 {
221 construct();
222
223 _x.resize(aX.size()); _y.resize(aY.size());
224 _x = aX; _y = aY;
225
226 _hasRegularSpacing = false;
227
228 constructFromSplines(af, smoothness);
229 }
230
231
232 // This is the constructor for regularly spaced samples.
Guts(const Vec2 & XY,const Vec2 & spacing,const Matrix & af,Real smoothness)233 BicubicSurface::Guts::Guts
234 (const Vec2& XY, const Vec2& spacing, const Matrix& af, Real smoothness)
235 {
236 construct();
237
238 // Check for reasonable spacing.
239 SimTK_ERRCHK2_ALWAYS(spacing > 0,
240 "BicubicSurface::BicubicSurface(XY,spacing,af,smoothness)",
241 "A BicubicSurface requires positive spacing in both x and y"
242 " but spacing was %g and %g.", spacing[0], spacing[1]);
243
244 const int nx = af.nrow(), ny = af.ncol();
245 _x.resize(nx); _y.resize(ny);
246
247 for (int i=0; i < nx; ++i)
248 _x[i] = XY[0] + i*spacing[0];
249 for (int j=0; j < ny; ++j)
250 _y[j] = XY[1] + j*spacing[1];
251
252 _hasRegularSpacing = true;
253 _spacing = spacing;
254
255 constructFromSplines(af, smoothness);
256 }
257
258 // This implementation is shared by the regular and irregular constructors.
259 // We expect _x and _y already to have been filled in with the grid sample
260 // locations (either as supplied or as generated from regular spacing).
261 void BicubicSurface::Guts::
constructFromSplines(const Matrix & af,Real smoothness)262 constructFromSplines(const Matrix& af, Real smoothness)
263 {
264 const int nx = af.nrow(), ny = af.ncol();
265
266 // Check for sufficient sample size.
267 SimTK_ERRCHK2_ALWAYS(af.nrow() >= 4 && af.ncol() >= 4,
268 "BicubicSurface::BicubicSurface()",
269 "A BicubicSurface requires at least 4 sample in x and y directions"
270 " but grid dimensions were %d and %d.", nx, ny);
271
272 SimTK_ERRCHK4_ALWAYS(_x.size() == nx && _y.size() == ny,
273 "BicubicSurface::BicubicSurface()",
274 "Number of samples must match the grid dimension (%d X %d) but"
275 "the number of supplied sample points was %d X %d.",
276 nx, ny, _x.size(), _y.size());
277
278 // We're checking this even for generated regularly-spaced samples
279 // to catch the rare case that the spacing was so small that it
280 // produced two identical sample locations.
281
282 SimTK_ERRCHK_ALWAYS( isMonotonicallyIncreasing(_x)
283 && isMonotonicallyIncreasing(_y),
284 "BicubicSurface::BicubicSurface()",
285 "Sample vectors must each be monotonically increasing.");
286
287 // The grid data stores a Vec4 at each grid point with (possibly smoothed)
288 // function value f, and derivatives fx, fy, fxy in that order.
289 _ff.resize(nx, ny);
290
291 _debug = false;
292
293 // These temporaries are needed for indexing into the Spline functions.
294 Vector coord(1);
295 const Array_<int> deriv1(1,0); // just one zero to pick 1st derivative
296
297 // This temporary will hold either the original function values or the
298 // smoothed ones.
299 Matrix fSmooth(nx,ny);
300
301 // Smoothing strategy: we want something that is symmetric in x and y
302 // so that you will get the same surface if you rotate the grid 90 degrees
303 // to exchange the meaning of x and y. To accomplish that, we smooth
304 // the grid separately along the rows and columns, and then average the
305 // results to produce a new grid to which we fit the surface.
306
307 if (smoothness > 0) {
308 // This temp holds the function values as they look after smoothing
309 // in the x direction (that is, down the columns of constant y).
310 Matrix xf(nx,ny);
311
312 // Smooth position data along lines of constant y.
313 for(int j=0; j < ny; ++j){
314 Spline_<Real> xspline = SplineFitter<Real>::fitForSmoothingParameter
315 (3,_x,af(j),smoothness).getSpline();
316 for(int i=0; i < nx; ++i){
317 coord[0] = _x[i];
318 xf(i,j) = xspline.calcValue(coord);
319 }
320 }
321
322 // Smooth position data along lines of constant x, then average the
323 // result with the corresponding value from smoothing the other way.
324 for(int i=0; i < nx; ++i){
325 Spline_<Real> yspline = SplineFitter<Real>::fitForSmoothingParameter
326 (3,_y,~af[i],smoothness).getSpline();
327 for(int j=0; j < ny; ++j){
328 coord[0] = _y[j];
329 const Real yfij = yspline.calcValue(coord);
330 fSmooth(i,j) = (xf(i,j) + yfij) / 2; // average xf,xy
331 }
332 }
333 } else {
334 // Not smoothing.
335 fSmooth = af;
336 }
337
338 // Now fill in the f and fx entries in our internal grid by exactly
339 // fitting a spline to the already-smoothed data.
340 for(int j=0; j < ny; ++j){
341 Spline_<Real> xspline = SplineFitter<Real>::fitForSmoothingParameter
342 (3,_x,fSmooth(j),0).getSpline();
343 for(int i=0; i < nx; ++i){
344 coord[0] = _x[i];
345 Vec4& fij = _ff(i,j);
346 fij[F] = fSmooth(i,j);
347 fij[Fx] = xspline.calcDerivative(deriv1,coord);
348 }
349 }
350
351 // Compute fy and fxy by fitting splines along the rows.
352 // Note that we are using the already-smoothed value of f here.
353 Vector tmpRow(ny);
354 for(int i=0; i < nx; ++i){
355 // Fit splines along rows of constant x to go exactly through the
356 // already-smoothed sample points in order to get fy=Df/Dy.
357 Spline_<Real> yspline = SplineFitter<Real>::fitForSmoothingParameter
358 (3,_y,~fSmooth[i],0).getSpline();
359
360 // Fit splines along rows of constant x to interpolate fx in the y
361 // direction to give fxy=Dfx/Dy.
362 for (int j=0; j<_y.size(); ++j) tmpRow[j] = _ff(i,j)[Fx];
363 Spline_<Real> ydxspline = SplineFitter<Real>::fitForSmoothingParameter
364 (3,_y,tmpRow,0).getSpline();
365
366 for(int j=0; j < ny; ++j){
367 coord[0] = _y[j];
368 Vec4& fij = _ff(i,j);
369 fij[Fy] = yspline.calcDerivative(deriv1,coord);
370 fij[Fxy] = ydxspline.calcDerivative(deriv1,coord);
371 }
372 }
373 }
374
375 // This is the advanced constructor where everything is known already.
Guts(const Vector & aX,const Vector & aY,const Matrix & af,const Matrix & afx,const Matrix & afy,const Matrix & afxy)376 BicubicSurface::Guts::Guts
377 (const Vector& aX, const Vector& aY, const Matrix& af,
378 const Matrix& afx, const Matrix& afy, const Matrix& afxy)
379 {
380 construct();
381
382 _x.resize(aX.size()); _y.resize(aY.size());
383 _x = aX; _y = aY;
384
385 _hasRegularSpacing = false;
386
387 constructFromKnownFunction(af, afx, afy, afxy);
388 }
389
Guts(const Vec2 & XY,const Vec2 & spacing,const Matrix & af,const Matrix & afx,const Matrix & afy,const Matrix & afxy)390 BicubicSurface::Guts::Guts
391 (const Vec2& XY, const Vec2& spacing, const Matrix& af,
392 const Matrix& afx, const Matrix& afy, const Matrix& afxy)
393 {
394 construct();
395
396 // Check for reasonable spacing.
397 SimTK_ERRCHK2_ALWAYS(spacing > 0,
398 "BicubicSurface::BicubicSurface(XY,spacing,af,smoothness)",
399 "A BicubicSurface requires positive spacing in both x and y"
400 " but spacing was %g and %g.", spacing[0], spacing[1]);
401
402 const int nx = af.nrow(), ny = af.ncol();
403 _x.resize(nx); _y.resize(ny);
404
405 for (int i=0; i < nx; ++i)
406 _x[i] = XY[0] + i*spacing[0];
407 for (int j=0; j < ny; ++j)
408 _y[j] = XY[1] + j*spacing[1];
409
410 _hasRegularSpacing = true;
411 _spacing = spacing;
412
413 constructFromKnownFunction(af, afx, afy, afxy);
414 }
415
416 // Expects _x and _y already to be filled in.
417 void BicubicSurface::Guts::
constructFromKnownFunction(const Matrix & af,const Matrix & afx,const Matrix & afy,const Matrix & afxy)418 constructFromKnownFunction
419 (const Matrix& af, const Matrix& afx, const Matrix& afy,
420 const Matrix& afxy)
421 {
422 const int nx = af.nrow(), ny = af.ncol();
423
424 // Check for sufficient sample size.
425 SimTK_ERRCHK2_ALWAYS(nx >= 2 && ny >= 2,
426 "BicubicSurface::BicubicSurface(f,fx,fy,fxy)",
427 "A BicubicSurface requires at least 2 sample in x and y directions"
428 " but grid dimensions were %d and %d.", nx, ny);
429
430 SimTK_ERRCHK4_ALWAYS(_x.size() == nx && _y.size() == ny,
431 "BicubicSurface::BicubicSurface(f,fx,fy,fxy)",
432 "Number of samples must match the grid dimension (%d X %d) but"
433 "the number of supplied sample points was %d X %d.",
434 nx, ny, _x.size(), _y.size());
435
436 SimTK_ERRCHK2_ALWAYS( afx.nrow() == nx && afx.ncol() == ny
437 && afy.nrow() == nx && afy.ncol() == ny
438 && afxy.nrow() == nx && afxy.ncol() == ny,
439 "BicubicSurface::BicubicSurface(f,fx,fy,fxy)",
440 "All the derivative sample matrices must match the grid dimension"
441 " (%d X %d).", nx, ny);
442
443 SimTK_ERRCHK_ALWAYS( isMonotonicallyIncreasing(_x)
444 && isMonotonicallyIncreasing(_y),
445 "BicubicSurface::BicubicSurface(f,fx,fy,fxy)",
446 "Sample vectors must each be monotonically increasing.");
447
448 _ff.resize(nx,ny);
449
450 _debug = false;
451
452 //Copy the data into our packed data structure.
453 for(int i=0; i < nx; ++i) {
454 for(int j=0; j < ny; ++j){
455 Vec4& fij = _ff(i,j);
456 fij[F] = af(i,j);
457 fij[Fx] = afx(i,j);
458 fij[Fy] = afy(i,j);
459 fij[Fxy] = afxy(i,j);
460 }
461 }
462 }
463
464 //_____________________________________________________________________________
465
calcValue(const Vec2 & aXY,PatchHint & hint) const466 Real BicubicSurface::Guts::calcValue(const Vec2& aXY, PatchHint& hint) const
467 {
468 getFdF(aXY,0,hint); // just function value
469 const PatchHint::Guts& h = hint.getGuts();
470 assert(h.xy == aXY && h.level >= 0);
471 return h.f;
472 }
473
474
calcDerivative(const Array_<int> & aDerivComponents,const Vec2 & aXY,PatchHint & hint) const475 Real BicubicSurface::Guts::calcDerivative
476 (const Array_<int>& aDerivComponents, const Vec2& aXY, PatchHint& hint) const
477 {
478 const int wantLevel = (int)aDerivComponents.size();
479
480 if (wantLevel == 0)
481 return calcValue(aXY, hint); // "0th" deriv is the function value
482
483 for (int i=0; i < wantLevel; ++i) {
484 SimTK_ERRCHK2_ALWAYS(aDerivComponents[i]==0 || aDerivComponents[i]==1,
485 "BicubicSurface::calcDerivative()",
486 "Component %d was %d but must be 0 or 1 for x or y.",
487 i, aDerivComponents[i]);
488 }
489
490 if (wantLevel > 3)
491 return 0; // 4th and higher derivatives are all zero
492
493 getFdF(aXY, wantLevel, hint);
494 const PatchHint::Guts& h = hint.getGuts();
495 assert(h.xy == aXY && h.level >= wantLevel);
496
497 if (aDerivComponents.size() == 1)
498 return aDerivComponents[0]==0 ? h.fx : h.fy; // fx : fy
499
500 if (aDerivComponents.size() == 2) {
501 if (aDerivComponents[0]==0) //x
502 return aDerivComponents[1]==0 ? h.fxx : h.fxy; // fxx:fxy
503 else //y (fyx==fxy)
504 return aDerivComponents[1]==0 ? h.fxy : h.fyy; // fyx:fyy
505 }
506
507 // Third derivative.
508 if (aDerivComponents[0]==0) { //x
509 if (aDerivComponents[1]==0) // xx
510 return aDerivComponents[2]==0 ? h.fxxx : h.fxxy; // fxxx:fxxy
511 else // xy (fxyx==fxxy)
512 return aDerivComponents[2]==0 ? h.fxxy : h.fxyy; // fxyx:fxyy
513 } else { //y (fyx==fxy)
514 if (aDerivComponents[1]==0) // yx (fyxx==fxxy, fyxy==fxyy)
515 return aDerivComponents[2]==0 ? h.fxxy : h.fxyy; // fyxx:fyxy
516 else // yy (fyyx==fxyy)
517 return aDerivComponents[2]==0 ? h.fxyy : h.fyyy; // fyyx:fyyy
518 }
519 }
520
521 // Cost is patch evaluation + about 200 flops.
calcParaboloid(const Vec2 & aXY,PatchHint & hint,Transform & X_SP,Vec2 & k) const522 void BicubicSurface::Guts::calcParaboloid
523 (const Vec2& aXY, PatchHint& hint, Transform& X_SP, Vec2& k) const
524 {
525 getFdF(aXY, 2, hint); // calculate through 2nd derivatives
526 const PatchHint::Guts& h = hint.getGuts();
527 assert(h.xy == aXY && h.level >= 2);
528
529 const Vec3 P = aXY.append1(h.f);
530 const Vec3 dPdx(1,0,h.fx);
531 const Vec3 dPdy(0,1,h.fy);
532 const UnitVec3 nn(-h.fx, -h.fy, 1); // dPdx X dPdy (normalizing, ~35 flops)
533 const Vec3 d2Pdx2(0,0,h.fxx);
534 const Vec3 d2Pdy2(0,0,h.fyy);
535 const Vec3 d2Pdxdy(0,0,h.fxy);
536
537 // TODO: could save a little time here by taking advantage of the known
538 // sparsity of these vectors (probably not worth the trouble).
539 k = ContactGeometry::evalParametricCurvature
540 (P,nn,dPdx,dPdy,d2Pdx2,d2Pdy2,d2Pdxdy,X_SP);
541 }
542
isSurfaceDefined(const Vec2 & XYval) const543 bool BicubicSurface::Guts::isSurfaceDefined(const Vec2& XYval) const
544 {
545 const bool valueDefined =
546 (_x[0] <= XYval[0] && XYval[0] <= _x[_x.size()-1])
547 && (_y[0] <= XYval[1] && XYval[1] <= _y[_y.size()-1]);
548
549 return valueDefined;
550 }
551
552 /* This function ensures that the given hint contains correct information for
553 the patch indexed (x0,y0). */
554 void BicubicSurface::Guts::
getPatchInfoIfNeeded(int x0,int y0,BicubicSurface::PatchHint::Guts & h) const555 getPatchInfoIfNeeded(int x0, int y0, BicubicSurface::PatchHint::Guts& h) const {
556 const int x1 = x0+1, y1 = y0+1;
557
558 // Compute Bicubic coefficients only if we're in a new patch
559 // else use the old ones, because this is an expensive step!
560 if( !(h.x0 == x0 && h.y0 == y0) ) {
561 // The hint is no good at all since it is for the wrong patch.
562 h.clear();
563 h.x0 = x0; h.y0 = y0;
564
565 // Compute the scaling of the new patch. Note that neither patch
566 // dimension can be zero since we don't allow duplicates in x or y.
567 h.xS = _x(x1)-_x(x0);
568 h.yS = _y(y1)-_y(y0);
569 h.ooxS = 1/h.xS; h.ooxS2 = h.ooxS*h.ooxS; h.ooxS3=h.ooxS*h.ooxS2;
570 h.ooyS = 1/h.yS; h.ooyS2 = h.ooyS*h.ooyS; h.ooyS3=h.ooyS*h.ooyS2;
571
572 // Form the vector f and multiply Ainv*f to form coefficient vector a.
573
574 const Vec4& f00 = _ff(x0,y0);
575 const Vec4& f01 = _ff(x0,y1);
576 const Vec4& f10 = _ff(x1,y0);
577 const Vec4& f11 = _ff(x1,y1);
578
579 h.fV[0] = f00[F];
580 h.fV[1] = f10[F];
581 h.fV[2] = f01[F];
582 h.fV[3] = f11[F];
583
584 // Can't precalculate these scaled values because the same grid point
585 // is used for up to four different patches, each scaled differently.
586 h.fV[4] = f00[Fx]*h.xS;
587 h.fV[5] = f10[Fx]*h.xS;
588 h.fV[6] = f01[Fx]*h.xS;
589 h.fV[7] = f11[Fx]*h.xS;
590
591 h.fV[8] = f00[Fy]*h.yS;
592 h.fV[9] = f10[Fy]*h.yS;
593 h.fV[10] = f01[Fy]*h.yS;
594 h.fV[11] = f11[Fy]*h.yS;
595
596 h.fV[12] = f00[Fxy]*h.xS*h.yS;
597 h.fV[13] = f10[Fxy]*h.xS*h.yS;
598 h.fV[14] = f01[Fxy]*h.xS*h.yS;
599 h.fV[15] = f11[Fxy]*h.xS*h.yS;
600
601 getCoefficients(h.fV,h.a);
602 }
603 }
604
605
606 /**
607 This function computes the surface value and all derivatives (because it is
608 cheap to compute these derivatives once the bicubic interpolation
609 coefficients have been solved for). These values are stored in mutable
610 function members so that if a user repeatedly asks for information about
611 the same location the stored data is supplied. Additionally, if the user
612 is staying within a single patch, the the coefficients required for this
613 patch are only computed once, and they are re-used until a location outside
614 the patch is requested.
615
616 @param aXY the X,Y location of interest
617 @param fV an empty vector that is set to the values of f,fx,fy and fxy of
618 the patch corners
619 f(0,0) f(1,0) f(0,1) f(1,1),
620 fx(0,0) fx(1,0) fx(0,1) fx(1,1),
621 fy(0,0) fy(1,0) fy(0,1) fy(1,1),
622 fxy(0,0) fxy(1,0) fxy(0,1) fxy(1,1)
623 @param aijV an empty vector that is set to the coefficient values of the
624 bicubic surface polynomials for the patch that the current
625 XY location resides in. The coefficients are returned in this
626 order
627
628 a00,a10,a20,a30,a01,a11,a21,a31,a02,a12,a22,a32,a03,a13,a23,a33
629
630 @param aFdF an empty vector that is set to the 10 values of f and its partial
631 derivatives at the point XY. These values are stored in the following order:
632 f(x,y) fx(x,y) fy(x,y) fxy(x,y) fxx(x,y) fyy(x,y)
633 fxxx(x,y) fxxy(x,y) fyyy(x,y) fxyy(x,y)
634 */
635 void BicubicSurface::Guts::
getFdF(const Vec2 & aXY,int wantLevel,PatchHint & hint) const636 getFdF(const Vec2& aXY, int wantLevel, PatchHint& hint) const {
637 ++numAccesses; // All surface accesses come through here.
638
639 //0. Check if the surface is defined for the XY value given.
640 SimTK_ERRCHK6_ALWAYS(isSurfaceDefined(aXY),
641 "BicubicSurface::getFdF (private fcn)",
642 "BicubicSurface is not defined at requested location (%g,%g)."
643 " The surface is valid from x[%g %g], y[%g %g].", aXY(0), aXY(1),
644 _x[0], _x[_x.size()-1], _y[0], _y[_y.size()-1]);
645
646 // -1 means just do the patch
647 // 0 means patch and function value
648 // 1 means add 1st deriv, 2 is 2nd, 3 is 3rd
649 assert(-1 <= wantLevel && wantLevel <= 3);
650
651 BicubicSurface::PatchHint::Guts& h = hint.updGuts();
652
653 //1. Check to see if we have already computed values for the requested point.
654 if(h.level >= wantLevel && aXY == h.xy){
655 ++numAccessesSamePoint;
656 return;
657 }
658
659 // Nope. We're at least changing points.
660 h.xy = aXY;
661 h.level = -1; // we don't know anything about this point
662
663 // We're going to feed calcLowerBoundIndex() our best guess as to the
664 // patch this point is on. For regularly-spaced grid points we can find
665 // it exactly, except for some possible roundoff. Otherwise the best we
666 // can do is supply the current index from the hint.
667 int pXidx = h.x0, pYidx = h.y0;
668 if (_hasRegularSpacing) {
669 pXidx = clamp(0, (int)std::floor((h.xy[0]-_x[0])/_spacing[0]),
670 _x.size()-2); // can't be last index
671 pYidx = clamp(0, (int)std::floor((h.xy[1]-_y[0])/_spacing[1]),
672 _y.size()-2);
673 }
674
675 // Compute the indices that define the patch containing this value.
676 int howResolvedX, howResolvedY;
677 const int x0 = calcLowerBoundIndex(_x,aXY[0],pXidx,howResolvedX);
678 const int x1 = x0+1;
679 const int y0 = calcLowerBoundIndex(_y,aXY[1],pYidx,howResolvedY);
680 const int y1 = y0+1;
681
682 // 0->same patch, 1->nearby patch, 2->had to search
683 const int howResolved = std::max(howResolvedX, howResolvedY);
684 if (howResolved == 0) ++numAccessesSamePatch;
685 else if (howResolved == 1) ++numAccessesNearbyPatch;
686
687 // Compute Bicubic coefficients only if we're in a new patch
688 // else use the old ones, because this is an expensive step!
689 getPatchInfoIfNeeded(x0,y0,h);
690
691 // At this point we know that the hint contains valid patch information,
692 // but it contains no valid point information.
693
694 if (wantLevel == -1)
695 return; // caller just wanted patch info
696
697 const Vec<16>& a = h.a; // abbreviate for convenience
698
699
700 // Compute where in the patch we are. This has to
701 // be done everytime the location within the patch changes
702 // ... which has happened if this code gets executed.
703
704 //--------------------------------------------------------------------------
705 // Evaluate function value f (38 flops).
706
707 // 8 flops
708 const Real xpt = (aXY(0)-_x(x0))*h.ooxS, xpt2=xpt*xpt, xpt3=xpt*xpt2;
709 const Real ypt = (aXY(1)-_y(y0))*h.ooyS, ypt2=ypt*ypt, ypt3=ypt*ypt2;
710 // 12 flops
711 const Mat44 mx(a[ 0], a[ 1]*xpt, a[ 2]*xpt2, a[ 3]*xpt3,
712 a[ 4], a[ 5]*xpt, a[ 6]*xpt2, a[ 7]*xpt3,
713 a[ 8], a[ 9]*xpt, a[10]*xpt2, a[11]*xpt3,
714 a[12], a[13]*xpt, a[14]*xpt2, a[15]*xpt3);
715 // 12 flops
716 const Vec4 xsum = mx.rowSum();
717 // 6 flops
718 h.f = xsum[0] + ypt*xsum[1] + ypt2*xsum[2] + ypt3*xsum[3];
719 h.level = 0; // function value is ready
720 if (wantLevel == 0)
721 return;
722
723 //--------------------------------------------------------------------------
724 // Evaluate first derivatives fx, fy (43 flops).
725
726 // fy is 9 flops
727 const Real dypt = h.ooyS, dypt2= 2*ypt*h.ooyS, dypt3= 3*ypt2*h.ooyS;
728 h.fy = dypt*xsum[1] + dypt2*xsum[2] + dypt3*xsum[3];
729
730 // fx is 34 flops
731 const Real dxpt=h.ooxS, dxpt2=2*xpt*h.ooxS, dxpt3=3*xpt2*h.ooxS;
732 const Mat43 mdx(a[ 1]*dxpt, a[ 2]*dxpt2, a[ 3]*dxpt3,
733 a[ 5]*dxpt, a[ 6]*dxpt2, a[ 7]*dxpt3,
734 a[ 9]*dxpt, a[10]*dxpt2, a[11]*dxpt3,
735 a[13]*dxpt, a[14]*dxpt2, a[15]*dxpt3);
736 const Vec4 dxsum = mdx.rowSum();
737 h.fx = dxsum[0] + ypt*dxsum[1] + ypt2*dxsum[2] + ypt3*dxsum[3];
738 h.level = 1; // first derivatives are ready
739 if (wantLevel == 1)
740 return;
741
742
743 //--------------------------------------------------------------------------
744 // Evaluate second derivatives fxy, fxx, fyy (40 flops).
745
746 // fxy, fyy are 11 flops
747 h.fxy = dypt*dxsum[1] + dypt2*dxsum[2] + dypt3*dxsum[3];
748 const Real dyypt2=2*h.ooyS2, dyypt3=6*ypt*h.ooyS2;
749 h.fyy = dyypt2*xsum[2] + dyypt3*xsum[3];
750
751 // fxx is 29 flops
752 const Real dxxpt2=2*h.ooxS2, dxxpt3=6*xpt*h.ooxS2;
753 const Mat42 mdxx(a[ 2]*dxxpt2, a[ 3]*dxxpt3,
754 a[ 6]*dxxpt2, a[ 7]*dxxpt3,
755 a[10]*dxxpt2, a[11]*dxxpt3,
756 a[14]*dxxpt2, a[15]*dxxpt3);
757 const Vec4 dxxsum = mdxx.rowSum();
758 h.fxx = dxxsum[0] + ypt*dxxsum[1] + ypt2*dxxsum[2] + ypt3*dxxsum[3];
759 h.level = 2; // second derivatives are ready
760 if (wantLevel == 2)
761 return;
762
763 //--------------------------------------------------------------------------
764 // Evaluate third derivatives fxxx, fxxy, fyyy, fxyy (21 flops).
765
766 // 10 flops
767 const Real dyyypt3=6*h.ooyS3;
768 h.fyyy = dyyypt3*xsum[3];
769 h.fxyy = dyypt2*dxsum[2] + dyypt3*dxsum[3];
770 h.fxxy = dypt*dxxsum[1] + dypt2*dxxsum[2] + dypt3*dxxsum[3];
771
772 // 11 flops
773 const Real dxxxpt3=6*h.ooxS3;
774 const Vec4 mdxxx(a[ 3]*dxxxpt3,
775 a[ 7]*dxxxpt3,
776 a[11]*dxxxpt3,
777 a[15]*dxxxpt3);
778 h.fxxx = mdxxx[0] + ypt* mdxxx[1] + ypt2*mdxxx[2] + ypt3*mdxxx[3];
779 h.level = 3; // third derivatives are ready
780
781 if(_debug == true){
782 cout<<" getFdF" << endl;
783 cout <<"Member variables" <<endl;
784 cout << "_x" << _x << endl;
785 cout << "\n"<<endl;
786 cout << "_y" << _y << endl;
787 cout << "\n"<<endl;
788 cout << "_ff" << _ff << endl;
789 cout << "\n"<<endl;
790
791 cout <<" Intermediate variables " << endl;
792 cout <<"XY: " << aXY << endl;
793 printf("[x0 x1], [y0 y1]: [%d %d],[%d %d]\n",x0,x1,y0,y1);
794 printf("[x0V x1V], [y0V y1V]: [%f %f],[%f %f]\n",_x(x0),_x(x1),_y(y0),_y(y1));
795 cout <<" xS " << h.xS << " yS " << h.yS << endl;
796 printf("(xp,yp): (%f %f)\n",xpt,ypt);
797 cout << "\n\n\n"<<endl;
798 }
799 }
800
801
802 /** Given a search value aVal and an n-vector aVec (n>=2) containing
803 monotonically increasing values (no duplicates), this method finds the unique
804 pair of indices (i,i+1) such that either
805 - aVec[i] <= aVal < aVec[i+1], or
806 - aVec[n-2] < aVal == aVec[n-1].
807
808 The following preconditions must be satisfied by the arguments to this
809 method:
810 - aVec.size() >= 2
811 - aVal >= aVec[0]
812 - aVal <= aVec[n-1]
813
814
815 aVec = [0.1 0.2 0.3 0.4 0.5];
816 aVal = 0.125
817 idxLB = calcLowerBoundIndex(aVec,aVal,-1);
818
819 Then idxLB should be 0. Some effort has been put into making this code
820 efficient, as it is expected that very large vectors could be used in this
821 function. If the data is evenly spaced, the index is computed. If not data
822 near a previous index (set by the user) is searched. If the data is still
823 not found a binary search is performed
824
825 @params aVec: A SimTK vector containing monotonically increasing data
826 @params aVal: A value bounded by the numbers in the vector
827 @params pIdx: A hint index provided by the user (of this function)
828 @returns int: The index of the entry in the vector that is the as close to
829 aVal without exceeding it.
830
831 */
832
833 // Return true if aVal is on the patch (really the line) between
834 // aVec[indxL] and aVec[indxL+1]. By "on the patch" we mean that
835 // aVec[indxL] <= aVal < aVec[indxL+1] unless this is the last patch in
836 // which case we allow aVec[indxL] < aVal <= aVec[indxL+1].
isOnPatch(const Vector & aVec,int indxL,Real aVal)837 static bool isOnPatch(const Vector& aVec, int indxL, Real aVal) {
838 assert(aVec.size() >= 2);
839 const int maxLB = aVec.size() - 2;
840 assert(0 <= indxL && indxL <= maxLB);
841
842 const Real low = aVec[indxL], high = aVec[indxL+1];
843
844 if (aVal < low || aVal > high)
845 return false;
846
847 // Here we know low <= aVal <= high.
848 if (aVal < high)
849 return true;
850
851 // Here we know low < aVal == high. This is only allowed on the last patch.
852 return indxL == maxLB;
853 }
854
855 // howResolved: 0->same patch, 1->nearby patch, 2->search
856 int BicubicSurface::Guts::
calcLowerBoundIndex(const Vector & aVec,Real aVal,int pIdx,int & howResolved) const857 calcLowerBoundIndex(const Vector& aVec, Real aVal, int pIdx,
858 int& howResolved) const {
859 assert(aVec.size() >= 2);
860
861 // Because we're trying to find the lower index, it can't be the very
862 // last knot.
863 const int maxLB = aVec.size() - 2;
864
865 // 1. Do a local search around the previous index, if one is given.
866 if(0 <= pIdx && pIdx <= maxLB) {
867 // Are we still on the same patch? Caution -- can't be equal to the
868 // upper knot unless it is the last one.
869 if (isOnPatch(aVec, pIdx, aVal)) {
870 howResolved = 0;
871 return pIdx;
872 }
873
874 // Not on the same patch, how about adjacent patches?
875 if (aVal < aVec[pIdx]) {
876 // Value moved below the current patch.
877 if (pIdx > 0 && isOnPatch(aVec, pIdx-1, aVal)) {
878 howResolved = 1;
879 return pIdx-1; // found it next door!
880 }
881 } else if (aVal >= aVec[pIdx+1]) {
882 // Value moved above the current patch.
883 if (pIdx < maxLB && isOnPatch(aVec, pIdx+1, aVal)) {
884 howResolved = 1;
885 return pIdx+1; // found it next door!
886 }
887 }
888 }
889
890 // Either we didn't have a previous index to try, or it didn't help.
891
892 // 2. Check the end points. We'll count these as "nearby patches" since
893 // they are about the same amount of work.
894 if (aVal <= aVec[0]) {
895 howResolved = 1;
896 return 0;
897 }
898 if (aVal >= aVec[maxLB+1]) {
899 howResolved = 1;
900 return maxLB;
901 }
902
903 // 3. If still not found, use binary search to find the appropriate index.
904
905 // std::upper_bound returns the index of the first element that
906 // is strictly greater than aVal (one after the last element
907 // if aVal is exactly equal to the last knot).
908 const Real* upper =
909 std::upper_bound(&aVec[0], &aVec[0] + aVec.size(), aVal);
910 const int upperIx = clamp(0, (int)(upper-&aVec[1]), maxLB);
911
912 howResolved = 2;
913 return upperIx;
914 }
915
916 /**
917 This function will compute the coefficients aij for a bicubic patch that has
918 the values of f, fx, fy and fxy defined at its four corners. This function is
919 relatively expensive, as it involves multiplying a 16x16 matrix by a 16x1,
920 so it is called only when absolutely necessary.
921
922 @param f Vector f defining the values of f, fx, fy and fxy at the four
923 corners of the patch. Vector f is in this order:
924 f(0,0) f(1,0) f(0,1) f(1,1),
925 fx(0,0) fx(1,0) fx(0,1) fx(1,1),
926 fy(0,0) fy(1,0) fy(0,1) fy(1,1),
927 fxy(0,0) fxy(1,0) fxy(0,1) fxy(1,1)
928 @param aV An empty vector for which the coefficients aij are written into
929 in the following order:
930
931 a00,a10,a20,a30,a01,a11,a21,a31,a02,a12,a22,a32,a03,a13,a23,a33
932 */
933 void BicubicSurface::Guts::
getCoefficients(const Vec<16> & fV,Vec<16> & aV) const934 getCoefficients(const Vec<16>& fV, Vec<16>& aV) const {
935 // This is what the full matrix inverse looks like (copied here from
936 // Wikipedia). It is very sparse and contains only a few unique values
937 // so the matrix-vector product can be done very cheaply if worked out
938 // in painstaking detail (with some help from Maple). The full
939 // matrix-vector product takes 496 flops; we can do it in 80.
940 /*
941 const Real Ainv[] = {
942 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
943 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
944 -3, 3, 0, 0, -2,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
945 2,-2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
946 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
947 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
948 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2,-1, 0, 0,
949 0, 0, 0, 0, 0, 0, 0, 0, 2,-2, 0, 0, 1, 1, 0, 0,
950 -3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0, 0, 0, 0, 0,
951 0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0,-1, 0,
952 9,-9,-9, 9, 6, 3,-6,-3, 6,-6, 3,-3, 4, 2, 2, 1,
953 -6, 6, 6,-6, -3,-3, 3, 3, -4, 4,-2, 2, -2,-2,-1,-1,
954 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,
955 0, 0, 0, 0, 2, 0,-2, 0, 0, 0, 0, 0, 1, 0, 1, 0,
956 -6, 6, 6,-6, -4,-2, 4, 2, -3, 3,-3, 3, -2,-1,-2,-1,
957 4,-4,-4, 4, 2, 2,-2,-2, 2,-2, 2,-2, 1, 1, 1, 1
958 };
959 Mat<16,16> AinvM(Ainv);
960 aV = AinvM*fV; // So cool that I can do this in C++! Go Sherm!
961 */
962
963
964 // Matt's masterful Maple work, plus a little manual hacking by Sherm
965 // produced this version:
966
967 // Caution: note index change.
968 Real f1=fV[0], f2=fV[1], f3=fV[2], f4=fV[3], f5=fV[4], f6=fV[5],
969 f7=fV[6], f8=fV[7], f9=fV[8], f10=fV[9], f11=fV[10], f12=fV[11],
970 f13=fV[12], f14=fV[13], f15=fV[14], f16=fV[15];
971
972 // 54 add/subtract
973 // 26 multiplies
974 Real f86 = f8-f6, f1211=f12-f11, f109=f10-f9, f75=f7-f5;
975 Real f1234 = f1-f2-f3+f4;
976 Real t312 = 2*f86;
977 Real t311 = 2*f1211;
978 Real t310 = 3*f86 - 2*f14;
979 Real t309 = 3*f1211 - 2*f15;
980 Real t10 = 2*f9;
981 Real t308 = f14 - 2*f10 + t10;
982 Real t307 = 3*f109 - f14;
983 Real t306 = 3*f75 - f15;
984 Real t15 = 2*f5;
985 Real t305 = t15 - 2*f7 + f13 + f15;
986 Real t289 = -2*f13;
987 Real t304 = t289 - 6*f1234 - f16;
988 Real t302 = -3*f1;
989 Real t301 = 2*f1;
990 aV(0) = f1;
991 aV(1) = f5;
992 aV(2) = t302 + 3*f2 - t15 - f6;
993 aV(3) = t301 - 2*f2 + f5 + f6;
994 aV(4) = f9;
995 aV(5) = f13;
996 aV(6) = t289 + t307;
997 aV(7) = f13 + t308;
998 aV(8) = t302 + 3*f3 - t10 - f11;
999 aV(9) = t289 + t306;
1000 aV(10) = 9*f1234 + 6*(f5-f7+f9-f10) + 4*f13 + f16 - t309 - t310;
1001 aV(11) = 4*f109 + t304 + t306 + t310 + t311;
1002 aV(12) = t301 - 2*f3 + f9 + f11;
1003 aV(13) = t305;
1004 aV(14) = 4*f75 + t304 + t307 + t309 + t312;
1005 aV(15) = 4*f1234 + f16 + t305 + t308 - t311 - t312;
1006
1007 if(_debug == true){
1008 cout << "getCoefficients" << endl;
1009 cout << "\tfV" << fV << endl;
1010 cout << "\taijV" << aV << endl;
1011 }
1012 }
1013
1014 // We're going to generate quads in strips like this:
1015 // * <- * <- * <- *
1016 // v ^ v ^ v ^
1017 // * -> * -> * -> *
1018 // Each patch will generate the same number of quads so there will be more
1019 // quads where the surface is denser.
1020 void BicubicSurface::Guts::
createPolygonalMesh(Real resolution,PolygonalMesh & mesh) const1021 createPolygonalMesh(Real resolution, PolygonalMesh& mesh) const {
1022 PatchHint hint;
1023 // Number of patches in x and y direction.
1024 const int nxpatch = _x.size()-1;
1025 const int nypatch = _y.size()-1;
1026 // n is the number of subdivisions per patch
1027 const int n = std::max(1 + (int)(resolution+.5), 1); // round
1028 const int nx = nxpatch*n + 1; // number of vertices along each row
1029 const int ny = nypatch*n + 1;
1030 // These will alternately serve as previous and current.
1031 Array_<int> row1(ny), row2(ny);
1032 Array_<int>* prevRowVerts = &row1;
1033 Array_<int>* curRowVerts = &row2;
1034 Vector xVals(nx), yVals(ny);
1035
1036 // Calculate the x and y sample values.
1037 int nxt = 0;
1038 for (int px=0; px < nxpatch; ++px) {
1039 const Real xlo = _x[px], xhi = _x[px+1];
1040 const Real xs = xhi - xlo, width = xs/n;
1041 // For each quad within patch px
1042 for (int qx=0; qx < n; ++qx)
1043 xVals[nxt++] = xlo + qx*width;
1044 }
1045 xVals[nxt++] = _x[_x.size()-1];
1046 assert(nxt == nx);
1047
1048 nxt = 0;
1049 for (int py=0; py < nypatch; ++py) {
1050 const Real ylo = _y[py], yhi = _y[py+1];
1051 const Real ys = yhi - ylo, width = ys/n;
1052 // For each quad within patch py
1053 for (int qx=0; qx < n; ++qx)
1054 yVals[nxt++] = ylo + qx*width;
1055 }
1056 yVals[nxt++] = _y[_y.size()-1];
1057 assert(nxt == ny);
1058
1059 // Fill in the zeroth row.
1060 Vec2 pt; pt[0] = xVals[0];
1061 for (int j=0; j < ny; ++j) {
1062 pt[1] = yVals[j];
1063 const Real z = calcValue(pt, hint);
1064 (*prevRowVerts)[j] = mesh.addVertex(Vec3(pt[0],pt[1],z));
1065 }
1066
1067 // For each remaining row, generate vertices and then a strip of faces.
1068 Array_<int> face(4);
1069 for (int i=1; i < nx; ++i) {
1070 Vec2 pt; pt[0] = xVals[i];
1071 for (int j=0; j < ny; ++j) {
1072 pt[1] = yVals[j];
1073 const Real z = calcValue(pt, hint);
1074 (*curRowVerts)[j] = mesh.addVertex(Vec3(pt[0],pt[1],z));
1075 }
1076 for (int j=1; j < ny; ++j) {
1077 face[0] = (*curRowVerts)[j-1]; // counterclockwise
1078 face[1] = (*curRowVerts)[j];
1079 face[2] = (*prevRowVerts)[j];
1080 face[3] = (*prevRowVerts)[j-1];
1081 mesh.addFace(face);
1082 }
1083 std::swap(prevRowVerts, curRowVerts);
1084 }
1085 }
1086
1087 //==============================================================================
1088 // BICUBIC SURFACE :: PATCH HINT
1089 //==============================================================================
1090
1091 // Here we ensure that there is always a valid PatchHint::Guts object present
1092 // and forward all operations to it.
1093
PatchHint()1094 BicubicSurface::PatchHint::PatchHint() : guts(new PatchHint::Guts) {}
~PatchHint()1095 BicubicSurface::PatchHint::~PatchHint() {delete guts;}
1096
PatchHint(const PatchHint & src)1097 BicubicSurface::PatchHint::PatchHint(const PatchHint& src)
1098 : guts(new PatchHint::Guts(*src.guts)) {}
1099
1100 BicubicSurface::PatchHint&
operator =(const PatchHint & src)1101 BicubicSurface::PatchHint::operator=(const PatchHint& src)
1102 { *guts = *src.guts; return *this; }
1103
isEmpty() const1104 bool BicubicSurface::PatchHint::isEmpty() const {return guts->isEmpty();}
clear()1105 void BicubicSurface::PatchHint::clear() {guts->clear();}
1106
1107
1108
1109
1110