Panda3D
Loading...
Searching...
No Matches
nurbsCurve.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 nurbsCurve.cxx
10 * @author drose
11 * @date 1998-02-27
12 */
13
14#include "nurbsCurve.h"
15#include "config_parametrics.h"
16
17#include "indent.h"
18#include "datagram.h"
19#include "datagramIterator.h"
20#include "bamWriter.h"
21#include "bamReader.h"
22#include "epvector.h"
23
24TypeHandle NurbsCurve::_type_handle;
25
26/**
27 *
28 */
29NurbsCurve::
30NurbsCurve() {
31 _order = 4;
32}
33
34/**
35 * Constructs a NURBS curve equivalent to the indicated (possibly non-NURBS)
36 * curve.
37 */
38NurbsCurve::
39NurbsCurve(const ParametricCurve &pc) {
40 _order = 4;
41
42 if (!pc.convert_to_nurbs(this)) {
43 parametrics_cat->warning()
44 << "Cannot make a NURBS from the indicated curve.\n";
45 }
46}
47
48/**
49 * Constructs a NURBS curve according to the indicated NURBS parameters.
50 */
51NurbsCurve::
52NurbsCurve(int order, int num_cvs,
53 const PN_stdfloat knots[], const LVecBase4 cvs[]) {
54 _order = order;
55
56 int i;
57 _cvs.reserve(num_cvs);
58 for (i = 0; i < num_cvs; i++) {
59 append_cv(cvs[i]);
60 }
61
62 int num_knots = num_cvs + order;
63 for (i = 0; i < num_knots; i++) {
64 set_knot(i, knots[i]);
65 }
66
67 recompute();
68}
69
70/**
71 *
72 */
73NurbsCurve::
74~NurbsCurve() {
75}
76
77/**
78 * Returns a newly-allocated PandaNode that is a shallow copy of this one. It
79 * will be a different pointer, but its internal data may or may not be shared
80 * with that of the original PandaNode. No children will be copied.
81 */
83make_copy() const {
84 return new NurbsCurve(*this);
85}
86
87/**
88 * Changes the order of the curve. Must be a value from 1 to 4. Can only be
89 * done when there are no cv's.
90 */
92set_order(int order) {
93 nassertv(order >= 1 && order <= 4);
94 nassertv(_cvs.empty());
95
96 _order = order;
97}
98
99/**
100 *
101 */
102int NurbsCurve::
103get_order() const {
104 return _order;
105}
106
107/**
108 *
109 */
110int NurbsCurve::
111get_num_cvs() const {
112 return _cvs.size();
113}
114
115/**
116 * Returns the number of knots on the curve.
117 */
119get_num_knots() const {
120 return _cvs.size() + _order;
121}
122
123
124
125/**
126 * Inserts a new CV into the middle of the curve at the indicated parametric
127 * value. This doesn't change the shape or timing of the curve; however, it
128 * is irreversible: if the new CV is immediately removed, the curve will be
129 * changed. Returns true if successful, false otherwise.
130 */
132insert_cv(PN_stdfloat t) {
133 if (_cvs.empty()) {
134 append_cv(0.0f, 0.0f, 0.0f);
135 return true;
136 }
137
138 if (t <= 0) {
139 t = 0.0f;
140 }
141
142 int k = find_cv(t);
143 if (k < 0) {
144 append_cv(_cvs.back()._p);
145 return true;
146 }
147
148 // Now we are inserting a knot between k-1 and k. We'll adjust the CV's
149 // according to Bohm's rule.
150
151 // First, get the new values of all the CV's that will change. These are
152 // the CV's in the range [k - (_order-1), k-1].
153
154 LVecBase4 new_cvs[3];
155 int i;
156 for (i = 0; i < _order-1; i++) {
157 int nk = i + k - (_order-1);
158 PN_stdfloat ti = get_knot(nk);
159 PN_stdfloat d = get_knot(nk + _order-1) - ti;
160 if (d == 0.0f) {
161 new_cvs[i] = _cvs[nk-1]._p;
162 } else {
163 PN_stdfloat a = (t - ti) / d;
164 new_cvs[i] = (1.0f-a)*_cvs[nk-1]._p + a*_cvs[nk]._p;
165 }
166 }
167
168 // Now insert the new CV
169 _cvs.insert(_cvs.begin() + k-1, CV());
170
171 // Set all the new position values
172 for (i = 0; i < _order-1; i++) {
173 int nk = i + k - (_order-1);
174 _cvs[nk]._p = new_cvs[i];
175 }
176
177 // And set the new knot value.
178 _cvs[k-1]._t = t;
179
180 return true;
181}
182
183/**
184 * Removes the indicated CV from the curve. Returns true if the CV index was
185 * valid, false otherwise.
186 */
188remove_cv(int n) {
189 if (n < 0 || n >= (int)_cvs.size()) {
190 return false;
191 }
192
193 _cvs.erase(_cvs.begin() + n);
194 return true;
195}
196
197/**
198 * Removes all CV's from the curve.
199 */
202 _cvs.erase(_cvs.begin(), _cvs.end());
203}
204
205
206/**
207 * Repositions the indicated CV. Returns true if successful, false otherwise.
208 */
210set_cv(int n, const LVecBase4 &v) {
211 nassertr(n >= 0 && n < get_num_cvs(), false);
212
213 _cvs[n]._p = v;
214 return true;
215}
216
217/**
218 * Returns the position in homogeneous space of the indicated CV.
219 */
220LVecBase4 NurbsCurve::
221get_cv(int n) const {
222 nassertr(n >= 0 && n < get_num_cvs(), LVecBase4::zero());
223
224 return _cvs[n]._p;
225}
226
227
228/**
229 * Sets the value of the indicated knot. There are get_num_cvs() + _order
230 * knot values, but the first _order - 1 and the last 1 knot values cannot be
231 * changed. It is also an error to set a knot value outside the range of its
232 * neighbors.
233 */
235set_knot(int n, PN_stdfloat t) {
236 nassertr(n >= 0 && n < get_num_knots(), false);
237
238 if (n < _order || n-1 >= (int)_cvs.size()) {
239 return false;
240 }
241 _cvs[n-1]._t = t;
242 return true;
243}
244
245/**
246 * Retrieves the value of the indicated knot.
247 */
248PN_stdfloat NurbsCurve::
249get_knot(int n) const {
250 if (n < _order || _cvs.empty()) {
251 return 0.0f;
252 } else if (n-1 >= (int)_cvs.size()) {
253 return _cvs.back()._t;
254 } else {
255 return _cvs[n-1]._t;
256 }
257}
258
259
260/**
261 * Recalculates the curve basis according to the latest position of the CV's,
262 * knots, etc. Until this function is called, adjusting the NURBS parameters
263 * will have no visible effect on the curve. Returns true if the resulting
264 * curve is valid, false otherwise.
265 */
267recompute() {
268 _segs.erase(_segs.begin(), _segs.end());
269
270 PN_stdfloat knots[8];
271 LVecBase4 cvs[4];
272
273 if ((int)_cvs.size() > _order-1) {
274 for (int cv = 0; cv < (int)_cvs.size()-(_order-1); cv++) {
275 if (get_knot(cv+_order-1) < get_knot(cv+_order)) {
276 // There are _order consecutive CV's that define each segment,
277 // beginning at cv. Collect the CV's and knot values that define this
278 // segment.
279 int c;
280 for (c = 0; c < _order; c++) {
281 cvs[c] = _cvs[c+cv]._p;
282 }
283 for (c = 0; c < _order+_order; c++) {
284 knots[c] = get_knot(c+cv);
285 }
286
287 insert_curveseg(_segs.size(), new CubicCurveseg(_order, knots, cvs),
288 knots[_order] - knots[_order-1]);
289 }
290 }
291 }
292
293 return !_segs.empty();
294}
295
296/**
297 * Rebuilds the current curve segment (as selected by the most recent call to
298 * find_curve()) according to the specified properties (see
299 * CubicCurveseg::compute_seg). Returns true if possible, false if something
300 * goes horribly wrong.
301 */
303rebuild_curveseg(int rtype0, PN_stdfloat t0, const LVecBase4 &v0,
304 int rtype1, PN_stdfloat t1, const LVecBase4 &v1,
305 int rtype2, PN_stdfloat t2, const LVecBase4 &v2,
306 int rtype3, PN_stdfloat t3, const LVecBase4 &v3) {
307 // Figure out which CV's contributed to this segment.
308 int seg = 0;
309
310 nassertr((int)_cvs.size() > _order-1, false);
311
312 int cv = 0;
313 for (cv = 0; cv < (int)_cvs.size()-(_order-1); cv++) {
314 if (get_knot(cv+_order-1) < get_knot(cv+_order)) {
315 if (seg == _last_ti) {
316 break;
317 }
318 seg++;
319 }
320 }
321
322 // Now copy the cvs and knots in question.
323 LMatrix4 G;
324 PN_stdfloat knots[8];
325
326 int c;
327
328 // We only need to build the geometry matrix if at least one of the
329 // properties depends on the original value.
330 if ((rtype0 | rtype1 | rtype2 | rtype3) & RT_KEEP_ORIG) {
331 for (c = 0; c < 4; c++) {
332 const LVecBase4 &s = (c < _order) ? _cvs[c+cv]._p : LVecBase4::zero();
333
334 G.set_col(c, s);
335 }
336 }
337
338 // But we always need the knot vector to determine the basis matrix.
339 for (c = 0; c < _order+_order; c++) {
340 knots[c] = get_knot(c+cv);
341 }
342
343 LMatrix4 B;
344 compute_nurbs_basis(_order, knots, B);
345
346 LMatrix4 Bi;
347 Bi = invert(B);
348
349 if (!CubicCurveseg::compute_seg(rtype0, t0, v0,
350 rtype1, t1, v1,
351 rtype2, t2, v2,
352 rtype3, t3, v3,
353 B, Bi, G)) {
354 return false;
355 }
356
357 // Now extract the new CV's from the new G matrix, and restore them to the
358 // curve.
359 for (c = 0; c < _order; c++) {
360 _cvs[c+cv]._p = G.get_col(c);
361 }
362
363 return true;
364}
365
366/**
367 * Regenerates this curve as one long curve: the first curve connected end-to-
368 * end with the second one. Either a or b may be the same as 'this'.
369 *
370 * Returns true if successful, false on failure or if the curve type does not
371 * support stitching.
372 */
374stitch(const ParametricCurve *a, const ParametricCurve *b) {
375 // First, make a copy of both of our curves. This ensures they are of the
376 // correct type, and also protects us in case one of them is the same as
377 // 'this'.
378 PT(NurbsCurve) na = new NurbsCurve(*a);
379 PT(NurbsCurve) nb = new NurbsCurve(*b);
380
381 if (na->get_num_cvs() == 0 || nb->get_num_cvs() == 0) {
382 return false;
383 }
384
385 if (na->get_order() != nb->get_order()) {
386 parametrics_cat->error()
387 << "Cannot stitch NURBS curves of different orders!\n";
388 return false;
389 }
390
391 // First, translate curve B to move its first CV to curve A's last CV.
392 LVecBase3 point_offset =
393 na->get_cv_point(na->get_num_cvs() - 1) - nb->get_cv_point(0);
394 int num_b_cvs = nb->get_num_cvs();
395 for (int i = 0; i < num_b_cvs; i++) {
396 nb->set_cv_point(i, nb->get_cv_point(i) + point_offset);
397 }
398
399 // Now define a vector of all of A's CV's except the last one.
400 _cvs = na->_cvs;
401 if (!_cvs.empty()) {
402 _cvs.pop_back();
403 }
404
405 PN_stdfloat t = na->get_max_t();
406
407 // Now add all the new CV's.
408 epvector<CV>::iterator ci;
409 for (ci = nb->_cvs.begin(); ci != nb->_cvs.end(); ++ci) {
410 CV new_cv = (*ci);
411 new_cv._t += t;
412 _cvs.push_back(new_cv);
413 }
414
415 recompute();
416 return true;
417}
418
419
420/**
421 * Returns a pointer to the object as a NurbsCurveInterface object if it
422 * happens to be a NURBS-style curve; otherwise, returns NULL.
423 */
428
429/**
430 * Stores in the indicated NurbsCurve a NURBS representation of an equivalent
431 * curve. Returns true if successful, false otherwise.
432 */
435 nc->set_curve_type(_curve_type);
436 return NurbsCurveInterface::convert_to_nurbs(nc);
437}
438
439/**
440 *
441 */
442void NurbsCurve::
443write(std::ostream &out, int indent_level) const {
444 NurbsCurveInterface::write(out, indent_level);
445}
446
447/**
448 * Adds a new CV to the end of the curve. Creates a new knot value by adding
449 * 1 to the last knot value. Returns the index of the new CV.
450 */
451int NurbsCurve::
452append_cv_impl(const LVecBase4 &v) {
453 _cvs.push_back(CV(v, get_knot(_cvs.size())+1.0f));
454 return _cvs.size()-1;
455}
456
457/**
458 * Formats the curve as an egg structure to write to the indicated stream.
459 * Returns true on success, false on failure.
460 */
461bool NurbsCurve::
462format_egg(std::ostream &out, const std::string &name, const std::string &curve_type,
463 int indent_level) const {
464 return NurbsCurveInterface::format_egg(out, name, curve_type, indent_level);
465}
466
467/**
468 * Finds the first knot whose value is >= t, or -1 if t is beyond the end of
469 * the curve.
470 */
471int NurbsCurve::
472find_cv(PN_stdfloat t) {
473 int i;
474 for (i = _order-1; i < (int)_cvs.size(); i++) {
475 if (_cvs[i]._t >= t) {
476 return i+1;
477 }
478 }
479
480 return -1;
481}
482
483/**
484 * Initializes the factory for reading these things from Bam files.
485 */
488 BamReader::get_factory()->register_factory(get_class_type(), make_NurbsCurve);
489}
490
491/**
492 * Factory method to generate an object of this type.
493 */
494TypedWritable *NurbsCurve::
495make_NurbsCurve(const FactoryParams &params) {
496 NurbsCurve *me = new NurbsCurve;
497 DatagramIterator scan;
498 BamReader *manager;
499
500 parse_params(params, scan, manager);
501 me->fillin(scan, manager);
502 return me;
503}
504
505/**
506 * Function to write the important information in the particular object to a
507 * Datagram
508 */
509void NurbsCurve::
510write_datagram(BamWriter *manager, Datagram &me) {
511 PiecewiseCurve::write_datagram(manager, me);
512
513 me.add_int8(_order);
514
515 me.add_uint32(_cvs.size());
516 size_t i;
517 for (i = 0; i < _cvs.size(); i++) {
518 const CV &cv = _cvs[i];
519 cv._p.write_datagram(me);
520 me.add_float64(cv._t);
521 }
522}
523
524/**
525 * Function that reads out of the datagram (or asks manager to read) all of
526 * the data that is needed to re-create this object and stores it in the
527 * appropiate place
528 */
529void NurbsCurve::
530fillin(DatagramIterator &scan, BamReader *manager) {
531 PiecewiseCurve::fillin(scan, manager);
532
533 _order = scan.get_int8();
534
535 size_t num_cvs = scan.get_uint32();
536
537 _cvs.reserve(num_cvs);
538 size_t i;
539 for (i = 0; i < num_cvs; i++) {
540 CV cv;
541 cv._p.read_datagram(scan);
542 cv._t = scan.get_float64();
543 _cvs.push_back(cv);
544 }
545}
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition bamReader.h:110
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition bamReader.I:177
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition bamWriter.h:63
A CubicCurveseg is any curve that can be completely described by four 4-valued basis vectors,...
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.
A class to retrieve the individual data elements previously stored in a Datagram.
uint32_t get_uint32()
Extracts an unsigned 32-bit integer.
int8_t get_int8()
Extracts a signed 8-bit integer.
PN_float64 get_float64()
Extracts a 64-bit floating-point number.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
void add_uint32(uint32_t value)
Adds an unsigned 32-bit integer to the datagram.
Definition datagram.I:94
void add_float64(PN_float64 value)
Adds a 64-bit floating-point number to the datagram.
Definition datagram.I:123
void add_int8(int8_t value)
Adds a signed 8-bit integer to the datagram.
Definition datagram.I:42
An instance of this class is passed to the Factory when requesting it to do its business and construc...
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
This abstract class defines the interface only for a Nurbs-style curve, with knots and coordinates in...
A Nonuniform Rational B-Spline.
Definition nurbsCurve.h:41
virtual NurbsCurveInterface * get_nurbs_interface()
Returns a pointer to the object as a NurbsCurveInterface object if it happens to be a NURBS-style cur...
virtual void set_order(int order)
Changes the order of the curve.
virtual bool recompute()
Recalculates the curve basis according to the latest position of the CV's, knots, etc.
virtual PN_stdfloat get_knot(int n) const
Retrieves the value of the indicated knot.
static void register_with_read_factory()
Initializes the factory for reading these things from Bam files.
virtual bool insert_cv(PN_stdfloat t)
Inserts a new CV into the middle of the curve at the indicated parametric value.
virtual void remove_all_cvs()
Removes all CV's from the curve.
virtual bool remove_cv(int n)
Removes the indicated CV from the curve.
virtual int get_num_knots() const
Returns the number of knots on the curve.
virtual bool stitch(const ParametricCurve *a, const ParametricCurve *b)
Regenerates this curve as one long curve: the first curve connected end-to- end with the second one.
virtual bool rebuild_curveseg(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)
Rebuilds the current curve segment (as selected by the most recent call to find_curve()) according to...
virtual bool set_cv(int n, const LVecBase4 &v)
Repositions the indicated CV.
virtual PandaNode * make_copy() const
Returns a newly-allocated PandaNode that is a shallow copy of this one.
virtual bool convert_to_nurbs(ParametricCurve *nc) const
Stores in the indicated NurbsCurve a NURBS representation of an equivalent curve.
virtual bool set_knot(int n, PN_stdfloat t)
Sets the value of the indicated knot.
A basic node of the scene graph or data graph.
Definition pandaNode.h:65
A virtual base class for parametric curves.
virtual bool convert_to_nurbs(ParametricCurve *nc) const
Stores in the indicated NurbsCurve a NURBS representation of an equivalent curve.
void set_curve_type(int type)
Sets the flag indicating the use to which the curve is intended to be put.
bool insert_curveseg(int ti, ParametricCurve *seg, PN_stdfloat tlength)
Inserts a new curve segment at the indicated index.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.