/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SoShape SoShape.h Inventor/nodes/SoShape.h \brief The SoShape class is the superclass for geometry shapes. \ingroup nodes The node types which have actual geometry to render inherits this class. For convenince, the SoShape class contains various common code used by the subclasses. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../misc/SoVBO.h" #include #include #include #include // COIN_OBSOLETED() // SoShape.cpp grew too big, so I had to move some code into new // files. pederb, 2001-07-18 #include "soshape_primdata.h" #include "soshape_trianglesort.h" #include "soshape_bigtexture.h" #include "soshape_bumprender.h" #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #ifdef HAVE_VRML97 #include #include #include #endif // HAVE_VRML97 #include #ifdef COIN_THREADSAFE #include #endif // COIN_THREADSAFE /*! \enum SoShape::TriangleShape \COININTERNAL */ /*! \fn void SoShape::computeBBox(SoAction * action, SbBox3f & box, SbVec3f & center) Implemented by SoShape subclasses to let the SoShape superclass know the exact size and weighted center point of the shape's bounding box. The bounding box and center point should be calculated and returned in the local coordinate system. The method implements action behavior for shape nodes for SoGetBoundingBoxAction. It is invoked from SoShape::getBoundingBox(). (Subclasses should \e not override SoNode::getBoundingBox().) The \a box parameter sent in is guaranteed to be an empty box, while \a center is undefined upon function entry. */ /*! \fn void SoShape::generatePrimitives(SoAction * action) The method implements action behavior for shape nodes for SoCallbackAction. It is invoked from SoShape::callback(). (Subclasses should \e not override SoNode::callback().) The subclass implementations uses the convenience methods SoShape::beginShape(), SoShape::shapeVertex(), and SoShape::endShape(), with SoDetail instances, to pass the primitives making up the shape back to the caller. */ // ************************************************************************* class SoShapeP { public: SoShapeP() { this->bboxcache = NULL; this->pvcache = NULL; this->bumprender = NULL; this->rendercnt = 0; this->flags = 0; } ~SoShapeP() { if (this->bboxcache) { this->bboxcache->unref(); } if (this->pvcache) { this->pvcache->unref(); } delete this->bumprender; } enum { RENDERCNT_BITS = 4, // bits needed to store rendercnt FLAG_BITS = 4, // bits needed to store flags VERTEXARRAY_WAITCNT = 4 // # frames to wait before trying to create // a SoPrimitiveVertexCache }; enum Flags { SHOULD_BBOX_CACHE = 0x1, NEED_SETUP_SHAPE_HINTS = 0x2, DISABLE_VERTEX_ARRAY_CACHE = 0x4, }; static void calibrateBBoxCache(void); static double bboxcachetimelimit; SoBoundingBoxCache * bboxcache; SoPrimitiveVertexCache * pvcache; soshape_bumprender * bumprender; uint32_t flags : FLAG_BITS; // stores the number of frames rendered with no node changes uint32_t rendercnt : RENDERCNT_BITS; #ifdef COIN_THREADSAFE SbMutex mutex; #endif // COIN_THREADSAFE // needed since some VRML97 nodes change the GL state inside the node void testSetupShapeHints(SoShape * shape) { #ifdef HAVE_VRML97 if ((this->flags & SoShapeP::NEED_SETUP_SHAPE_HINTS) == 0) { if (shape->isOfType(SoVRMLIndexedFaceSet::getClassTypeId()) || shape->isOfType(SoVRMLExtrusion::getClassTypeId()) || shape->isOfType(SoVRMLElevationGrid::getClassTypeId())) { this->flags |= SoShapeP::NEED_SETUP_SHAPE_HINTS; } } #endif // HAVE_VRML97 } void setupShapeHints(SoShape * shape, SoState * state) { #ifdef HAVE_VRML97 if (this->flags & SoShapeP::NEED_SETUP_SHAPE_HINTS) { SbBool ccw = ((SoSFBool*)(shape->getField("ccw")))->getValue(); SbBool solid = ((SoSFBool*)(shape->getField("solid")))->getValue(); SoGLShapeHintsElement::forceSend(state, ccw, solid, !solid); } #endif // HAVE_VRML97 } void lock(void) { #ifdef COIN_THREADSAFE this->mutex.lock(); #endif // COIN_THREADSAFE } void unlock(void) { #ifdef COIN_THREADSAFE this->mutex.unlock(); #endif // COIN_THREADSAFE } }; double SoShapeP::bboxcachetimelimit; #undef PRIVATE #define PRIVATE(_thisp_) ((_thisp_)->pimpl) // ************************************************************************* // code/structures to handle static and/or thread safe data enum SoShapeRenderMode { NORMAL, BIGTEXTURE, SORTED_TRIANGLES, PVCACHE }; typedef struct { soshape_primdata * primdata; SbList * bigtexturelist; SbList * bigtexturecontext; soshape_trianglesort * trianglesort; soshape_bigtexture * currentbigtexture; // used in generatePrimitives() callbacks to set correct material SoMaterialBundle * currentbundle; int rendermode; } soshape_staticdata; static soshape_bigtexture * soshape_get_bigtexture(soshape_staticdata * data, uint32_t context) { for (int i = 0; i < data->bigtexturecontext->getLength(); i++) { if ((*(data->bigtexturecontext))[i] == context) { return (*(data->bigtexturelist))[i]; } } soshape_bigtexture * newtex = new soshape_bigtexture; data->bigtexturelist->append(newtex); data->bigtexturecontext->append(context); return newtex; } static void soshape_construct_staticdata(void * closure) { soshape_staticdata * data = (soshape_staticdata*) closure; data->bigtexturelist = new SbList ; data->bigtexturecontext = new SbList ; data->primdata = new soshape_primdata(); data->trianglesort = new soshape_trianglesort(); data->rendermode = NORMAL; } static void soshape_destruct_staticdata(void * closure) { soshape_staticdata * data = (soshape_staticdata*) closure; for (int i = 0; i < data->bigtexturelist->getLength(); i++) { delete (*(data->bigtexturelist))[i]; } delete data->bigtexturelist; delete data->bigtexturecontext; delete data->primdata; delete data->trianglesort; } static SbStorage * soshape_staticstorage; static soshape_staticdata * soshape_get_staticdata(void) { return (soshape_staticdata*) soshape_staticstorage->get(); } // called by atexit static void soshape_cleanup(void) { delete soshape_staticstorage; } // ************************************************************************* SO_NODE_ABSTRACT_SOURCE(SoShape); // ************************************************************************* /*! Constructor. */ SoShape::SoShape(void) { SO_NODE_INTERNAL_CONSTRUCTOR(SoShape); PRIVATE(this) = new SoShapeP; } /*! Destructor. */ SoShape::~SoShape() { delete PRIVATE(this); } static int soshape_use_gl_vertex_arrays = 0; // Doc in parent. void SoShape::initClass(void) { SO_NODE_INTERNAL_INIT_ABSTRACT_CLASS(SoShape, SO_FROM_INVENTOR_1); soshape_staticstorage = new SbStorage(sizeof(soshape_staticdata), soshape_construct_staticdata, soshape_destruct_staticdata); const char * env = coin_getenv("COIN_USE_GL_VERTEX_ARRAYS"); if (env) { soshape_use_gl_vertex_arrays = atoi(env); } SoShapeP::calibrateBBoxCache(); coin_atexit((coin_atexit_f*) soshape_cleanup, CC_ATEXIT_NORMAL); } // Doc in parent. void SoShape::getBoundingBox(SoGetBoundingBoxAction * action) { SbBox3f box; SbVec3f center; this->getBBox(action, box, center); if (!box.isEmpty()) { action->extendBy(box); action->setCenter(center, TRUE); } } // Doc in parent. void SoShape::GLRender(SoGLRenderAction * action) { // if we get here, the shape do not have a render method and // generatePrimitives should therefore be used to render the // shape. This is probably painfully slow, so if you want speed, // implement the GLRender() method. pederb, 20000612 if (!this->shouldGLRender(action)) return; // test for SoVertexShape node and push data onto the state before // calling generatePrimitives(). This is needed for SoMaterialBundle // to work correctly. SoVertexProperty * vp = NULL; if (this->isOfType(SoVertexShape::getClassTypeId())) { vp = (SoVertexProperty*) ((SoVertexShape*)this)->vertexProperty.getValue(); } if (vp) { action->getState()->push(); vp->doAction(action); } SoMaterialBundle mb(action); mb.sendFirst(); soshape_get_staticdata()->currentbundle = &mb; // needed in the primitive callbacks this->generatePrimitives(action); if (vp) action->getState()->pop(); } // Doc in parent. void SoShape::callback(SoCallbackAction * action) { if (action->shouldGeneratePrimitives(this)) { soshape_staticdata * shapedata = soshape_get_staticdata(); shapedata->primdata->faceCounter = 0; this->generatePrimitives(action); } } // test bbox intersection static SbBool soshape_ray_intersect(SoRayPickAction * action, const SbBox3f & box) { if (box.isEmpty()) return FALSE; return action->intersect(box, TRUE); } /*! Calculates picked point based on primitives generated by subclasses. */ void SoShape::rayPick(SoRayPickAction * action) { if (this->shouldRayPick(action)) { this->computeObjectSpaceRay(action); if (!PRIVATE(this)->bboxcache || !PRIVATE(this)->bboxcache->isValid(action->getState()) || soshape_ray_intersect(action, PRIVATE(this)->bboxcache->getProjectedBox())) { this->generatePrimitives(action); } } } /*! A convenience function that returns the size of a \a boundingbox projected onto the screen. Useful for \c SCREEN_SPACE complexity geometry. */ void SoShape::getScreenSize(SoState * const state, const SbBox3f & boundingbox, SbVec2s & rectsize) { SbMatrix projmatrix; projmatrix = (SoModelMatrixElement::get(state) * SoViewingMatrixElement::get(state) * SoProjectionMatrixElement::get(state)); SbVec2s vpsize = SoViewportRegionElement::get(state).getViewportSizePixels(); SbVec3f bmin, bmax; boundingbox.getBounds(bmin, bmax); SbVec3f v; SbBox2f normbox; normbox.makeEmpty(); for (int i = 0; i < 8; i++) { v.setValue(i&1 ? bmin[0] : bmax[0], i&2 ? bmin[1] : bmax[1], i&4 ? bmin[2] : bmax[2]); projmatrix.multVecMatrix(v, v); normbox.extendBy(SbVec2f(v[0], v[1])); } float nx, ny; normbox.getSize(nx, ny); // restrict size of projection. It is often way off when object // intersects the near plane. We should probably do clipping against // the view volume do be 100% correct, but that would be too slow. // pederb, 2001-05-20 if (nx > 10.0f) nx = 10.0f; if (ny > 10.0f) ny = 10.0f; rectsize[0] = (short) SbMin(32767.0f, float(vpsize[0])*0.5f*nx); rectsize[1] = (short) SbMin(32767.0f, float(vpsize[1])*0.5f*ny); } /*! Returns the complexity value to be used by subclasses. Considers complexity type. For \c OBJECT_SPACE complexity this will be a number between 0 and 1. For \c SCREEN_SPACE complexity it is a number from 0 and up. */ float SoShape::getComplexityValue(SoAction * action) { SoState * state = action->getState(); switch (SoComplexityTypeElement::get(state)) { case SoComplexityTypeElement::SCREEN_SPACE: { SbBox3f box; SbVec3f center; this->getBBox(action, box, center); SbVec2s size; SoShape::getScreenSize(state, box, size); // FIXME: probably needs calibration. #if 1 // testing new complexity code // The cast within the sqrt() is done to avoid ambigouity error // from HPUX aCC, as sqrt() can be either "long double sqrt(long // double)" or "float sqrt(float)". mortene. return float(sqrt((float)SbMax(size[0], size[1]))) * 0.4f * SoComplexityElement::get(state); #else // first version float numPixels = float(size[0])* float(size[1]); return numPixels * 0.0001f * SoComplexityElement::get(state); #endif } case SoComplexityTypeElement::OBJECT_SPACE: return SoComplexityElement::get(state); case SoComplexityTypeElement::BOUNDING_BOX: // return default value. We might get here when generating // primitives, not when rendering. return 0.5f; default: assert(0 && "unknown complexity type"); return 0.5f; } } /*! \COININTERNAL */ SbBool SoShape::shouldGLRender(SoGLRenderAction * action) { SoState * state = action->getState(); const SoShapeStyleElement * shapestyle = SoShapeStyleElement::get(state); unsigned int shapestyleflags = shapestyle->getFlags(); if (shapestyleflags & SoShapeStyleElement::INVISIBLE) return FALSE; if (PRIVATE(this)->bboxcache && !state->isCacheOpen() && !SoCullElement::completelyInside(state)) { if (PRIVATE(this)->bboxcache->isValid(state)) { if (SoCullElement::cullTest(state, PRIVATE(this)->bboxcache->getProjectedBox())) { return FALSE; } } } SbBool transparent = (shapestyleflags & (SoShapeStyleElement::TRANSP_TEXTURE| SoShapeStyleElement::TRANSP_MATERIAL)) != 0; if (action->handleTransparency(transparent)) return FALSE; if (shapestyleflags & SoShapeStyleElement::BBOXCMPLX) { this->GLRenderBoundingBox(action); return FALSE; } // test if we should sort triangles before rendering if (transparent && (shapestyleflags & SoShapeStyleElement::TRANSP_SORTED_TRIANGLES)) { soshape_staticdata * shapedata = soshape_get_staticdata(); #if 0 // old code // do this before generating triangles to get correct // material for lines and point (only triangles are sorted). SoMaterialBundle mb(action); mb.sendFirst(); shapedata->currentbundle = &mb; shapedata->trianglesort->beginShape(state); // when setting this flag, the triangles will be sent to the // trianglesort handler in invokeTriangleCallbacks(). shapedata->rendermode = SORTED_TRIANGLES; this->generatePrimitives(action); shapedata->rendermode = NORMAL; shapedata->trianglesort->endShape(state, mb); // this will render the triangles #else // new code that used the pvcache to render // lock mutex since pvcache is shared among all threads PRIVATE(this)->lock(); if (PRIVATE(this)->pvcache == NULL || !PRIVATE(this)->pvcache->isValid(state)) { if (PRIVATE(this)->pvcache) { PRIVATE(this)->pvcache->unref(); } SbBool storedinvalid = SoCacheElement::setInvalid(FALSE); // must push state to make cache dependencies work state->push(); PRIVATE(this)->pvcache = new SoPrimitiveVertexCache(state); PRIVATE(this)->pvcache->ref(); SoCacheElement::set(state, PRIVATE(this)->pvcache); shapedata->rendermode = PVCACHE; this->generatePrimitives(action); shapedata->rendermode = NORMAL; // this _must_ be called after creating the pvcache state->pop(); SoCacheElement::setInvalid(storedinvalid); PRIVATE(this)->pvcache->fit(); PRIVATE(this)->testSetupShapeHints(this); } int arrays = SoPrimitiveVertexCache::NORMAL|SoPrimitiveVertexCache::COLOR; SoGLTextureImageElement::Model model; SbColor blendcolor; SoGLImage * glimage = SoGLTextureImageElement::get(state, model, blendcolor); if (glimage) arrays |= SoPrimitiveVertexCache::TEXCOORD; SoMaterialBundle mb(action); mb.sendFirst(); PRIVATE(this)->setupShapeHints(this, state); PRIVATE(this)->pvcache->depthSortTriangles(state); PRIVATE(this)->pvcache->renderTriangles(state, arrays); if (PRIVATE(this)->pvcache->getNumLineIndices() || PRIVATE(this)->pvcache->getNumPointIndices()) { const SoNormalElement * nelem = SoNormalElement::getInstance(state); if (nelem->getNum() == 0) { glPushAttrib(GL_LIGHTING_BIT); glDisable(GL_LIGHTING); arrays &= SoPrimitiveVertexCache::NORMAL; } PRIVATE(this)->pvcache->renderLines(state, arrays); PRIVATE(this)->pvcache->renderPoints(state, arrays); if (nelem->getNum() == 0) { glPopAttrib(); } } PRIVATE(this)->unlock(); #endif // end of new sorted triangles transparency rendering return FALSE; // tell shape _not_ to render } if (shapestyleflags & SoShapeStyleElement::BIGIMAGE) { SoGLTextureImageElement::Model model; SbColor blendcolor; SoGLImage * glimage = SoGLTextureImageElement::get(state, model, blendcolor); if (glimage && glimage->isOfType(SoGLBigImage::getClassTypeId()) && SoGLTextureEnabledElement::get(state)) { // don't attempt to cache bigimage shapes if (state->isCacheOpen()) { SoCacheElement::invalidate(state); } SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DONT_AUTO_CACHE); soshape_staticdata * shapedata = soshape_get_staticdata(); // do this before generating triangles to get correct // material for lines and point (only triangles are handled for now). SoMaterialBundle mb(action); mb.sendFirst(); shapedata->currentbundle = &mb; SoGLBigImage * big = (SoGLBigImage*) glimage; shapedata->rendermode = BIGTEXTURE; soshape_bigtexture * bigtex = soshape_get_bigtexture(shapedata, action->getCacheContext()); shapedata->currentbigtexture = bigtex; bigtex->beginShape(big, SoTextureQualityElement::get(state)); this->generatePrimitives(action); // endShape() returns whether more/less detailed textures need to be // fetched. We force a redraw if this is needed. if (bigtex->endShape(state, this, mb) == FALSE) { action->getCurPath()->getHead()->touch(); } shapedata->rendermode = NORMAL; return FALSE; } } const cc_glglue * glue = sogl_glue_instance(state); if (shapestyleflags & SoShapeStyleElement::BUMPMAP) { const SoNodeList & lights = SoLightElement::getLights(state); if (lights.getLength()) { soshape_staticdata * shapedata = soshape_get_staticdata(); // lock mutex since bumprender and pvcache is shared among all threads PRIVATE(this)->lock(); if (PRIVATE(this)->bumprender == NULL) { PRIVATE(this)->bumprender = new soshape_bumprender; } if (PRIVATE(this)->pvcache == NULL || !PRIVATE(this)->pvcache->isValid(state)) { if (PRIVATE(this)->pvcache) { PRIVATE(this)->pvcache->unref(); } SbBool storedinvalid = SoCacheElement::setInvalid(FALSE); // must push state to make cache dependencies work state->push(); PRIVATE(this)->pvcache = new SoPrimitiveVertexCache(state); PRIVATE(this)->pvcache->ref(); SoCacheElement::set(state, PRIVATE(this)->pvcache); shapedata->rendermode = PVCACHE; this->generatePrimitives(action); shapedata->rendermode = NORMAL; // this _must_ be called after creating the pvcache PRIVATE(this)->bumprender->calcTangentSpace(PRIVATE(this)->pvcache); state->pop(); SoCacheElement::setInvalid(storedinvalid); PRIVATE(this)->pvcache->fit(); PRIVATE(this)->testSetupShapeHints(this); } if (PRIVATE(this)->pvcache->getNumIndices() == 0) { PRIVATE(this)->unlock(); return TRUE; } SoGLLazyElement::getInstance(state)->send(state, SoLazyElement::ALL_MASK); glPushAttrib(GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_LEQUAL); glDisable(GL_LIGHTING); glColor3f(1.0f, 1.0f, 1.0f); PRIVATE(this)->setupShapeHints(this, state); const int numlights = lights.getLength(); for (int i = 0; i < numlights; i++) { // fetch matrix that convert the light from its object space // to the OpenGL world space SbMatrix lm = SoLightElement::getMatrix(state, i); // convert light back to this objects' object space SbMatrix m = SoModelMatrixElement::get(state) * SoViewingMatrixElement::get(state); m = m.inverse(); m.multLeft(lm); // bumprender is shared among all threads, so the mutex needs to // be locked when we get here since some internal arrays are // used while rendering PRIVATE(this)->bumprender->renderBump(state, PRIVATE(this)->pvcache, (SoLight*) lights[i], m); if (i == 0) glEnable(GL_BLEND); if (i == numlights-1) { glBlendFunc(GL_DST_COLOR, GL_ZERO); } else if (i == 0) { glBlendFunc(GL_ONE, GL_ONE); } } SoGLLazyElement::getInstance(state)->reset(state, SoLazyElement::DIFFUSE_MASK | SoLazyElement::GLIMAGE_MASK); SoMaterialBundle mb(action); mb.sendFirst(); PRIVATE(this)->setupShapeHints(this, state); PRIVATE(this)->bumprender->renderNormal(state, PRIVATE(this)->pvcache); const SbColor spec = SoLazyElement::getSpecular(state); if (spec[0] != 0 || spec[1] != 0 || spec[2] != 0) { // Is the spec. color black? // Can the hardware do specular bump maps? if (glue->has_arb_fragment_program && glue->has_arb_vertex_program) { SoGLLazyElement::getInstance(state)->reset(state, SoLazyElement::DIFFUSE_MASK | SoLazyElement::GLIMAGE_MASK); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); for (int i = 0; i < numlights; i++) { SbMatrix lm = SoLightElement::getMatrix(state, i); SbMatrix m = SoModelMatrixElement::get(state) * SoViewingMatrixElement::get(state); m = m.inverse(); m.multLeft(lm); PRIVATE(this)->bumprender->renderBumpSpecular(state, PRIVATE(this)->pvcache, (SoLight*) lights[i], m); } } } PRIVATE(this)->unlock(); glPopAttrib(); glDisable(GL_BLEND); // FIXME: temporary for FPS-counter SoGLLazyElement::getInstance(state)->reset(state, SoLazyElement::LIGHT_MODEL_MASK| SoLazyElement::BLENDING_MASK); return FALSE; } } // wait some frames before trying to create the vertex array // cache. This will give Coin a chance to create display list caches // for parts of the scene graph. if (((shapestyleflags & SoShapeStyleElement::VERTEXARRAY) || soshape_use_gl_vertex_arrays) && ((PRIVATE(this)->flags & SoShapeP::DISABLE_VERTEX_ARRAY_CACHE) == 0) && cc_glglue_has_vertex_array(glue) && (PRIVATE(this)->rendercnt >= SoShapeP::VERTEXARRAY_WAITCNT) && !SoCacheElement::anyOpen(state)) { // Only create cache for built in Coin shapes, as it is often // tempting for developers writing Coin extension nodes to not // bother with implementing a proper generatePrimitives() // function. // // Can be overridden by setting COIN_USE_GL_VERTEX_ARRAYS=-1. // // FIXME: we should make it possible to have more fine-grained // control of this, to somehow flag *particular* extension nodes // as ok for vertex array rendering. 20040220 mortene. if (!this->isBuiltIn && (soshape_use_gl_vertex_arrays != -1)) return TRUE; soshape_staticdata * shapedata = soshape_get_staticdata(); // lock mutex since pvcache is shared among all threads PRIVATE(this)->lock(); if (PRIVATE(this)->pvcache == NULL || !PRIVATE(this)->pvcache->isValid(state)) { if (PRIVATE(this)->pvcache) { // invalid cache. Don't try to create it again PRIVATE(this)->flags |= SoShapeP::DISABLE_VERTEX_ARRAY_CACHE; PRIVATE(this)->pvcache->unref(); PRIVATE(this)->pvcache = NULL; PRIVATE(this)->unlock(); // let shape render return TRUE; } // for debugging // fprintf(stderr,"creating pvcache: %p\n", this); SbBool storedinvalid = SoCacheElement::setInvalid(FALSE); // must push state to make cache dependencies work state->push(); PRIVATE(this)->pvcache = new SoPrimitiveVertexCache(state); PRIVATE(this)->pvcache->ref(); SoCacheElement::set(state, PRIVATE(this)->pvcache); shapedata->rendermode = PVCACHE; this->generatePrimitives(action); shapedata->rendermode = NORMAL; // this _must_ be called after creating the pvcache state->pop(); SoCacheElement::setInvalid(storedinvalid); PRIVATE(this)->pvcache->fit(); PRIVATE(this)->testSetupShapeHints(this); } if ((PRIVATE(this)->pvcache->getNumIndices() < 100) && (PRIVATE(this)->pvcache->getNumLineIndices() < 100) && (PRIVATE(this)->pvcache->getNumPointIndices() < 100)) { PRIVATE(this)->pvcache->unref(); PRIVATE(this)->pvcache = NULL; // don't try to create this cache again PRIVATE(this)->flags |= SoShapeP::DISABLE_VERTEX_ARRAY_CACHE; PRIVATE(this)->unlock(); // let shape render return TRUE; } PRIVATE(this)->unlock(); SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DONT_AUTO_CACHE); int arrays = SoPrimitiveVertexCache::NORMAL|SoPrimitiveVertexCache::COLOR; SoGLTextureImageElement::Model model; SbColor blendcolor; SoGLImage * glimage = SoGLTextureImageElement::get(state, model, blendcolor); if (glimage) arrays |= SoPrimitiveVertexCache::TEXCOORD; SoMaterialBundle mb(action); mb.sendFirst(); PRIVATE(this)->setupShapeHints(this, state); PRIVATE(this)->pvcache->renderTriangles(state, arrays); if (PRIVATE(this)->pvcache->getNumLineIndices() || PRIVATE(this)->pvcache->getNumPointIndices()) { const SoNormalElement * nelem = SoNormalElement::getInstance(state); if (nelem->getNum() == 0) { glPushAttrib(GL_LIGHTING_BIT); glDisable(GL_LIGHTING); arrays &= SoPrimitiveVertexCache::NORMAL; } PRIVATE(this)->pvcache->renderLines(state, arrays); PRIVATE(this)->pvcache->renderPoints(state, arrays); if (nelem->getNum() == 0) { glPopAttrib(); } } // we have rendered, return FALSE return FALSE; } #if COIN_DEBUG && 0 // enable this to test generatePrimitives() rendering SoMaterialBundle mb(action); mb.sendFirst(); soshape_get_staticdata()->currentbundle = &mb; // needed in the primitive callbacks this->generatePrimitives(action); return FALSE; #else // generatePrimitives() rendering if (PRIVATE(this)->rendercnt < ((1<rendercnt++; } return TRUE; // let the shape node render the geometry using OpenGL #endif // ! generatePrimitives() rendering } /*! \COININTERNAL */ SbBool SoShape::shouldRayPick(SoRayPickAction * const action) { switch (SoPickStyleElement::get(action->getState())) { case SoPickStyleElement::SHAPE: return TRUE; case SoPickStyleElement::BOUNDING_BOX: this->rayPickBoundingBox(action); return FALSE; case SoPickStyleElement::UNPICKABLE: return FALSE; default: assert(0 && "unknown pick style"); return TRUE; } } /*! \COININTERNAL */ void SoShape::beginSolidShape(SoGLRenderAction * action) { SoState * state = action->getState(); state->push(); SoShapeHintsElement::set(state, SoShapeHintsElement::COUNTERCLOCKWISE, SoShapeHintsElement::SOLID, SoShapeHintsElement::FACE_TYPE_AS_IS); } /*! \COININTERNAL */ void SoShape::endSolidShape(SoGLRenderAction * action) { action->getState()->pop(); } /*! \COININTERNAL */ void SoShape::computeObjectSpaceRay(SoRayPickAction * const action) { action->setObjectSpace(); } /*! \COININTERNAL */ void SoShape::computeObjectSpaceRay(SoRayPickAction * const action, const SbMatrix & matrix) { action->setObjectSpace(matrix); } /*! Will create triangle detail for a SoPickedPoint. This method will only be called internally, when generatePrimitives() is used for picking (SoShape::rayPick() is not overridden). This method returns \c NULL in Open Inventor, and subclasses will need to override this method to create details for a SoPickedPoint. This is not necessary with Coin. Of course, if you choose to override it, it will work in the same way as Open Inventor. For this to work, you must supply a face or line detail when generating primitives. If you supply \c NULL for the detail argument in SoShape::beginShape(), you'll have to override this method. */ SoDetail * SoShape::createTriangleDetail(SoRayPickAction * action, const SoPrimitiveVertex * /*v1*/, const SoPrimitiveVertex * /*v2*/, const SoPrimitiveVertex * /*v3*/, SoPickedPoint * pp) { soshape_staticdata * shapedata = soshape_get_staticdata(); if (shapedata->primdata->faceDetail) { return shapedata->primdata->createPickDetail(); } // don't warn here. SoDetail instances are optional for extension nodes. #if COIN_DEBUG && 0 SoDebugError::postInfo("SoShape::createTriangleDetail", "Unable to create triangle detail."); #endif // COIN_DEBUG return NULL; } /*! Will create line detail for a SoPickedPoint. This method will only be called internally, when generatePrimitives() is used for picking (SoShape::rayPick() is not overridden). This method returns \c NULL in Open Inventor, and subclasses will need to override this method to create details for a SoPickedPoint. This is not necessary with Coin. Of course, if you choose to override it, it will work in the same way as Open Inventor. For this to work, you must supply a face or line detail when generating primitives. If you supply \c NULL for the detail argument in SoShape::beginShape(), you'll have to override this method. */ SoDetail * SoShape::createLineSegmentDetail(SoRayPickAction * action, const SoPrimitiveVertex * /* v1 */, const SoPrimitiveVertex * /* v2 */, SoPickedPoint * pp) { soshape_staticdata * shapedata = soshape_get_staticdata(); if (shapedata->primdata->lineDetail) { return shapedata->primdata->createPickDetail(); } // don't warn here. SoDetail instances are optional for extension nodes. #if COIN_DEBUG && 0 SoDebugError::postInfo("SoShape::createLineSegmentDetail", "Unable to create line segment detail."); #endif // COIN_DEBUG return NULL; } /*! Will create point detail for a SoPickedPoint. This method will only be called internally, when generatePrimitives() is used for picking (SoShape::rayPick() is not overridden). This method returns \c NULL in Open Inventor, and subclasses will need to override this method to create details for a SoPickedPoint. This is not necessary with Coin. Of course, if you choose to override it, it will work in the same way as Open Inventor. For this to work, you must supply a point detail in the SoPrimitiveVertex in generatePrimitives(). */ SoDetail * SoShape::createPointDetail(SoRayPickAction * /* action */, const SoPrimitiveVertex * v, SoPickedPoint * /* pp */) { if (v->getDetail()) return v->getDetail()->copy(); return NULL; } /*! \COININTERNAL */ void SoShape::invokeTriangleCallbacks(SoAction * const action, const SoPrimitiveVertex * const v1, const SoPrimitiveVertex * const v2, const SoPrimitiveVertex * const v3) { if (action->getTypeId().isDerivedFrom(SoRayPickAction::getClassTypeId())) { SoRayPickAction * ra = (SoRayPickAction *) action; SbVec3f intersection; SbVec3f barycentric; SbBool front; if (ra->intersect(v1->getPoint(), v2->getPoint(), v3->getPoint(), intersection, barycentric, front)) { if (ra->isBetweenPlanes(intersection)) { SoPickedPoint * pp = ra->addIntersection(intersection); if (pp) { pp->setDetail(this->createTriangleDetail(ra, v1, v2, v3, pp), this); // calculate normal at picked point SbVec3f n = v1->getNormal() * barycentric[0] + v2->getNormal() * barycentric[1] + v3->getNormal() * barycentric[2]; n.normalize(); pp->setObjectNormal(n); // calculate texture coordinate at picked point SbVec4f tc = v1->getTextureCoords() * barycentric[0] + v2->getTextureCoords() * barycentric[1] + v3->getTextureCoords() * barycentric[2]; pp->setObjectTextureCoords(tc); // material index need to be approximated, since there is no // way to average material indices :( This makes it // impossible to fully support color per vertex. An // extension to the OIV API would perhaps be a good idea // here? Maybe calculate the rgba value for diffuse and // transparency and set it in SoPickedPoint? float maxval = barycentric[0]; const SoPrimitiveVertex * maxv = v1; if (barycentric[1] > maxval) { maxv = v2; maxval = barycentric[1]; } if (barycentric[2] > maxval) { maxv = v3; } pp->setMaterialIndex(maxv->getMaterialIndex()); } } } } else if (action->getTypeId().isDerivedFrom(SoCallbackAction::getClassTypeId())) { SoCallbackAction * ca = (SoCallbackAction *) action; ca->invokeTriangleCallbacks(this, v1, v2, v3); } else if (action->getTypeId().isDerivedFrom(SoGetPrimitiveCountAction::getClassTypeId())) { SoGetPrimitiveCountAction * ga = (SoGetPrimitiveCountAction *) action; ga->incNumTriangles(); } else if (action->getTypeId().isDerivedFrom(SoGLRenderAction::getClassTypeId())) { soshape_staticdata * shapedata = soshape_get_staticdata(); switch (shapedata->rendermode) { case SORTED_TRIANGLES: shapedata->trianglesort->triangle(action->getState(), v1, v2, v3); break; case BIGTEXTURE: shapedata->currentbigtexture->triangle(action->getState(), v1, v2, v3); break; case PVCACHE: { int pdidx[3]; pdidx[0] = shapedata->primdata->getPointDetailIndex(v1); pdidx[1] = shapedata->primdata->getPointDetailIndex(v2); pdidx[2] = shapedata->primdata->getPointDetailIndex(v3); PRIVATE(this)->pvcache->addTriangle(v1, v2, v3, pdidx); } break; default: glBegin(GL_TRIANGLES); glTexCoord4fv(v1->getTextureCoords().getValue()); glNormal3fv(v1->getNormal().getValue()); shapedata->currentbundle->send(v1->getMaterialIndex(), TRUE); glVertex3fv(v1->getPoint().getValue()); glTexCoord4fv(v2->getTextureCoords().getValue()); glNormal3fv(v2->getNormal().getValue()); shapedata->currentbundle->send(v2->getMaterialIndex(), TRUE); glVertex3fv(v2->getPoint().getValue()); glTexCoord4fv(v3->getTextureCoords().getValue()); glNormal3fv(v3->getNormal().getValue()); shapedata->currentbundle->send(v3->getMaterialIndex(), TRUE); glVertex3fv(v3->getPoint().getValue()); glEnd(); break; } } } /*! \COININTERNAL */ void SoShape::invokeLineSegmentCallbacks(SoAction * const action, const SoPrimitiveVertex * const v1, const SoPrimitiveVertex * const v2) { if (action->getTypeId().isDerivedFrom(SoRayPickAction::getClassTypeId())) { SoRayPickAction * ra = (SoRayPickAction *) action; SbVec3f intersection; if (ra->intersect(v1->getPoint(), v2->getPoint(), intersection)) { if (ra->isBetweenPlanes(intersection)) { SoPickedPoint * pp = ra->addIntersection(intersection); if (pp) { pp->setDetail(this->createLineSegmentDetail(ra, v1, v2, pp), this); float total = (v2->getPoint()-v1->getPoint()).length(); float len1 = 1.0f; float len2 = 0.0f; if (total >= 0.0f) { len1 = (intersection-v1->getPoint()).length(); len2 = (intersection-v2->getPoint()).length(); len1 /= total; len2 /= total; } SbVec3f n = v1->getNormal() * len1 + v2->getNormal() * len2; n.normalize(); pp->setObjectNormal(n); SbVec4f tc = v1->getTextureCoords() * len1 + v2->getTextureCoords() * len2; pp->setObjectTextureCoords(tc); pp->setMaterialIndex(len1 >= len2 ? v1->getMaterialIndex() : v2->getMaterialIndex()); } } } } else if (action->getTypeId().isDerivedFrom(SoCallbackAction::getClassTypeId())) { SoCallbackAction * ca = (SoCallbackAction *) action; ca->invokeLineSegmentCallbacks(this, v1, v2); } else if (action->getTypeId().isDerivedFrom(SoGetPrimitiveCountAction::getClassTypeId())) { SoGetPrimitiveCountAction * ga = (SoGetPrimitiveCountAction *) action; ga->incNumLines(); } else if (action->getTypeId().isDerivedFrom(SoGLRenderAction::getClassTypeId())) { soshape_staticdata * shapedata = soshape_get_staticdata(); switch (shapedata->rendermode) { case PVCACHE: PRIVATE(this)->pvcache->addLine(v1, v2); break; default: glBegin(GL_LINES); glTexCoord4fv(v1->getTextureCoords().getValue()); glNormal3fv(v1->getNormal().getValue()); shapedata->currentbundle->send(v1->getMaterialIndex(), TRUE); glVertex3fv(v1->getPoint().getValue()); glTexCoord4fv(v2->getTextureCoords().getValue()); glNormal3fv(v2->getNormal().getValue()); shapedata->currentbundle->send(v2->getMaterialIndex(), TRUE); glVertex3fv(v2->getPoint().getValue()); glEnd(); break; } } } /*! \COININTERNAL */ void SoShape::invokePointCallbacks(SoAction * const action, const SoPrimitiveVertex * const v) { if (action->getTypeId().isDerivedFrom(SoRayPickAction::getClassTypeId())) { SoRayPickAction * ra = (SoRayPickAction *) action; SbVec3f intersection = v->getPoint(); if (ra->intersect(intersection)) { if (ra->isBetweenPlanes(intersection)) { SoPickedPoint * pp = ra->addIntersection(intersection); if (pp) { pp->setDetail(this->createPointDetail(ra, v, pp), this); pp->setObjectNormal(v->getNormal()); pp->setObjectTextureCoords(v->getTextureCoords()); pp->setMaterialIndex(v->getMaterialIndex()); } } } } else if (action->getTypeId().isDerivedFrom(SoCallbackAction::getClassTypeId())) { SoCallbackAction * ca = (SoCallbackAction *) action; ca->invokePointCallbacks(this, v); } else if (action->getTypeId().isDerivedFrom(SoGetPrimitiveCountAction::getClassTypeId())) { SoGetPrimitiveCountAction * ga = (SoGetPrimitiveCountAction *) action; ga->incNumPoints(); } else if (action->getTypeId().isDerivedFrom(SoGLRenderAction::getClassTypeId())) { soshape_staticdata * shapedata = soshape_get_staticdata(); switch (shapedata->rendermode) { case PVCACHE: PRIVATE(this)->pvcache->addPoint(v); break; default: glBegin(GL_POINTS); glTexCoord4fv(v->getTextureCoords().getValue()); glNormal3fv(v->getNormal().getValue()); shapedata->currentbundle->send(v->getMaterialIndex(), TRUE); glVertex3fv(v->getPoint().getValue()); glEnd(); break; } } } /*! This method is used to generate primitives for a shape. It's typically called from a node's generatePrimitives() method. If you have your own shape and want to write a generatePrimitives() method for that shape, it's probably a good idea to take a peek in the generatePrimitives() method for a similar shape in Coin. generatePrimitives() can contain several beginShape()/endShape() sequences. shapeVertex() is used for each vertex between beginShape() and endShape(). For instance, to generate primitives for a triangle you'd do something like this: \verbatim SoPrimitiveVertex vertex; this->beginShape(action, SoShape::POLYGON); vertex.setPoint(SbVec3f(0.0f, 0.0f, 0.0f)); this->shapeVertex(&vertex); vertex.setPoint(SbVec3f(1.0f, 0.0f, 0.0f)); this->shapeVertex(&vertex); vertex.setPoint(SbVec3f(1.0f, 1.0f, 0.0f)); this->shapeVertex(&vertex); this->endShape(); \endverbatim Note that the SoPrimitiveVertex instance can simply be placed on the stack and not allocated. SoShape will copy the needed information when you call shapeVertex(). Before calling shapeVertex(), you can set extra information for the SoPrimitiveVertex, including normal, material index, and texture coordinates. This method is slightly different from its counterpart from the original Open Inventor library, as this method has an SoDetail as the last argument, and not an SoFaceDetail. This is because we accept more TriangleShape types, and the detail might be a SoFaceDetail or a SoLineDetail. There is no use sending in a SoPointDetail, as nothing will be done with it. */ void SoShape::beginShape(SoAction * const action, const TriangleShape shapetype, SoDetail * const detail) { soshape_get_staticdata()->primdata->beginShape(this, action, shapetype, detail); } /*! This method is used while generating primitives for a shape. See beginShape() for more details. \sa beginShape(), endShape() */ void SoShape::shapeVertex(const SoPrimitiveVertex * const v) { soshape_get_staticdata()->primdata->shapeVertex(v); } /*! This method is used while generating primitives for a shape. See beginShape() for more details. \sa beginShape(), shapeVertex() */ void SoShape::endShape(void) { soshape_get_staticdata()->primdata->endShape(); } /*! Convenience function which sets up an SoPrimitiveVertex, and sends it using the SoShape::shapeVertex() function. 2D version */ void SoShape::generateVertex(SoPrimitiveVertex * const pv, const SbVec3f & point, const SbBool usetexfunc, const SoTextureCoordinateElement * const tce, const float s, const float t, const SbVec3f & normal) { this->generateVertex(pv, point, usetexfunc, tce, s, t, 0.0f, normal); } /*! Convenience function which sets up an SoPrimitiveVertex, and sends it using the SoShape::shapeVertex() function. 3D version. \COIN_FUNCTION_EXTENSION \since Coin 2.0 */ void SoShape::generateVertex(SoPrimitiveVertex * const pv, const SbVec3f & point, const SbBool usetexfunc, const SoTextureCoordinateElement * const tce, const float s, const float t, const float r, const SbVec3f & normal) { SbVec4f texCoord; if (usetexfunc) texCoord = tce->get(point, normal); else texCoord.setValue(s, t, r, 1.0f); pv->setPoint(point); pv->setNormal(normal); pv->setTextureCoords(texCoord); shapeVertex(pv); } // Doc in superclass. SbBool SoShape::affectsState(void) const { // Overridden from default setting in SoNode to return FALSE instead // of TRUE, as we know for certain that no node classes derived from // SoShape will affect the rendering state. return FALSE; } // Doc in superclass. void SoShape::getPrimitiveCount(SoGetPrimitiveCountAction * action) { if (this->shouldPrimitiveCount(action)) this->generatePrimitives(action); } /*! Not implemented in Coin. Should probably have been private in TGS Inventor API. */ float SoShape::getDecimatedComplexity(SoState * state, float complexity) { COIN_OBSOLETED(); return 1.0f * complexity; } /*! Render a bounding box. */ void SoShape::GLRenderBoundingBox(SoGLRenderAction * action) { SbBox3f box; SbVec3f center; this->getBBox(action, box, center); center = (box.getMin() + box.getMax()) * 0.5f; SbVec3f size = box.getMax() - box.getMin(); SoMaterialBundle mb(action); mb.sendFirst(); { SoGLShapeHintsElement::forceSend(action->getState(), TRUE, FALSE, FALSE); } glPushMatrix(); glTranslatef(center[0], center[1], center[2]); sogl_render_cube(size[0], size[1], size[2], &mb, SOGL_NEED_NORMALS | SOGL_NEED_TEXCOORDS, NULL); glPopMatrix(); } /*! \COININTERNAL */ SbBool SoShape::shouldPrimitiveCount(SoGetPrimitiveCountAction * action) { return TRUE; // FIXME: what to do here? pederb 1999-11-25 } // // used when pickStyle == BOUNDING_BOX // void SoShape::rayPickBoundingBox(SoRayPickAction * action) { SbBox3f box; SbVec3f center; this->getBBox(action, box, center); if (box.isEmpty()) return; this->computeObjectSpaceRay(action); SbVec3f isect; if (action->intersect(box, isect, FALSE)) { if (action->isBetweenPlanes(isect)) { action->addIntersection(isect); } } } // Doc from superclass. void SoShape::notify(SoNotList * nl) { inherited::notify(nl); PRIVATE(this)->lock(); if (PRIVATE(this)->bboxcache) { PRIVATE(this)->bboxcache->invalidate(); } if (PRIVATE(this)->pvcache) { PRIVATE(this)->pvcache->invalidate(); } PRIVATE(this)->flags &= ~SoShapeP::SHOULD_BBOX_CACHE; PRIVATE(this)->rendercnt = 0; PRIVATE(this)->unlock(); } /*! Return the bounding box cache for this shape. It might return NULL if no bounding box cache has been created. If not NULL, the caller must check if the cache is valid before using it. This can be done using SoCache::isValid(). \COIN_FUNCTION_EXTENSION \since Coin 2.0 */ const SoBoundingBoxCache * SoShape::getBoundingBoxCache(void) const { return PRIVATE(this)->bboxcache; } // return the bbox for this shape, using the cache if valid, // calculating it if not. void SoShape::getBBox(SoAction * action, SbBox3f & box, SbVec3f & center) { SoState * state = action->getState(); SbBool isvalid = PRIVATE(this)->bboxcache && PRIVATE(this)->bboxcache->isValid(state); if (isvalid) { box = PRIVATE(this)->bboxcache->getProjectedBox(); // we know center will be set, so just fetch it from the cache center = PRIVATE(this)->bboxcache->getCenter(); } if (isvalid) { return; } // destroy the old cache if we have one if (PRIVATE(this)->bboxcache) { PRIVATE(this)->lock(); PRIVATE(this)->bboxcache->unref(); PRIVATE(this)->bboxcache = NULL; PRIVATE(this)->unlock(); // don't create bbox caches for shapes that change PRIVATE(this)->flags &= ~SoShapeP::SHOULD_BBOX_CACHE; } SbBool shouldcache = (PRIVATE(this)->flags & SoShapeP::SHOULD_BBOX_CACHE) != 0; SbBool storedinvalid = FALSE; if (shouldcache) { // must push state to make cache dependencies work state->push(); storedinvalid = SoCacheElement::setInvalid(FALSE); assert(PRIVATE(this)->bboxcache == NULL); PRIVATE(this)->lock(); PRIVATE(this)->bboxcache = new SoBoundingBoxCache(state); PRIVATE(this)->bboxcache->ref(); PRIVATE(this)->unlock(); SoCacheElement::set(state, PRIVATE(this)->bboxcache); } SbTime begin = SbTime::getTimeOfDay(); this->computeBBox(action, box, center); SbTime end = SbTime::getTimeOfDay(); if (shouldcache) { PRIVATE(this)->bboxcache->set(box, TRUE, center); // pop state since we pushed it state->pop(); SoCacheElement::setInvalid(storedinvalid); } // only create cache if calculating it took longer than the limit else if ((end.getValue() - begin.getValue()) >= SoShapeP::bboxcachetimelimit) { PRIVATE(this)->flags |= SoShapeP::SHOULD_BBOX_CACHE; if (action->isOfType(SoGetBoundingBoxAction::getClassTypeId())) { // just recalculate the bbox so that the cache is created at // once. SoGLRenderAction and SoRayPickAction might need it. state->push(); storedinvalid = SoCacheElement::setInvalid(FALSE); assert(PRIVATE(this)->bboxcache == NULL); PRIVATE(this)->lock(); PRIVATE(this)->bboxcache = new SoBoundingBoxCache(state); PRIVATE(this)->bboxcache->ref(); PRIVATE(this)->unlock(); SoCacheElement::set(state, PRIVATE(this)->bboxcache); box.makeEmpty(); this->computeBBox(action, box, center); PRIVATE(this)->bboxcache->set(box, TRUE, center); // pop state since we pushed it state->pop(); SoCacheElement::setInvalid(storedinvalid); } } } void SoShapeP::calibrateBBoxCache(void) { int i; const int ARRAYSIZE = 100; // just create 100 random vertices SbVec3f vecarray[ARRAYSIZE]; for (i = 0; i < ARRAYSIZE; i++) { for (int j = 0; j < 3; j++) { vecarray[i][j] = ((float) rand()) / ((float) RAND_MAX); } } // FIXME: should really measure CPU time spent, and not just wall // time. See the item in Coin/docs/todo.txt on implementing a // "stopwatch" ADT. 20021111 mortene. SbTime begin = SbTime::getTimeOfDay(); SbBox3f bbox; bbox.makeEmpty(); for (i = 0; i < ARRAYSIZE; i++) { bbox.extendBy(vecarray[i]); } SbTime end = SbTime::getTimeOfDay(); SoShapeP::bboxcachetimelimit = end.getValue() - begin.getValue(); } #ifdef COIN_NEXT_MINOR /*! Convenience method that enables vertex arrays and/or VBOs Returns \e TRUE if VBO is used. \sa finishVertexArray() \since Coin 2.5 */ SbBool SoShape::startVertexArray(SoGLRenderAction * action, const SoCoordinateElement * coords, const SbVec3f * pervertexnormals, const SbBool texpervertex, const SbBool colorpervertex) { SoState * state = action->getState(); const cc_glglue * glue = sogl_glue_instance(state); const SoGLVBOElement * vboelem = SoGLVBOElement::getInstance(state); const uint32_t contextid = action->getCacheContext(); SoVBO * vertexvbo = vboelem->getVertexVBO(); SbBool dovbo = TRUE; if (!vertexvbo) dovbo = FALSE; SbBool didbind = FALSE; if (colorpervertex) { const GLvoid * dataptr = NULL; SoVBO * colorvbo = dovbo ? vboelem->getColorVBO() : NULL; SoGLLazyElement * lelem = (SoGLLazyElement*) SoLazyElement::getInstance(state); if (colorvbo) { lelem->updateColorVBO(colorvbo); colorvbo->bindBuffer(contextid); didbind = TRUE; } else { if (didbind) { cc_glglue_glBindBuffer(glue, GL_ARRAY_BUFFER, 0); didbind = FALSE; } dataptr = (const GLvoid*) lelem->getDiffusePointer(); } if (colorvbo) { cc_glglue_glColorPointer(glue, 4, GL_UNSIGNED_BYTE, 0, dataptr); } else { cc_glglue_glColorPointer(glue, 3, GL_FLOAT, 0, dataptr); } cc_glglue_glEnableClientState(glue, GL_COLOR_ARRAY); } if (texpervertex) { const SoTextureCoordinateElement * telem = NULL; const SoMultiTextureCoordinateElement * mtelem = NULL; const SbBool * enabledunits = NULL; int lastenabled; telem = SoTextureCoordinateElement::getInstance(state); enabledunits = SoMultiTextureEnabledElement::getEnabledUnits(state, lastenabled); if (enabledunits) { mtelem = SoMultiTextureCoordinateElement::getInstance(state); } SoVBO * vbo; if (telem->getNum()) { int dim = telem->getDimension(); const GLvoid * tptr; switch (dim) { default: case 2: tptr = (const GLvoid*) telem->getArrayPtr2(); break; case 3: tptr = (const GLvoid*) telem->getArrayPtr3(); break; case 4: tptr = (const GLvoid*) telem->getArrayPtr4(); break; } vbo = dovbo ? vboelem->getTexCoordVBO(0) : NULL; if (vbo) { vbo->bindBuffer(contextid); didbind = TRUE; tptr = NULL; } else { if (didbind) { cc_glglue_glBindBuffer(glue, GL_ARRAY_BUFFER, 0); didbind = FALSE; } } cc_glglue_glTexCoordPointer(glue, dim, GL_FLOAT, 0, tptr); cc_glglue_glEnableClientState(glue, GL_TEXTURE_COORD_ARRAY); } for (int i = 1; i <= lastenabled; i++) { if (enabledunits[i] && mtelem->getNum(i)) { int dim = mtelem->getDimension(i); const GLvoid * tptr; switch (dim) { default: case 2: tptr = (const GLvoid*) mtelem->getArrayPtr2(i); break; case 3: tptr = (const GLvoid*) mtelem->getArrayPtr3(i); break; case 4: tptr = (const GLvoid*) mtelem->getArrayPtr4(i); break; } cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0 + i); vbo = dovbo ? vboelem->getTexCoordVBO(i) : NULL; if (vbo) { vbo->bindBuffer(contextid); didbind = TRUE; tptr = NULL; } else { if (didbind) { cc_glglue_glBindBuffer(glue, GL_ARRAY_BUFFER, 0); didbind = FALSE; } } cc_glglue_glTexCoordPointer(glue, dim, GL_FLOAT, 0, tptr); cc_glglue_glEnableClientState(glue, GL_TEXTURE_COORD_ARRAY); } } } if (pervertexnormals != NULL) { SoVBO * vbo = dovbo ? vboelem->getNormalVBO() : NULL; const GLvoid * dataptr = NULL; if (vbo) { vbo->bindBuffer(contextid); didbind = TRUE; } else { dataptr = (const GLvoid*) pervertexnormals; if (didbind) { cc_glglue_glBindBuffer(glue, GL_ARRAY_BUFFER, 0); didbind = FALSE; } } cc_glglue_glNormalPointer(glue, GL_FLOAT, 0, dataptr); cc_glglue_glEnableClientState(glue, GL_NORMAL_ARRAY); } const GLvoid * dataptr = NULL; if (vertexvbo) { vertexvbo->bindBuffer(contextid); } else { dataptr = coords->is3D() ? ((const GLvoid *)coords->getArrayPtr3()) : ((const GLvoid *)coords->getArrayPtr4()); } cc_glglue_glVertexPointer(glue, coords->is3D() ? 3 : 4, GL_FLOAT, 0, dataptr); cc_glglue_glEnableClientState(glue, GL_VERTEX_ARRAY); return dovbo; } /*! Should be called after rendering with vertex arrays. This method will disable arrays and VBOs enabled in the startVertexArray() function. \sa startVertexArray() \since Coin 2.5 */ void SoShape::finishVertexArray(SoGLRenderAction * action, const SbBool vbo, const SbBool normpervertex, const SbBool texpervertex, const SbBool colorpervertex) { SoState * state = action->getState(); const cc_glglue * glue = sogl_glue_instance(state); if (vbo) { if (!coin_glglue_vbo_in_displaylist_supported(glue)) { SoCacheElement::invalidate(state); SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DONT_AUTO_CACHE); } // unset VBO buffer cc_glglue_glBindBuffer(glue, GL_ARRAY_BUFFER, 0); } cc_glglue_glDisableClientState(glue, GL_VERTEX_ARRAY); if (normpervertex) { cc_glglue_glDisableClientState(glue, GL_NORMAL_ARRAY); } if (texpervertex) { int lastenabled; const SbBool * enabledunits = SoMultiTextureEnabledElement::getEnabledUnits(state, lastenabled); const SoMultiTextureCoordinateElement * mtelem = SoMultiTextureCoordinateElement::getInstance(state); const SoTextureCoordinateElement * telem = SoTextureCoordinateElement::getInstance(state); for (int i = 1; i <= lastenabled; i++) { if (enabledunits[i] && mtelem->getNum(i)) { cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0 + i); cc_glglue_glDisableClientState(glue, GL_TEXTURE_COORD_ARRAY); } } cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0); if (telem->getNum()) { cc_glglue_glDisableClientState(glue, GL_TEXTURE_COORD_ARRAY); } } if (colorpervertex) { SoGLLazyElement * lelem = (SoGLLazyElement*) SoLazyElement::getInstance(state); lelem->reset(state, SoLazyElement::DIFFUSE_MASK); cc_glglue_glDisableClientState(glue, GL_COLOR_ARRAY); } } #endif // COIN_NEXT_MINOR #undef PRIVATE