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