Panda3D
cubicCurveseg.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file cubicCurveseg.cxx
10  * @author drose
11  * @date 2001-03-04
12  */
13 
14 #include "piecewiseCurve.h"
15 
16 #include "config_parametrics.h"
17 #include "hermiteCurve.h"
18 
19 #include "datagram.h"
20 #include "datagramIterator.h"
21 #include "bamWriter.h"
22 #include "bamReader.h"
23 
24 TypeHandle CubicCurveseg::_type_handle;
25 
26 /**
27  *
28  */
29 CubicCurveseg::
30 CubicCurveseg() {
31 }
32 
33 /**
34  * Creates the curveseg given the four basis vectors (the columns of the
35  * matrix) explicitly.
36  */
37 CubicCurveseg::
38 CubicCurveseg(const LMatrix4 &basis) {
39  Bx = basis.get_col(0);
40  By = basis.get_col(1);
41  Bz = basis.get_col(2);
42  Bw = basis.get_col(3);
43  rational = true;
44 }
45 
46 
47 /**
48  * Creates the curveseg as a Bezier segment.
49  */
50 CubicCurveseg::
51 CubicCurveseg(const BezierSeg &seg) {
52  bezier_basis(seg);
53 }
54 
55 
56 /**
57  * Creates the curveseg as a NURBS segment. See nurbs_basis for a description
58  * of the parameters.
59  */
60 CubicCurveseg::
61 CubicCurveseg(int order, const PN_stdfloat knots[], const LVecBase4 cvs[]) {
62  nurbs_basis(order, knots, cvs);
63 }
64 
65 /**
66  *
67  */
68 CubicCurveseg::
69 ~CubicCurveseg() {
70 }
71 
72 
73 
74 /**
75  * Computes the surface point at a given parametric point t.
76  */
77 bool CubicCurveseg::
78 get_point(PN_stdfloat t, LVecBase3 &point) const {
79  PN_stdfloat t_sqrd = t*t;
80  evaluate_point(LVecBase4(t*t_sqrd, t_sqrd, t, 1.0f), point);
81  return true;
82 }
83 
84 /**
85  * Computes the surface tangent at a given parametric point t.
86  */
87 bool CubicCurveseg::
88 get_tangent(PN_stdfloat t, LVecBase3 &tangent) const {
89  evaluate_vector(LVecBase4(3.0f*t*t, 2.0f*t, 1.0f, 0.0f), tangent);
90  return true;
91 }
92 
93 /**
94  * Simultaneously computes the point and the tangent at the given parametric
95  * point.
96  */
97 bool CubicCurveseg::
98 get_pt(PN_stdfloat t, LVecBase3 &point, LVecBase3 &tangent) const {
99  PN_stdfloat t_sqrd=t*t;
100  evaluate_point(LVecBase4(t*t_sqrd, t_sqrd, t, 1.0f), point);
101  evaluate_vector(LVecBase4(3.0f*t_sqrd, /*2.0f*t*/t+t, 1.0f, 0.0f), tangent);
102  return true;
103 }
104 
105 /**
106  * Computes the surface 2nd-order tangent at a given parametric point t.
107  */
108 bool CubicCurveseg::
109 get_2ndtangent(PN_stdfloat t, LVecBase3 &tangent2) const {
110  evaluate_vector(LVecBase4(6.0f*t, 2.0f, 0.0f, 0.0f), tangent2);
111  return true;
112 }
113 
114 
115 /**
116  * Defines the curve segment as a Hermite. This only sets up the basis
117  * vectors, so the curve will be computed correctly; it does not retain the
118  * CV's.
119  */
120 void CubicCurveseg::
122  const HermiteCurveCV &cv1,
123  PN_stdfloat tlength) {
124  static LMatrix4
125  Mh( 2.0f, -3.0f, 0.0f, 1.0f,
126  -2.0f, 3.0f, 0.0f, 0.0f,
127  1.0f, -2.0f, 1.0f, 0.0f,
128  1.0f, -1.0f, 0.0f, 0.0f);
129 
130  LVecBase4 Gx(cv0._p[0], cv1._p[0],
131  cv0._out[0]*tlength, cv1._in[0]*tlength);
132  LVecBase4 Gy(cv0._p[1], cv1._p[1],
133  cv0._out[1]*tlength, cv1._in[1]*tlength);
134  LVecBase4 Gz(cv0._p[2], cv1._p[2],
135  cv0._out[2]*tlength, cv1._in[2]*tlength);
136 
137  Bx = Gx * Mh;
138  By = Gy * Mh;
139  Bz = Gz * Mh;
140  rational = false;
141 }
142 
143 /**
144  * Defines the curve segment as a Bezier. This only sets up the basis
145  * vectors, so the curve will be computed correctly; it does not retain the
146  * CV's.
147  */
148 void CubicCurveseg::
149 bezier_basis(const BezierSeg &seg) {
150  static LMatrix4
151  Mb(-1.0f, 3.0f, -3.0f, 1.0f,
152  3.0f, -6.0f, 3.0f, 0.0f,
153  -3.0f, 3.0f, 0.0f, 0.0f,
154  1.0f, 0.0f, 0.0f, 0.0f);
155 
156  LVecBase4 Gx(seg._v[0][0], seg._v[1][0], seg._v[2][0], seg._v[3][0]);
157  LVecBase4 Gy(seg._v[0][1], seg._v[1][1], seg._v[2][1], seg._v[3][1]);
158  LVecBase4 Gz(seg._v[0][2], seg._v[1][2], seg._v[2][2], seg._v[3][2]);
159 
160  Bx = Gx * Mb;
161  By = Gy * Mb;
162  Bz = Gz * Mb;
163  rational = false;
164 }
165 
166 static LVecBase4
167 nurbs_blending_function(int order, int i, int j,
168  const PN_stdfloat knots[]) {
169  // This is doubly recursive. Ick.
170  LVecBase4 r;
171 
172  if (j==1) {
173  if (i==order-1 && knots[i] < knots[i+1]) {
174  r.set(0.0f, 0.0f, 0.0f, 1.0f);
175  } else {
176  r.set(0.0f, 0.0f, 0.0f, 0.0f);
177  }
178 
179  } else {
180  LVecBase4 bi0 = nurbs_blending_function(order, i, j-1, knots);
181  LVecBase4 bi1 = nurbs_blending_function(order, i+1, j-1, knots);
182 
183  PN_stdfloat d0 = knots[i+j-1] - knots[i];
184  PN_stdfloat d1 = knots[i+j] - knots[i+1];
185 
186  // First term. Division by zero is defined to equal zero.
187  if (d0 != 0.0f) {
188  if (d1 != 0.0f) {
189  r = bi0 / d0 - bi1 / d1;
190  } else {
191  r = bi0 / d0;
192  }
193 
194  } else if (d1 != 0.0f) {
195  r = - bi1 / d1;
196 
197  } else {
198  r.set(0.0f, 0.0f, 0.0f, 0.0f);
199  }
200 
201  // scale by t.
202  r[0] = r[1];
203  r[1] = r[2];
204  r[2] = r[3];
205  r[3] = 0.0f;
206 
207  // Second term.
208  if (d0 != 0.0f) {
209  if (d1 != 0.0f) {
210  r += bi0 * (- knots[i] / d0) + bi1 * (knots[i+j] / d1);
211  } else {
212  r += bi0 * (- knots[i] / d0);
213  }
214 
215  } else if (d1 != 0.0f) {
216  r += bi1 * (knots[i+j] / d1);
217  }
218  }
219 
220  return r;
221 }
222 
223 void
224 compute_nurbs_basis(int order,
225  const PN_stdfloat knots_in[],
226  LMatrix4 &basis) {
227  int i;
228 
229  // Scale the supplied knots to the range 0..1.
230  PN_stdfloat knots[8];
231  PN_stdfloat mink = knots_in[order-1];
232  PN_stdfloat maxk = knots_in[order];
233 
234  if (mink==maxk) {
235  // Huh. What were you thinking? This is a trivial NURBS.
236  parametrics_cat->warning()
237  << "Trivial NURBS curve specified." << std::endl;
238  memset((void *)&basis, 0, sizeof(LMatrix4));
239  return;
240  }
241 
242  for (i = 0; i<2*order; i++) {
243  knots[i] = (knots_in[i] - mink) / (maxk-mink);
244  }
245 
246 
247  LVecBase4 b[4];
248  for (i = 0; i<order; i++) {
249  b[i] = nurbs_blending_function(order, i, order, knots);
250  }
251 
252  for (i = 0; i<order; i++) {
253  basis.set_row(i, b[i]);
254  }
255 
256  for (i=order; i<4; i++) {
257  basis.set_row(i, LVecBase4::zero());
258  }
259 }
260 
261 
262 
263 /**
264  * Defines the curve segment as a NURBS. Order is one more than the degree,
265  * and must be 1, 2, 3, or 4; knots is an array of order*2 values, and cvs is
266  * an array of order values.
267  */
268 void CubicCurveseg::
269 nurbs_basis(int order, const PN_stdfloat knots[], const LVecBase4 cvs[]) {
270  assert(order>=1 && order<=4);
271 
272  LMatrix4 B;
273  compute_nurbs_basis(order, knots, B);
274 
275  // Create a local copy of our CV's, so we can zero out the unused elements.
276  LVecBase4 c[4];
277  for (int i = 0; i < 4; i++) {
278  c[i] = (i<order) ? cvs[i] : LVecBase4(0.0f, 0.0f, 0.0f, 0.0f);
279  }
280 
281  Bx = LVecBase4(c[0][0], c[1][0], c[2][0], c[3][0]) * B;
282  By = LVecBase4(c[0][1], c[1][1], c[2][1], c[3][1]) * B;
283  Bz = LVecBase4(c[0][2], c[1][2], c[2][2], c[3][2]) * B;
284  Bw = LVecBase4(c[0][3], c[1][3], c[2][3], c[3][3]) * B;
285 
286  rational = true;
287 }
288 
289 /**
290  * Fills the BezierSeg structure with a description of the curve segment as a
291  * Bezier, if possible, but does not change the _t member of the structure.
292  * Returns true if successful, false otherwise.
293  */
294 bool CubicCurveseg::
296  static LMatrix4
297  Mbi(0.0f, 0.0f, 0.0f, 1.0f,
298  0.0f, 0.0f, 1.0f/3.0f, 1.0f,
299  0.0f, 1.0f/3.0f, 2.0f/3.0f, 1.0f,
300  1.0f, 1.0f, 1.0f, 1.0f);
301 
302  LVecBase4 Gx = Bx * Mbi;
303  LVecBase4 Gy = By * Mbi;
304  LVecBase4 Gz = Bz * Mbi;
305 
306  if (rational) {
307  LVecBase4 Gw = Bw * Mbi;
308  seg._v[0].set(Gx[0]/Gw[0], Gy[0]/Gw[0], Gz[0]/Gw[0]);
309  seg._v[1].set(Gx[1]/Gw[1], Gy[1]/Gw[1], Gz[1]/Gw[1]);
310  seg._v[2].set(Gx[2]/Gw[2], Gy[2]/Gw[2], Gz[2]/Gw[2]);
311  seg._v[3].set(Gx[3]/Gw[3], Gy[3]/Gw[3], Gz[3]/Gw[3]);
312  } else {
313  seg._v[0].set(Gx[0], Gy[0], Gz[0]);
314  seg._v[1].set(Gx[1], Gy[1], Gz[1]);
315  seg._v[2].set(Gx[2], Gy[2], Gz[2]);
316  seg._v[3].set(Gx[3], Gy[3], Gz[3]);
317  }
318 
319  return true;
320 }
321 
322 // We need this operator since Performer didn't supply it.
323 inline LVecBase4
324 col_mult(const LMatrix4 &M, const LVecBase4 &v) {
325  return LVecBase4(M(0,0)*v[0] + M(0,1)*v[1] + M(0,2)*v[2] + M(0,3)*v[3],
326  M(1,0)*v[0] + M(1,1)*v[1] + M(1,2)*v[2] + M(1,3)*v[3],
327  M(2,0)*v[0] + M(2,1)*v[1] + M(2,2)*v[2] + M(2,3)*v[3],
328  M(3,0)*v[0] + M(3,1)*v[1] + M(3,2)*v[2] + M(3,3)*v[3]);
329 }
330 
331 /**
332  * Interprets the parameters for a particular column of compute_seg. Builds
333  * the indicated column of T and P.
334  */
335 static bool
336 compute_seg_col(int c,
337  int rtype, PN_stdfloat t, const LVecBase4 &v,
338  const LMatrix4 &B,
339  const LMatrix4 &Bi,
340  const LMatrix4 &G,
341  const LMatrix4 &GB,
342  LMatrix4 &T, LMatrix4 &P) {
343  bool keep_orig = ((rtype & RT_KEEP_ORIG) != 0);
344 
345  if (parametrics_cat.is_debug()) {
346  parametrics_cat.debug()
347  << "Computing col " << c << " type " << (rtype & RT_BASE_TYPE)
348  << " at " << t << " keep_orig = " << keep_orig
349  << " v = " << v << "\n";
350  }
351 
352  switch (rtype & RT_BASE_TYPE) {
353  // RT_point defines the point on the curve at t. This is the vector [ t^3
354  // t^2 t^1 t^0 ].
355  PN_stdfloat t_sqrd,t_cubed;
356 
357  case RT_POINT:
358  t_sqrd = t*t;
359  t_cubed = t_sqrd*t;
360  T.set_col(c, LVecBase4(t_cubed, t_sqrd, t, 1.0f));
361  if (keep_orig) {
362  LVecBase4 vec(t_cubed, t_sqrd, t, 1.0f);
363  LVecBase4 ov = col_mult(GB, vec);
364  if (parametrics_cat.is_debug()) {
365  parametrics_cat.debug()
366  << "orig point = " << ov << "\n";
367  }
368  P.set_col(c, ov);
369  } else {
370  P.set_col(c, v);
371  }
372  break;
373 
374  // RT_tangent defines the tangent to the curve at t. This is the vector [
375  // 3t^2 2t 1 0 ].
376  case RT_TANGENT:
377  t_sqrd = t*t;
378  T.set_col(c, LVecBase4(3.0f*t_sqrd, t+t, 1.0f, 0.0f));
379  if (keep_orig) {
380  LVecBase4 vec(3.0f*t_sqrd, /*2.0f*t*/t+t, 1.0f, 0.0f);
381  LVecBase4 ov = col_mult(GB, vec);
382  if (parametrics_cat.is_debug()) {
383  parametrics_cat.debug()
384  << "Matrix is:\n";
385  GB.write(parametrics_cat.debug(false), 2);
386  parametrics_cat.debug(false)
387  << "vector is " << vec << "\n"
388  << "orig tangent = " << ov << "\n";
389  }
390  P.set_col(c, ov);
391  } else {
392  P.set_col(c, v);
393  }
394  break;
395 
396  // RT_cv defines the cth control point. This is the cth column vector
397  // from Bi.
398  case RT_CV:
399  T.set_col(c, Bi.get_col(c));
400  if (keep_orig) {
401  if (parametrics_cat.is_debug()) {
402  parametrics_cat.debug()
403  << "orig CV = " << G.get_col(c) << "\n";
404  }
405  P.set_col(c, G.get_col(c));
406  } else {
407  P.set_col(c, v);
408  }
409  break;
410 
411  default:
412  std::cerr << "Invalid rebuild type in compute_seg\n";
413  return false;
414  }
415 
416  return true;
417 }
418 
419 /**
420  * Given a set of four properties of a curve segment (e.g. four points, four
421  * tangent values, four control points, or any combination), and a basis
422  * matrix, computes the corresponding geometry matrix that (together with the
423  * basis matrix) represents the curve that satisfies the four properties.
424  *
425  * The basis matrix is passed in as B, and its inverse must be precomputed and
426  * passed in as Bi.
427  *
428  * The result is returned in the matrix G, each column of which represents the
429  * cth control vertex. If any of the four properties has RT_KEEP_ORIG set
430  * (see below), G's input value is used to define the original shape of the
431  * curve; otherwise, G's input value is ignored.
432  *
433  * Each property is defined by an rtype, which may be any of RT_POINT,
434  * RT_TANGENT, or RT_CV, and may or may not be or'ed with RT_KEEP_ORIG. The
435  * meanings of the types are as follows:
436  *
437  * RT_POINT defines a specific point which the curve segment must pass
438  * through. t is in the range [0,1] and represents the parametric value at
439  * which the curve segment will intersect the given point. If RT_KEEP_ORIG is
440  * not set, v defines the point; otherwise, v is ignored and the original
441  * curve at point t defines the point.
442  *
443  * RT_TANGENT defines a specific tangent value which the curve segment must
444  * have at point t. As with RT_POINT, if RT_KEEP_ORIG is not set, v defines
445  * the tangent; otherwise, v is ignored and the original curve defines the
446  * tangent.
447  *
448  * RT_CV defines a specific control vertex which the curve segment must have.
449  * In this case, t is ignored. The position within the argument list
450  * determines which control vertex is applicable; e.g. rtype0 = RT_CV defines
451  * control vertex 0, and rtype2 = RT_CV defines control vertex 2. If
452  * RT_KEEP_ORIG is not set, v defines the new control vertex; otherwise, the
453  * control vertex is taken from G.
454  *
455  * The return value is true if all the parameters are sensible, or false if
456  * there is some error.
457  */
458 bool CubicCurveseg::
459 compute_seg(int rtype0, PN_stdfloat t0, const LVecBase4 &v0,
460  int rtype1, PN_stdfloat t1, const LVecBase4 &v1,
461  int rtype2, PN_stdfloat t2, const LVecBase4 &v2,
462  int rtype3, PN_stdfloat t3, const LVecBase4 &v3,
463  const LMatrix4 &B,
464  const LMatrix4 &Bi,
465  LMatrix4 &G) {
466 
467  // We can define a cubic curve segment given four arbitrary properties of
468  // the segment: any point along the curve, any tangent along the curve, any
469  // control point. Given any four such properties, a single cubic curve
470  // segment is defined.
471 
472  // For a given cubic curve segment so defined, and given a basis matrix B,
473  // we can define the four control vertices that represent the segment with
474  // the basis matrix. That is, we can define the matrix G such that G * B *
475  // tc, where tc is [ t^3 t^2 t^1 t^0 ] for t in [ 0..1 ], represents the
476  // point on the curve segment corresponding to t.
477 
478  // First, we build a matrix T, such that each of the four columns of T
479  // contains the vector that would compute the corresponding property. We
480  // also build a corresponding matrix P, such that each of its columns
481  // contains the vector that is the solution of the corresponding column in
482  // T.
483 
484  LMatrix4 T, P, GB;
485 
486  // GB is G * B, but we only need to compute this if any of the columns wants
487  // the value from the original G.
488  if ((rtype0 | rtype1 | rtype2 | rtype3) & RT_KEEP_ORIG) {
489  GB = G * B;
490  }
491 
492  if (! (compute_seg_col(0, rtype0, t0, v0, B, Bi, G, GB, T, P) &&
493  compute_seg_col(1, rtype1, t1, v1, B, Bi, G, GB, T, P) &&
494  compute_seg_col(2, rtype2, t2, v2, B, Bi, G, GB, T, P) &&
495  compute_seg_col(3, rtype3, t3, v3, B, Bi, G, GB, T, P))) {
496  return false;
497  }
498 
499  LMatrix4 Ti;
500  Ti = invert(T);
501 
502  // Now we have T and P such that P represents the solution of T, when T is
503  // applied to the geometry and basis matrices. That is, each column of P
504  // represents the solution computed by the corresponding column of T. P = G
505  // * B * T.
506 
507  // We simply solve for G and get G = P * T^(-1) * B^(-1).
508 
509  G = P * Ti * Bi;
510 
511  return true;
512 }
513 
514 /**
515  * Initializes the factory for reading these things from Bam files.
516  */
517 void CubicCurveseg::
519  BamReader::get_factory()->register_factory(get_class_type(), make_CubicCurveseg);
520 }
521 
522 /**
523  * Factory method to generate an object of this type.
524  */
525 TypedWritable *CubicCurveseg::
526 make_CubicCurveseg(const FactoryParams &params) {
527  CubicCurveseg *me = new CubicCurveseg;
528  DatagramIterator scan;
529  BamReader *manager;
530 
531  parse_params(params, scan, manager);
532  me->fillin(scan, manager);
533  return me;
534 }
535 
536 /**
537  * Function to write the important information in the particular object to a
538  * Datagram
539  */
540 void CubicCurveseg::
541 write_datagram(BamWriter *manager, Datagram &me) {
542  ParametricCurve::write_datagram(manager, me);
543 
544  Bx.write_datagram(me);
545  By.write_datagram(me);
546  Bz.write_datagram(me);
547  Bw.write_datagram(me);
548  me.add_bool(rational);
549 }
550 
551 /**
552  * Function that reads out of the datagram (or asks manager to read) all of
553  * the data that is needed to re-create this object and stores it in the
554  * appropiate place
555  */
556 void CubicCurveseg::
557 fillin(DatagramIterator &scan, BamReader *manager) {
558  ParametricCurve::fillin(scan, manager);
559 
560  Bx.read_datagram(scan);
561  By.read_datagram(scan);
562  Bz.read_datagram(scan);
563  Bw.read_datagram(scan);
564  rational = scan.get_bool();
565 }
bool get_bool()
Extracts a boolean value.
virtual bool get_point(PN_stdfloat t, LVecBase3 &point) const
Computes the surface point at a given parametric point t.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
void hermite_basis(const HermiteCurveCV &cv0, const HermiteCurveCV &cv1, PN_stdfloat tlength=1.0f)
Defines the curve segment as a Hermite.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void bezier_basis(const BezierSeg &seg)
Defines the curve segment as a Bezier.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
virtual bool get_2ndtangent(PN_stdfloat t, LVecBase3 &tangent2) const
Computes the surface 2nd-order tangent at a given parametric point t.
A CubicCurveseg is any curve that can be completely described by four 4-valued basis vectors,...
Definition: cubicCurveseg.h:50
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void parse_params(const FactoryParams &params, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
Definition: bamReader.I:275
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition: datagram.I:34
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void register_factory(TypeHandle handle, CreateFunc *func, void *user_data=nullptr)
Registers a new kind of thing the Factory will be able to create.
Definition: factory.I:73
virtual bool get_bezier_seg(BezierSeg &seg) const
Fills the BezierSeg structure with a description of the curve segment as a Bezier,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static bool compute_seg(int rtype0, PN_stdfloat t0, const LVecBase4 &v0, int rtype1, PN_stdfloat t1, const LVecBase4 &v1, int rtype2, PN_stdfloat t2, const LVecBase4 &v2, int rtype3, PN_stdfloat t3, const LVecBase4 &v3, const LMatrix4 &B, const LMatrix4 &Bi, LMatrix4 &G)
Given a set of four properties of a curve segment (e.g.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
A single CV of a Hermite curve.
Definition: hermiteCurve.h:50
static void register_with_read_factory()
Initializes the factory for reading these things from Bam files.
void nurbs_basis(int order, const PN_stdfloat knots[], const LVecBase4 cvs[])
Defines the curve segment as a NURBS.
A class to retrieve the individual data elements previously stored in a Datagram.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
virtual bool get_pt(PN_stdfloat t, LVecBase3 &point, LVecBase3 &tangent) const
Simultaneously computes the point and the tangent at the given parametric point.
virtual bool get_tangent(PN_stdfloat t, LVecBase3 &tangent) const
Computes the surface tangent at a given parametric point t.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.