1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoNurbsProfile SoNurbsProfile.h Inventor/nodes/SoNurbsProfile.h
35   \brief The SoNurbsProfile class is a node for specifying smooth profile curves.
36 
37   \ingroup nodes
38 
39   Use nodes of this type if you want to set up profiles that are
40   smooth curves.
41 
42   Use ProfileCoordinate2 for nonrational profile where weight is 1.0 (default),
43   and ProfileCoordinate3 for rational profile curves to specify the weight.
44   Weight is analogous to having magnets pulling on the curve.
45 
46   A typical usage case for SoNurbsProfile is to specify NURBS trimming
47   curves. For example:
48 
49   \code
50   #Inventor V2.1 ascii
51 
52   ShapeHints {
53     vertexOrdering COUNTERCLOCKWISE
54   }
55 
56   Coordinate3 {
57     point [
58       -3 -3 -3, -3 -1 -3, -3 1 -3, -3 3 -3,
59       -1 -3 -3, -1 -1  3, -1 1  3, -1 3 -3,
60        1 -3 -3,  1 -1  3,  1 1  3,  1 3 -3,
61        3 -3 -3,  3 -1 -3,  3 1 -3,  3 3 -3
62      ]
63   }
64 
65   ProfileCoordinate2 {
66     point [ 0.0 0.0 ,
67             0.75 0.0,
68             0.75 0.75 ,
69             0.25 0.75 ,
70             0.0 0.0  ]
71   }
72 
73   NurbsProfile {
74      index [ 0 , 1 , 2 , 3, 4 ]
75      linkage START_NEW
76      knotVector [ 0, 0, 0, 0, 0.5, 1, 1, 1, 1 ]
77   }
78 
79   NurbsSurface {
80     numUControlPoints 4
81     numVControlPoints 4
82     uKnotVector [ 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 ]
83     vKnotVector [ 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 ]
84   }
85   \endcode
86 
87   <center>
88   \image html nurbsprofile.png "Rendering of Example Scenegraph"
89   </center>
90 
91   Note that the coordinates of the NurbsProfile live in the parametric
92   space of the trimmed SoNurbsSurface, and that the same complexity
93   setting (which is calculated based on the dimensions of the bounding
94   box of the nurbs surface) is used to determine the sampling
95   tolerance both for the SoNurbsSurface and the SoNurbsProfile.
96 
97   This means that if you want to change the tessellation of the
98   trimming curve <i>itself</i> (i.e. increase or decrease the
99   resolution of the boundaries of the "cut-out"), you should not
100   change the SoComplexity setting but rather adapt the parametric
101   scale in relation to the trimmed surface.
102 
103   As an example, to increase the resolution of the curve in the above
104   example, replace...
105 
106   \code
107   ProfileCoordinate2 {
108     point [ 0.0 0.0, 0.75 0.0, 0.75 0.75, 0.25 0.75, 0.0 0.0 ]
109   }
110   \endcode
111 
112   .. with...
113 
114   \code
115   ProfileCoordinate2 {
116     point [ 0.0 0.0, 7.5 0.0, 7.5 7.5, 2.5 7.5, 0.0 0.0 ]
117   }
118   \endcode
119 
120   and change the uKnotVector and vKnotVector of the NurbsSurface to be
121 
122   \code
123     uKnotVector [ 0.0, 0.0, 0.0, 0.0, 10, 10, 10, 10 ]
124     vKnotVector [ 0.0, 0.0, 0.0, 0.0, 10, 10, 10, 10 ]
125   \endcode
126 
127   However, keep in mind that increasing the accuracy of the trimming
128   curve results in a much more complex tesselation of the trimmed
129   surface. As a general rule of thumb, the extent of the trimming
130   curve coordinates should never be greater than its "real" extents in
131   relation to the trimmed surface, and often can be much lower.
132 
133   If you find the above confusing, you probably do not want to use
134   NURBS without reading up on the general concepts first.  An
135   explanation of NURBS is beyond the scope of the Coin documentation;
136   for detailed information, refer to the specialized literature on the
137   topic (for example "An Introduction to NURBS: With Historical
138   Perspective" by David F. Rogers). A basic overview of curve and
139   surface rendering using NURBS can also be found in chapter 8 of "The
140   Inventor Mentor".
141 
142   <b>FILE FORMAT/DEFAULTS:</b>
143   \code
144     NurbsProfile {
145         index 0
146         linkage START_FIRST
147         knotVector 0
148     }
149   \endcode
150 */
151 
152 // *************************************************************************
153 
154 #include <Inventor/nodes/SoNurbsProfile.h>
155 
156 #include <cstdlib>
157 
158 #ifdef HAVE_CONFIG_H
159 #include "config.h"
160 #endif // HAVE_CONFIG_H
161 
162 #include <Inventor/SbMatrix.h>
163 #include <Inventor/SbViewVolume.h>
164 #include <Inventor/elements/SoComplexityElement.h>
165 #include <Inventor/elements/SoProfileCoordinateElement.h>
166 #include <Inventor/lists/SbList.h>
167 #include <Inventor/errors/SoDebugError.h>
168 #include <Inventor/system/gl.h>
169 #include <Inventor/threads/SbStorage.h>
170 
171 #include "nodes/SoSubNodeP.h"
172 #include "glue/GLUWrapper.h"
173 #include "tidbitsp.h"
174 
175 // *************************************************************************
176 
177 /*!
178   \var SoMFFloat SoNurbsProfile::knotVector
179   Knot values for the nurbs curve.
180 */
181 
182 // *************************************************************************
183 
184 typedef struct {
185   SbList <float> * coordlist;
186   SbList <float> * tmplist;
187 } so_nurbsprofile_data;
188 
189 static void
so_nurbsprofile_construct_data(void * closure)190 so_nurbsprofile_construct_data(void * closure)
191 {
192   so_nurbsprofile_data * data = (so_nurbsprofile_data*) closure;
193   data->coordlist = NULL;
194   data->tmplist = NULL;
195 }
196 
197 static void
so_nurbsprofile_destruct_data(void * closure)198 so_nurbsprofile_destruct_data(void * closure)
199 {
200   so_nurbsprofile_data * data = (so_nurbsprofile_data*) closure;
201   delete data->coordlist;
202   delete data->tmplist;
203 }
204 
205 static SbStorage * so_nurbsprofile_storage;
206 
207 static void
so_nurbsprofile_cleanup(void)208 so_nurbsprofile_cleanup(void)
209 {
210   delete so_nurbsprofile_storage;
211 }
212 
213 static SbList <float> *
so_nurbsprofile_get_coordlist(const SbBool tmplist)214 so_nurbsprofile_get_coordlist(const SbBool tmplist)
215 {
216   so_nurbsprofile_data * data = NULL;
217   data = (so_nurbsprofile_data*) so_nurbsprofile_storage->get();
218 
219   if (tmplist) {
220     if (data->tmplist == NULL) {
221       data->tmplist = new SbList<float>;
222     }
223     return data->tmplist;
224   }
225   else {
226     if (data->coordlist == NULL) {
227       data->coordlist = new SbList<float>;
228     }
229     return data->coordlist;
230   }
231 }
232 
233 // *************************************************************************
234 
235 SO_NODE_SOURCE(SoNurbsProfile);
236 
237 /*!
238   Constructor.
239 */
SoNurbsProfile(void)240 SoNurbsProfile::SoNurbsProfile(void)
241 {
242   SO_NODE_INTERNAL_CONSTRUCTOR(SoNurbsProfile);
243 
244   SO_NODE_ADD_FIELD(knotVector, (0.0f));
245   this->nurbsrenderer = NULL;
246 }
247 
248 /*!
249   Destructor.
250 */
~SoNurbsProfile()251 SoNurbsProfile::~SoNurbsProfile()
252 {
253   if (this->nurbsrenderer) {
254     GLUWrapper()->gluDeleteNurbsRenderer(this->nurbsrenderer);
255   }
256 }
257 
258 // Doc from superclass.
259 void
initClass(void)260 SoNurbsProfile::initClass(void)
261 {
262   SO_NODE_INTERNAL_INIT_CLASS(SoNurbsProfile, SO_FROM_INVENTOR_1);
263   so_nurbsprofile_storage = new SbStorage(sizeof(so_nurbsprofile_data),
264                                           so_nurbsprofile_construct_data,
265                                           so_nurbsprofile_destruct_data);
266   coin_atexit((coin_atexit_f*) so_nurbsprofile_cleanup, CC_ATEXIT_NORMAL);
267 }
268 
269 // Doc from superclass.
270 void
getTrimCurve(SoState * state,int32_t & numpoints,float * & points,int & floatspervec,int32_t & numknots,float * & knotvector)271 SoNurbsProfile::getTrimCurve(SoState * state, int32_t & numpoints,
272                              float *& points, int & floatspervec,
273                              int32_t & numknots, float *& knotvector)
274 {
275   SbList <float> * coordListNurbsProfile =
276     so_nurbsprofile_get_coordlist(FALSE);
277 
278   numknots = this->knotVector.getNum();
279   if (numknots) knotvector = (float *)(this->knotVector.getValues(0));
280 
281   const SoProfileCoordinateElement * elem = (const SoProfileCoordinateElement*)
282     SoProfileCoordinateElement::getInstance(state);
283 
284   coordListNurbsProfile->truncate(0);
285 
286   // Get the number of SoProfileCoordinate2/3 points
287   int32_t numcoords = elem->getNum();
288   // Get the number of profile coordinate indices
289   int n = this->index.getNum();
290 
291   if (numcoords) {
292     // Both 2D or 3D profile coordinates might have been specified, so
293     // get the appropriate coordinates and save the number of floats
294     // per vector for later usage.
295     if (elem->is2D()) {
296       points = (float*) elem->getArrayPtr2();
297       floatspervec = 2;
298     }
299     else {
300       points = (float*) elem->getArrayPtr3();
301       floatspervec = 3;
302     }
303 
304     assert(points);
305   }
306 
307   // Append the coordinates to a list over the profile coordinates.
308   for (int i = 0; i < n; i++) {
309     int idx = this->index[i];
310 
311     // If valid profile coordinates have been specified
312     if (idx >= 0 && idx < numcoords) {
313       for (int j = 0; j < floatspervec; j++) {
314         coordListNurbsProfile->append(points[(idx * floatspervec) + j]);
315       }
316     }
317     // If invalid profile coordinates have been specified
318     else {
319       // Add dummy coordinate for robustness
320       for (int j = 0; j < floatspervec; j++) {
321         coordListNurbsProfile->append(0.0f);
322       }
323 
324       // Print errormessage
325       static uint32_t current_errors = 0;
326       if (current_errors < 1) {
327         SoDebugError::postWarning("SoNurbsProfile::getTrimCurve", "Illegal profile "
328                                   "coordinate index specified: %d. Should be within "
329                                   "[0, %d]", idx, numcoords - 1);
330       }
331       current_errors++;
332     }
333   }
334 
335   points = (float*) coordListNurbsProfile->getArrayPtr();
336   numpoints = n;
337 }
338 
339 
340 static void APIENTRY
nurbsprofile_tess_vertex(float * vertex)341 nurbsprofile_tess_vertex(float * vertex)
342 {
343   SbList <float> * coordListNurbsProfile =
344     so_nurbsprofile_get_coordlist(FALSE);
345 
346   coordListNurbsProfile->append(vertex[0]);
347   coordListNurbsProfile->append(vertex[1]);
348 }
349 
350 // doc from superclass.
351 void
getVertices(SoState * state,int32_t & numvertices,SbVec2f * & vertices)352 SoNurbsProfile::getVertices(SoState * state, int32_t & numvertices,
353                             SbVec2f * & vertices)
354 {
355   // FIXME: optimize by detecting when the previously calculated
356   // vertices can be returned. pederb, 20000922
357   int32_t numpoints;
358   float * points;
359   int floatspervec;
360   int32_t numknots;
361   float * knotvector;
362   this->getTrimCurve(state, numpoints, points, floatspervec, numknots, knotvector);
363   if (numpoints == 0 || numknots == 0) {
364     numvertices = 0;
365     vertices = NULL;
366     return;
367   }
368 
369   SbList <float> * coordListNurbsProfile =
370     so_nurbsprofile_get_coordlist(FALSE);
371 
372   SbList <float> * nurbsProfileTempList =
373     so_nurbsprofile_get_coordlist(TRUE);
374 
375   nurbsProfileTempList->truncate(0);
376   for (int i = 0; i < numpoints; i++) {
377     nurbsProfileTempList->append(points[i*floatspervec]);
378     nurbsProfileTempList->append(points[i*floatspervec+1]);
379     if (GLUWrapper()->available &&
380         GLUWrapper()->versionMatchesAtLeast(1, 3, 0)) {
381       nurbsProfileTempList->append(0.0f); // gluNurbs needs 3D coordinates
382     }
383   }
384   if (GLUWrapper()->available &&
385       GLUWrapper()->versionMatchesAtLeast(1, 3, 0)) {
386     // we will write into this array in the GLU callback
387     coordListNurbsProfile->truncate(0);
388 
389     if (this->nurbsrenderer == NULL) {
390       this->nurbsrenderer = GLUWrapper()->gluNewNurbsRenderer();
391       GLUWrapper()->gluNurbsCallback(this->nurbsrenderer, (GLenum) GLU_NURBS_VERTEX,
392                                      (gluNurbsCallback_cb_t)nurbsprofile_tess_vertex);
393       GLUWrapper()->gluNurbsProperty(this->nurbsrenderer, (GLenum) GLU_NURBS_MODE, GLU_NURBS_TESSELLATOR);
394       GLUWrapper()->gluNurbsProperty(this->nurbsrenderer, (GLenum) GLU_AUTO_LOAD_MATRIX, FALSE);
395       GLUWrapper()->gluNurbsProperty(this->nurbsrenderer, (GLenum) GLU_DISPLAY_MODE, GLU_POINT);
396       GLUWrapper()->gluNurbsProperty(this->nurbsrenderer, (GLenum) GLU_SAMPLING_METHOD, GLU_DOMAIN_DISTANCE);
397     }
398 
399     // this looks pretty good
400     float cmplx = SoComplexityElement::get(state);
401     cmplx += 1.0f;
402     cmplx = cmplx * cmplx * cmplx;
403     GLUWrapper()->gluNurbsProperty(this->nurbsrenderer, (GLenum) GLU_U_STEP, float(numpoints)*cmplx);
404 
405     // these values are not important as we're not using screen-space
406     // complexity (yet)
407     SbMatrix modelmatrix = SbMatrix::identity();
408     SbMatrix affine, proj;
409     SbViewVolume vv;
410     vv.ortho(0.0f, 1.0f,
411              0.0f, 1.0f,
412              -1.0f, 1.0f);
413     vv.getMatrices(affine, proj);
414     GLint viewport[4];
415     viewport[0] = 0;
416     viewport[1] = 0;
417     viewport[2] = 256;
418     viewport[3] = 256;
419     GLUWrapper()->gluLoadSamplingMatrices(this->nurbsrenderer,
420                                           modelmatrix[0],
421                                           proj[0],
422                                           viewport);
423 
424     // generate curve
425     GLUWrapper()->gluBeginCurve(this->nurbsrenderer);
426     GLUWrapper()->gluNurbsCurve(this->nurbsrenderer,
427                                 numknots,
428                                 (float*)knotvector,
429                                 3,
430                                 (float*)nurbsProfileTempList->getArrayPtr(),
431                                 numknots - numpoints,
432                                 GL_MAP1_VERTEX_3);
433     GLUWrapper()->gluEndCurve(this->nurbsrenderer);
434 
435     // when we get here, the GLU callback should have added the
436     // points to the list
437     numvertices = coordListNurbsProfile->getLength() / 2;
438     vertices = (SbVec2f*) coordListNurbsProfile->getArrayPtr();
439   }
440   else {
441     // just send the control points when GLU v1.3 is not available
442     numvertices = numpoints;
443     vertices = (SbVec2f*) nurbsProfileTempList->getArrayPtr();
444   }
445 }
446