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 SoTexture3 SoTexture3.h Inventor/nodes/SoTexture3.h
35   \brief The SoTexture3 class is used to map a 3D texture onto geometry.
36 
37   \ingroup nodes
38 
39   Shape nodes within the scope of SoTexture3 nodes in the scenegraph
40   (ie below the same SoSeparator and to the righthand side of the
41   SoTexture3) will have the texture applied according to each shape
42   type's individual characteristics.  See the documentation of the
43   various shape types (SoFaceSet, SoCube, SoSphere, etc etc) for
44   information about the specifics of how the textures will be applied.
45   An SoTexture3 node will override any previous encountered SoTexture2 nodes
46   and vice versa. Mixing of SoTexture3 and SoTextureCoordinate2 (or the other
47   way around) is legal, but the third texture coordinate component will
48   be ignored (set to 0.0).
49 
50   <center>
51   \image html texture3.png "Rendering of an Example Texture3"
52   </center>
53 
54   \COIN_CLASS_EXTENSION
55 
56   <b>FILE FORMAT/DEFAULTS:</b>
57   \code
58     Texture3 {
59         filenames ""
60         images 0 0 0 0
61         wrapR REPEAT
62         wrapS REPEAT
63         wrapT REPEAT
64         model MODULATE
65         blendColor 0 0 0
66         enableCompressedTexture FALSE
67     }
68   \endcode
69 
70   \since Coin 2.0
71   \since TGS Inventor 2.6
72 */
73 
74 // *************************************************************************
75 
76 #include <Inventor/nodes/SoTexture3.h>
77 
78 #include <cassert>
79 #include <cstring>
80 
81 #include <Inventor/SoInput.h>
82 #include <Inventor/actions/SoCallbackAction.h>
83 #include <Inventor/actions/SoGLRenderAction.h>
84 #include <Inventor/elements/SoGLMultiTextureEnabledElement.h>
85 #include <Inventor/elements/SoGLMultiTextureImageElement.h>
86 #include <Inventor/elements/SoGLCacheContextElement.h>
87 #include <Inventor/elements/SoTextureQualityElement.h>
88 #include <Inventor/elements/SoTextureOverrideElement.h>
89 #include <Inventor/elements/SoTextureUnitElement.h>
90 #include <Inventor/errors/SoReadError.h>
91 #include <Inventor/misc/SoGLBigImage.h>
92 #include <Inventor/misc/SoGLDriverDatabase.h>
93 #include <Inventor/sensors/SoFieldSensor.h>
94 #include <Inventor/lists/SbStringList.h>
95 #include <Inventor/errors/SoDebugError.h>
96 #include <Inventor/SbImage.h>
97 #include <Inventor/C/glue/gl.h>
98 
99 #include "nodes/SoSubNodeP.h"
100 #include "elements/SoTextureScalePolicyElement.h"
101 
102 // *************************************************************************
103 
104 /*!
105   \enum SoTexture3::Model
106   Texture mapping model.
107 */
108 /*!
109   \var SoTexture3::Model SoTexture3::MODULATE
110   Texture image is modulated with polygon.
111 */
112 /*!
113   \var SoTexture3::Model SoTexture3::DECAL
114   Texture image overwrites polygon color.
115 */
116 /*!
117   \var SoTexture3::Model SoTexture3::BLEND
118   Blend image using blendColor.
119 */
120 
121 /*!
122   \enum SoTexture3::Wrap
123   Enum used to specify wrapping strategy.
124 */
125 /*!
126   \var SoTexture3::Wrap SoTexture3::REPEAT
127   Repeat texture when coordinate is not between 0 and 1.
128 */
129 /*!
130   \var SoTexture3::Wrap SoTexture3::CLAMP
131   Clamp coordinate between 0 and 1.
132 */
133 
134 
135 /*!
136   \var SoMFString SoTexture3::filenames
137   Texture filename(s). Specify either this or use SoTexture3::images, not both.
138   The depth of the volume is specifies by the number of filenames specified.
139   All images must have the same dimensions and number of components.
140   NB! A field sensor is attached to this field internally and reloads all
141   images when this field changes. You must therefore be careful when
142   setting this field and either use startEditing()/finishEditing() or set
143   all values with one function call; setValues().
144 */
145 /*!
146   \var SoSFImage3 SoTexture3::images
147   Inline image data.
148 */
149 /*!
150   \var SoSFEnum SoTexture3::wrapR
151   Wrapping strategy for the R coordinate (depth).
152 */
153 /*!
154   \var SoSFEnum SoTexture3::wrapS
155   Wrapping strategy for the S coordinate.
156 */
157 /*!
158   \var SoSFEnum SoTexture3::wrapT
159   Wrapping strategy for the T coordinate.
160 */
161 /*!
162   \var SoSFEnum SoTexture3::model
163   Texture model.
164 */
165 /*!
166   \var SoSFColor SoTexture3::blendColor
167   Blend color. Used when SoTexture3::model is SoTexture3::BLEND.
168 */
169 
170 /*!
171   \var SoSFBool SoTexture3::enableCompressedTexture
172 
173   Hint to Coin that compressed textures should be used if this
174   is supported by the graphics hardware and OpenGL drivers.
175   Using compressed textures usually reduces texture memory usage
176   for a texture by 4-6 times.
177 
178   \since Coin 2.4.2
179   \since TGS Inventor 4.0
180 */
181 
182 // *************************************************************************
183 
184 SO_NODE_SOURCE(SoTexture3);
185 
186 /*!
187   Constructor.
188 */
SoTexture3(void)189 SoTexture3::SoTexture3(void)
190 {
191   SO_NODE_INTERNAL_CONSTRUCTOR(SoTexture3);
192 
193   SO_NODE_ADD_FIELD(filenames, (""));
194   SO_NODE_ADD_FIELD(images, (SbVec3s(0, 0, 0), 0, NULL));
195   SO_NODE_ADD_FIELD(wrapR, (REPEAT));
196   SO_NODE_ADD_FIELD(wrapS, (REPEAT));
197   SO_NODE_ADD_FIELD(wrapT, (REPEAT));
198   SO_NODE_ADD_FIELD(model, (MODULATE));
199   SO_NODE_ADD_FIELD(blendColor, (0.0f, 0.0f, 0.0f));
200   SO_NODE_ADD_FIELD(enableCompressedTexture, (FALSE));
201 
202   SO_NODE_DEFINE_ENUM_VALUE(Wrap, REPEAT);
203   SO_NODE_DEFINE_ENUM_VALUE(Wrap, CLAMP);
204 
205   SO_NODE_SET_SF_ENUM_TYPE(wrapS, Wrap);
206   SO_NODE_SET_SF_ENUM_TYPE(wrapT, Wrap);
207   SO_NODE_SET_SF_ENUM_TYPE(wrapR, Wrap);
208 
209   SO_NODE_DEFINE_ENUM_VALUE(Model, MODULATE);
210   SO_NODE_DEFINE_ENUM_VALUE(Model, DECAL);
211   SO_NODE_DEFINE_ENUM_VALUE(Model, BLEND);
212   SO_NODE_SET_SF_ENUM_TYPE(model, Model);
213 
214   this->glimage = NULL;
215   this->glimagevalid = FALSE;
216   this->readstatus = 1;
217 
218   // use field sensor for filename since we will load an image if
219   // filename changes. This is a time-consuming task which should
220   // not be done in notify().
221   this->filenamesensor = new SoFieldSensor(filenameSensorCB, this);
222   this->filenamesensor->setPriority(0);
223   this->filenamesensor->attach(&this->filenames);
224 }
225 
226 /*!
227   Destructor.
228 */
~SoTexture3()229 SoTexture3::~SoTexture3()
230 {
231   if (this->glimage) this->glimage->unref(NULL);
232   delete this->filenamesensor;
233 }
234 
235 // doc from parent
236 void
initClass(void)237 SoTexture3::initClass(void)
238 {
239   SO_NODE_INTERNAL_INIT_CLASS(SoTexture3, SO_FROM_INVENTOR_2_6|SO_FROM_COIN_2_0);
240 
241   SO_ENABLE(SoGLRenderAction, SoGLMultiTextureImageElement);
242   SO_ENABLE(SoCallbackAction, SoMultiTextureImageElement);
243 }
244 
245 
246 // Documented in superclass.
247 SbBool
readInstance(SoInput * in,unsigned short flags)248 SoTexture3::readInstance(SoInput * in, unsigned short flags)
249 {
250   // Overridden to check if texture files (if any) can be found and
251   // loaded.
252 
253   this->filenamesensor->detach();
254   SbBool readOK = inherited::readInstance(in, flags);
255   this->setReadStatus((int) readOK);
256   if (readOK && !filenames.isDefault() && filenames.getNum()>0) {
257     if (!this->loadFilenames(in)) {
258       this->setReadStatus(FALSE);
259     }
260   }
261   this->filenamesensor->attach(&this->filenames);
262   return readOK;
263 }
264 
265 static SoGLImage::Wrap
translateWrap(const SoTexture3::Wrap wrap)266 translateWrap(const SoTexture3::Wrap wrap)
267 {
268   if (wrap == SoTexture3::REPEAT) return SoGLImage::REPEAT;
269   return SoGLImage::CLAMP;
270 }
271 
272 // doc from parent
273 void
GLRender(SoGLRenderAction * action)274 SoTexture3::GLRender(SoGLRenderAction * action)
275 {
276   SoState * state = action->getState();
277 
278   const cc_glglue * glue = cc_glglue_instance((uint32_t) SoGLCacheContextElement::get(state));
279 
280   int unit = SoTextureUnitElement::get(state);
281 
282   if (!SoGLDriverDatabase::isSupported(glue, SO_GL_3D_TEXTURES)) {
283     static SbBool first = TRUE;
284     if (first) {
285       SoDebugError::postWarning("SoTexture3::GLRender",
286                                 "The current OpenGL context does not support 3D textures "
287                                 "(This warning message is only shown once, but "
288                                 "there could be more cases of this in the "
289                                 "scene graph.).");
290       first = FALSE;
291     }
292     return;
293   }
294 
295 
296   if (SoTextureOverrideElement::getImageOverride(state))
297     return;
298 
299   float quality = SoTextureQualityElement::get(state);
300   if (!this->glimagevalid) {
301     int nc;
302     SbVec3s size;
303     const unsigned char *bytes = this->images.getValue(size, nc);
304     //FIXME: 3D support in SoGLBigImage (kintel 20011113)
305 //      SbBool needbig =
306 //        SoTextureScalePolicyElement::get(state) ==
307 //        SoTextureScalePolicyElement::DONT_SCALE;
308 
309 //      if (needbig &&
310 //          (this->glimage == NULL ||
311 //           this->glimage->getTypeId() != SoGLBigImage::getClassTypeId())) {
312 //        if (this->glimage) this->glimage->unref(state);
313 //        this->glimage = new SoGLBigImage();
314 //      }
315 //      else if (!needbig &&
316 //               (this->glimage == NULL ||
317 //                this->glimage->getTypeId() != SoGLImage::getClassTypeId())) {
318 //        if (this->glimage) this->glimage->unref(state);
319 //        this->glimage = new SoGLImage();
320 //      }
321     if (this->glimage) this->glimage->unref(state);
322     this->glimage = new SoGLImage();
323 
324     if (this->enableCompressedTexture.getValue()) {
325       this->glimage->setFlags(this->glimage->getFlags()|
326                               SoGLImage::COMPRESSED);
327     }
328 
329     if (bytes && size != SbVec3s(0,0,0)) {
330       this->glimage->setData(bytes, size, nc,
331                              translateWrap((Wrap)this->wrapS.getValue()),
332                              translateWrap((Wrap)this->wrapT.getValue()),
333                              translateWrap((Wrap)this->wrapR.getValue()),
334                              quality);
335       this->glimagevalid = TRUE;
336     }
337   }
338 
339   if (this->glimagevalid && quality > 0.0f) {
340     SoGLMultiTextureEnabledElement::enableTexture3(state, this, unit);
341   }
342   else {
343     SoGLMultiTextureEnabledElement::set(state, this, unit, FALSE);
344   }
345   SoGLMultiTextureImageElement::set(state, this, unit,
346                                     this->glimagevalid ? this->glimage : NULL,
347                                     (SoMultiTextureImageElement::Model) model.getValue(),
348                                     this->blendColor.getValue());
349 
350   if (this->isOverride() && unit == 0) {
351     SoTextureOverrideElement::setImageOverride(state, TRUE);
352   }
353 }
354 
355 // doc from parent
356 void
doAction(SoAction * action)357 SoTexture3::doAction(SoAction *action)
358 {
359   SoState *state = action->getState();
360 
361   int unit = SoTextureUnitElement::get(state);
362   if (SoTextureOverrideElement::getImageOverride(state) && unit == 0)
363     return;
364 
365   int nc;
366   SbVec3s size;
367   const unsigned char *bytes = this->images.getValue(size, nc);
368 
369   if (size != SbVec3s(0,0,0)) {
370     SoMultiTextureImageElement::set(state, this, unit,
371                                     size, nc, bytes,
372                                     (SoMultiTextureImageElement::Wrap)this->wrapT.getValue(),
373                                     (SoMultiTextureImageElement::Wrap)this->wrapS.getValue(),
374                                     (SoMultiTextureImageElement::Wrap)this->wrapR.getValue(),
375                                     (SoMultiTextureImageElement::Model) model.getValue(),
376                                     this->blendColor.getValue());
377   }
378   // if a filename has been set, but the file has not been loaded, supply
379   // a dummy texture image to make sure texture coordinates are generated.
380   else if (this->images.isDefault() &&
381            this->filenames.getNum()>0 &&
382            this->filenames[0].getLength()) {
383     static const unsigned char dummytex[] = {0xff,0xff,0xff,0xff,
384                                              0xff,0xff,0xff,0xff};
385     SoMultiTextureImageElement::set(state, this, unit,
386                                     SbVec3s(2,2,2), 1, dummytex,
387                                     (SoMultiTextureImageElement::Wrap)this->wrapT.getValue(),
388                                     (SoMultiTextureImageElement::Wrap)this->wrapS.getValue(),
389                                     (SoMultiTextureImageElement::Wrap)this->wrapR.getValue(),
390                                     (SoMultiTextureImageElement::Model) model.getValue(),
391                                     this->blendColor.getValue());
392   }
393   else {
394     SoMultiTextureImageElement::setDefault(state, this, unit);
395   }
396   if (this->isOverride() && unit == 0) {
397     SoTextureOverrideElement::setImageOverride(state, TRUE);
398   }
399 }
400 
401 // doc from parent
402 void
callback(SoCallbackAction * action)403 SoTexture3::callback(SoCallbackAction * action)
404 {
405   SoTexture3::doAction(action);
406 }
407 
408 /*!
409   Returns read status. 1 for success, 0 for failure.
410 */
411 int
getReadStatus(void)412 SoTexture3::getReadStatus(void)
413 {
414   return this->readstatus;
415 }
416 
417 /*!
418   Sets read status.
419   \sa getReadStatus()
420  */
421 void
setReadStatus(int s)422 SoTexture3::setReadStatus(int s)
423 {
424   this->readstatus = s;
425 }
426 
427 // Documented in superclass.
428 void
notify(SoNotList * l)429 SoTexture3::notify(SoNotList * l)
430 {
431   // Overridden to detect when fields change.
432 
433   SoField *f = l->getLastField();
434   if (f == &this->images) {
435     this->glimagevalid = FALSE;
436     this->filenames.setDefault(TRUE); // write image, not filename
437   }
438   else if (f == &this->wrapS || f == &this->wrapT || f == &this->wrapR) {
439     this->glimagevalid = FALSE;
440   }
441   inherited::notify(l);
442 }
443 
444 //
445 // Called from readInstance() or when user changes the
446 // filenames field. \e in is set if this function is called
447 // while reading a scene graph.
448 //
449 //FIXME: Recalc so all images have same w, h and nc (kintel 20011201)
450 //FIXME: Rescale depth to be n^2 ? This might not work very well though
451 //       if someone decides to add one layer at the time (kintel 20011201)
452 SbBool
loadFilenames(SoInput * in)453 SoTexture3::loadFilenames(SoInput * in)
454 {
455   SbBool retval = FALSE;
456   SbVec3s volumeSize(0,0,0);
457   int volumenc;
458   int numImages = this->filenames.getNum();
459   SbBool sizeError = FALSE;
460   int i;
461 
462   // Fail on empty filenames
463   for (i=0;i<numImages;i++) if (this->filenames[i].getLength()==0) break;
464 
465   if (i==numImages) { // All filenames valid
466     for (int n=0 ; n<numImages && !sizeError ; n++) {
467       SbString filename = this->filenames[n];
468       SbImage tmpimage;
469       const SbStringList &sl = SoInput::getDirectories();
470       if (tmpimage.readFile(filename, sl.getArrayPtr(), sl.getLength())) {
471         int nc;
472         SbVec3s size;
473         unsigned char *imgbytes = tmpimage.getValue(size, nc);
474         if (size[2]==0) size[2]=1;
475         if (this->images.isDefault()) { // First time => allocate memory
476           volumeSize.setValue(size[0],
477                               size[1],
478                               size[2]*numImages);
479           volumenc = nc;
480           this->images.setValue(volumeSize, nc, NULL);
481         }
482         else { // Verify size & components
483           if (size[0] != volumeSize[0] ||
484               size[1] != volumeSize[1] ||
485               //FIXME: always 1 or what? (kintel 20020110)
486               size[2] != (volumeSize[2]/numImages) ||
487               nc != volumenc) {
488             sizeError = TRUE;
489             retval = FALSE;
490 
491             SbString errstr;
492             errstr.sprintf("Texture file #%d (%s) has wrong size:"
493                            "Expected (%d,%d,%d,%d) got (%d,%d,%d,%d)\n",
494                            n, filename.getString(),
495                            volumeSize[0],volumeSize[1],volumeSize[2],
496                            volumenc,
497                            size[0],size[1],size[2],nc);
498             if (in) SoReadError::post(in, errstr.getString());
499             else SoDebugError::postWarning("SoTexture3::loadFilenames()",
500                                            errstr.getString());
501           }
502         }
503         if (!sizeError) {
504           // disable notification on images while setting data from the
505           // filenames as a notify will cause a filenames.setDefault(TRUE).
506           SbBool oldnotify = this->images.enableNotify(FALSE);
507           unsigned char *volbytes = this->images.startEditing(volumeSize,
508                                                               volumenc);
509           memcpy(volbytes+int(size[0])*int(size[1])*int(size[2])*nc*n,
510                  imgbytes, int(size[0])*int(size[1])*int(size[2])*nc);
511           this->images.finishEditing();
512           this->images.enableNotify(oldnotify);
513           this->glimagevalid = FALSE; // recreate GL images in next GLRender()
514           retval = TRUE;
515         }
516       }
517       else {
518         SbString errstr;
519         errstr.sprintf("Could not read texture file #%d: %s",
520                        n, filename.getString());
521         if (in) SoReadError::post(in, errstr.getString());
522         else SoDebugError::postWarning("SoTexture3::loadFilenames()",
523                                        errstr.getString());
524         retval = FALSE;
525       }
526     }
527   }
528   //FIXME: If sizeError, invalidate texture? (kintel 20011113)
529   this->images.setDefault(TRUE); // write filenames, not images
530   return retval;
531 }
532 
533 //
534 // called when \e filenames changes
535 //
536 void
filenameSensorCB(void * data,SoSensor *)537 SoTexture3::filenameSensorCB(void * data, SoSensor *)
538 {
539   SoTexture3 *thisp = (SoTexture3 *)data;
540 
541   thisp->setReadStatus(TRUE);
542   if ((thisp->filenames.getNum()<=0) ||
543       (thisp->filenames[0].getLength() && !thisp->loadFilenames())) {
544     thisp->setReadStatus(FALSE);
545   }
546 }
547