/*------------------------------------------------------------------------------------- Copyright (c) 2006 John Judnich Modified 2008 by Erik Ogenvik (erik@ogenvik.org) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------------*/ #include "GrassLoader.h" #include "PagedGeometry.h" #include "PropertyMaps.h" #include "RandomTable.h" #include "ShaderHelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Ogre; namespace Forests { GrassLayer::GrassLayer(PagedGeometry *geom, GrassLoader *ldr) { GrassLayer::geom = geom; GrassLayer::parent = ldr; density = 1.0f; minWidth = 1.0f; maxWidth = 1.0f; minHeight = 1.0f; maxHeight = 1.0f; minY = 0; maxY = 0; maxSlope = 1000; renderTechnique = GRASSTECH_QUAD; fadeTechnique = FADETECH_ALPHA; animMag = 1.0f; animSpeed = 1.0f; animFreq = 1.0f; waveCount = 0.0f; animate = false; blend = false; lighting = false; shaderNeedsUpdate = true; densityMap = NULL; densityMapFilter = MAPFILTER_BILINEAR; colorMap = NULL; colorMapFilter = MAPFILTER_BILINEAR; } GrassLayer::~GrassLayer() { if (densityMap) densityMap->unload(); if (colorMap) colorMap->unload(); } unsigned int GrassLayer::prepareGrass(const PageInfo& page, float densityFactor, float volume, bool& isAvailable) { isAvailable = true; return density * densityFactor * volume; } unsigned int GrassLayer::_populateGrassList(PageInfo page, float *posBuff, unsigned int grassCount) { if (densityMap){ if (densityMap->getFilter() == MAPFILTER_NONE) return _populateGrassList_UnfilteredDM(page, posBuff, grassCount); else if (densityMap->getFilter() == MAPFILTER_BILINEAR) return _populateGrassList_BilinearDM(page, posBuff, grassCount); } return _populateGrassList_Uniform(page, posBuff, grassCount); } void GrassLayerBase::setMaterialName(const String &matName) { if (!material || matName != material->getName()){ material = MaterialManager::getSingleton().getByName(matName); if (!material) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "The specified grass material does not exist", "GrassLayer::setMaterialName()"); shaderNeedsUpdate = true; } } void GrassLayerBase::setMinimumSize(float width, float height) { minWidth = width; minHeight = height; } void GrassLayerBase::setMaximumSize(float width, float height) { maxWidth = width; if (maxHeight != height){ maxHeight = height; shaderNeedsUpdate = true; } } void GrassLayerBase::setRenderTechnique(GrassTechnique style, bool blendBase) { if (blend != blendBase || renderTechnique != style){ blend = blendBase; renderTechnique = style; shaderNeedsUpdate = true; } } void GrassLayerBase::setFadeTechnique(FadeTechnique style) { if (fadeTechnique != style){ fadeTechnique = style; shaderNeedsUpdate = true; } } void GrassLayerBase::setAnimationEnabled(bool enabled) { if (animate != enabled){ animate = enabled; shaderNeedsUpdate = true; } } void GrassLayerBase::setLightingEnabled(bool enabled) { lighting = enabled; } bool GrassLayerBase::isColoursEnabled() const { return true; } bool GrassLayerBase::isNormalsEnabled() const { return false; } bool GrassLayerBase::isTangentsEnabled() const { return false; } bool GrassLayerBase::isCastShadowsEnabled() const { return false; } void GrassLayer::setDensityMap(const String &mapFile, MapChannel channel) { if (densityMap){ densityMap->unload(); densityMap = NULL; } if (mapFile != ""){ densityMap = DensityMap::load(mapFile, channel); densityMap->setFilter(densityMapFilter); } } void GrassLayer::setDensityMap(TexturePtr map, MapChannel channel) { if (densityMap){ densityMap->unload(); densityMap = NULL; } if (map){ densityMap = DensityMap::load(map, channel); densityMap->setFilter(densityMapFilter); } } void GrassLayer::setDensityMapFilter(MapFilter filter) { densityMapFilter = filter; if (densityMap) densityMap->setFilter(densityMapFilter); } unsigned int GrassLayer::_populateGrassList_Uniform(PageInfo page, float *posBuff, unsigned int grassCount) { float *posPtr = posBuff; parent->rTable->resetRandomIndex(); //No density map if (!minY && !maxY){ //No height range for (unsigned int i = 0; i < grassCount; ++i){ //Pick a random position float x = parent->rTable->getRangeRandom(page.bounds.left, page.bounds.right); float z = parent->rTable->getRangeRandom(page.bounds.top, page.bounds.bottom); //Add to list in within bounds if (!colorMap){ *posPtr++ = x; *posPtr++ = z; } else if (x >= mapBounds.left && x <= mapBounds.right && z >= mapBounds.top && z <= mapBounds.bottom){ *posPtr++ = x; *posPtr++ = z; } *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::TWO_PI); } } else { //Height range Real min, max; if (minY) min = minY; else min = Math::NEG_INFINITY; if (maxY) max = maxY; else max = Math::POS_INFINITY; for (unsigned int i = 0; i < grassCount; ++i){ //Pick a random position float x = parent->rTable->getRangeRandom(page.bounds.left, page.bounds.right); float z = parent->rTable->getRangeRandom(page.bounds.top, page.bounds.bottom); //Calculate height float y = parent->heightFunction(x, z, parent->heightFunctionUserData); //Add to list if in range if (y >= min && y <= max){ //Add to list in within bounds if (!colorMap){ *posPtr++ = x; *posPtr++ = z; *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::PI); } else if (x >= mapBounds.left && x <= mapBounds.right && z >= mapBounds.top && z <= mapBounds.bottom){ *posPtr++ = x; *posPtr++ = z; *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::PI); } } } } grassCount = (posPtr - posBuff) / 4; return grassCount; } unsigned int GrassLayer::_populateGrassList_UnfilteredDM(PageInfo page, float *posBuff, unsigned int grassCount) { float *posPtr = posBuff; parent->rTable->resetRandomIndex(); //Use density map if (!minY && !maxY){ //No height range for (unsigned int i = 0; i < grassCount; ++i){ //Pick a random position float x = parent->rTable->getRangeRandom(page.bounds.left, page.bounds.right); float z = parent->rTable->getRangeRandom(page.bounds.top, page.bounds.bottom); //Determine whether this grass will be added based on the local density. //For example, if localDensity is .32, grasses will be added 32% of the time. if (parent->rTable->getUnitRandom() < densityMap->_getDensityAt_Unfiltered(x, z, mapBounds)){ //Add to list *posPtr++ = x; *posPtr++ = z; *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::TWO_PI); } else { parent->rTable->getUnitRandom(); parent->rTable->getUnitRandom(); } } } else { //Height range Real min, max; if (minY) min = minY; else min = Math::NEG_INFINITY; if (maxY) max = maxY; else max = Math::POS_INFINITY; for (unsigned int i = 0; i < grassCount; ++i){ //Pick a random position float x = parent->rTable->getRangeRandom(page.bounds.left, page.bounds.right); float z = parent->rTable->getRangeRandom(page.bounds.top, page.bounds.bottom); //Determine whether this grass will be added based on the local density. //For example, if localDensity is .32, grasses will be added 32% of the time. if (parent->rTable->getUnitRandom() < densityMap->_getDensityAt_Unfiltered(x, z, mapBounds)){ //Calculate height float y = parent->heightFunction(x, z, parent->heightFunctionUserData); //Add to list if in range if (y >= min && y <= max){ //Add to list *posPtr++ = x; *posPtr++ = z; *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::TWO_PI); } else { parent->rTable->getUnitRandom(); parent->rTable->getUnitRandom(); } } else { parent->rTable->getUnitRandom(); parent->rTable->getUnitRandom(); } } } grassCount = (posPtr - posBuff) / 4; return grassCount; } unsigned int GrassLayer::_populateGrassList_BilinearDM(PageInfo page, float *posBuff, unsigned int grassCount) { float *posPtr = posBuff; parent->rTable->resetRandomIndex(); if (!minY && !maxY){ //No height range for (unsigned int i = 0; i < grassCount; ++i){ //Pick a random position float x = parent->rTable->getRangeRandom(page.bounds.left, page.bounds.right); float z = parent->rTable->getRangeRandom(page.bounds.top, page.bounds.bottom); //Determine whether this grass will be added based on the local density. //For example, if localDensity is .32, grasses will be added 32% of the time. if (parent->rTable->getUnitRandom() < densityMap->_getDensityAt_Bilinear(x, z, mapBounds)){ //Add to list *posPtr++ = x; *posPtr++ = z; *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::TWO_PI); } else { parent->rTable->getUnitRandom(); parent->rTable->getUnitRandom(); } } } else { //Height range Real min, max; if (minY) min = minY; else min = Math::NEG_INFINITY; if (maxY) max = maxY; else max = Math::POS_INFINITY; for (unsigned int i = 0; i < grassCount; ++i){ //Pick a random position float x = parent->rTable->getRangeRandom(page.bounds.left, page.bounds.right); float z = parent->rTable->getRangeRandom(page.bounds.top, page.bounds.bottom); //Determine whether this grass will be added based on the local density. //For example, if localDensity is .32, grasses will be added 32% of the time. if (parent->rTable->getUnitRandom() < densityMap->_getDensityAt_Bilinear(x, z, mapBounds)){ //Calculate height float y = parent->heightFunction(x, z, parent->heightFunctionUserData); //Add to list if in range if (y >= min && y <= max){ //Add to list *posPtr++ = x; *posPtr++ = z; *posPtr++ = parent->rTable->getUnitRandom(); *posPtr++ = parent->rTable->getRangeRandom(0, Math::TWO_PI); } else { parent->rTable->getUnitRandom(); parent->rTable->getUnitRandom(); } } else { parent->rTable->getUnitRandom(); parent->rTable->getUnitRandom(); } } } grassCount = (posPtr - posBuff) / 4; return grassCount; } void GrassLayer::setColorMap(const String &mapFile, MapChannel channel) { if (colorMap){ colorMap->unload(); colorMap = NULL; } if (mapFile != ""){ colorMap = ColorMap::load(mapFile, channel); colorMap->setFilter(colorMapFilter); } } void GrassLayer::setColorMap(TexturePtr map, MapChannel channel) { if (colorMap){ colorMap->unload(); colorMap = NULL; } if (map){ colorMap = ColorMap::load(map, channel); colorMap->setFilter(colorMapFilter); } } void GrassLayer::setColorMapFilter(MapFilter filter) { colorMapFilter = filter; if (colorMap) colorMap->setFilter(colorMapFilter); } void GrassLayerBase::_updateShaders() { if (shaderNeedsUpdate){ shaderNeedsUpdate = false; //Proceed only if there is no custom vertex shader and the user's computer supports vertex shaders const RenderSystemCapabilities *caps = Root::getSingleton().getRenderSystem()->getCapabilities(); if (caps->hasCapability(RSC_VERTEX_PROGRAM) && geom->getShadersEnabled()) { //Calculate fade range float farViewDist = geom->getDetailLevels().front()->getFarRange(); float fadeRange = farViewDist / 1.2247449f; //Note: 1.2247449 ~= sqrt(1.5), which is necessary since the far view distance is measured from the centers //of pages, while the vertex shader needs to fade grass completely out (including the closest corner) //before the page center is out of range. //Generate a string ID that identifies the current set of vertex shader options Ogre::StringStream tmpName; tmpName << "GrassVS_"; if (animate) tmpName << "anim_"; if (blend) tmpName << "blend_"; if (lighting) tmpName << "lighting_"; tmpName << renderTechnique << "_"; tmpName << fadeTechnique << "_"; if (fadeTechnique == FADETECH_GROW || fadeTechnique == FADETECH_ALPHAGROW) tmpName << maxHeight << "_"; tmpName << farViewDist << "_"; tmpName << "vp"; const String vsName = tmpName.str(); //Generate a string ID that identifies the material combined with the vertex shader const String matName = material->getName() + "_" + vsName; //Check if the desired material already exists (if not, create it) MaterialPtr tmpMat = MaterialManager::getSingleton().getByName(matName); if (!tmpMat) { //Clone the original material tmpMat = material->clone(matName); //Disable lighting tmpMat->setLightingEnabled(false); //tmpMat->setReceiveShadows(false); //Check if the desired shader already exists (if not, compile it) String shaderLanguage = ShaderHelper::getShaderLanguage(); HighLevelGpuProgramPtr vertexShader = HighLevelGpuProgramManager::getSingleton().getByName(vsName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); if (!vertexShader) { //Generate the grass shader String vertexProgSource; if(!shaderLanguage.compare("hlsl") || !shaderLanguage.compare("cg")) { vertexProgSource = "void main( \n" " float4 iPosition : POSITION, \n" " float4 iColor : COLOR, \n" " float2 iUV : TEXCOORD0, \n" " out float4 oPosition : POSITION, \n" " out float4 oColor : COLOR, \n" " out float2 oUV : TEXCOORD0, \n"; if (lighting) vertexProgSource += " uniform float4 objSpaceLight, \n" " uniform float4 lightDiffuse, \n" " uniform float4 lightAmbient, \n"; if (animate) vertexProgSource += " uniform float time, \n" " uniform float frequency, \n" " uniform float4 direction, \n"; if (fadeTechnique == FADETECH_GROW || fadeTechnique == FADETECH_ALPHAGROW) vertexProgSource += " uniform float grassHeight, \n"; if (renderTechnique == GRASSTECH_SPRITE || lighting) vertexProgSource += " float4 iNormal : NORMAL, \n"; vertexProgSource += " uniform float4x4 worldViewProj, \n" " uniform float3 camPos, \n" " uniform float fadeRange ) \n" "{ \n" " oColor.rgb = iColor.rgb; \n" " float4 position = iPosition; \n" " float dist = distance(camPos.xz, position.xz); \n"; if (lighting) { vertexProgSource += " float3 light = normalize(objSpaceLight.xyz - (iPosition.xyz * objSpaceLight.w)); \n" " float diffuseFactor = max(dot(float4(0,1,0,0), light), 0); \n" " oColor = (lightAmbient + diffuseFactor * lightDiffuse) * iColor; \n"; } else { vertexProgSource += " oColor.rgb = iColor.rgb; \n"; } if (fadeTechnique == FADETECH_ALPHA || fadeTechnique == FADETECH_ALPHAGROW) vertexProgSource += //Fade out in the distance " oColor.a = 2.0f - (2.0f * dist / fadeRange); \n"; else vertexProgSource += " oColor.a = 1.0f; \n"; vertexProgSource += " float oldposx = position.x; \n"; if (renderTechnique == GRASSTECH_SPRITE) vertexProgSource += //Face the camera " float3 dirVec = (float3)position - (float3)camPos; \n" " float3 p = normalize(cross(float4(0,1,0,0), dirVec)); \n" " position += float4(p.x * iNormal.x, iNormal.y, p.z * iNormal.x, 0); \n"; if (animate) vertexProgSource += " if (iUV.y == 0.0f){ \n" //Wave grass in breeze " float offset = sin(time + oldposx * frequency); \n" " position += direction * offset; \n" " } \n"; if (blend && animate) vertexProgSource += " else { \n"; else if (blend) vertexProgSource += " if (iUV.y != 0.0f){ \n"; if (blend) vertexProgSource += //Blend the base of nearby grass into the terrain " oColor.a = clamp(oColor.a, 0, 1) * 4.0f * ((dist / fadeRange) - 0.1f); \n" " } \n"; if (fadeTechnique == FADETECH_GROW || fadeTechnique == FADETECH_ALPHAGROW) vertexProgSource += " float offset = (2.0f * dist / fadeRange) - 1.0f; \n" " position.y -= grassHeight * clamp(offset, 0, 1); "; vertexProgSource += " oPosition = mul(worldViewProj, position); \n"; vertexProgSource += " oUV = iUV;\n" "}"; } else { //Must be glsl if (lighting) { vertexProgSource = "uniform vec4 objSpaceLight; \n" "uniform vec4 lightDiffuse; \n" "uniform vec4 lightAmbient; \n"; } if (animate) { vertexProgSource += "uniform float time; \n" "uniform float frequency; \n" "uniform vec4 direction; \n"; } if (fadeTechnique == FADETECH_GROW || fadeTechnique == FADETECH_ALPHAGROW) { vertexProgSource += "uniform float grassHeight; \n"; } vertexProgSource += "uniform vec3 camPos; \n" "uniform float fadeRange; \n" "\n" "void main()" "{ \n" " vec4 color = gl_Color; \n" " vec4 position = gl_Vertex; \n" " float dist = distance(camPos.xz, position.xz); \n"; if (lighting) { vertexProgSource += " vec3 light = normalize(objSpaceLight.xyz - (gl_Vertex.xyz * objSpaceLight.w)); \n" " float diffuseFactor = max( dot( vec3(0.0,1.0,0.0), light), 0.0); \n" " color = (lightAmbient + diffuseFactor * lightDiffuse) * gl_Color; \n"; } else { vertexProgSource += " color.xyz = gl_Color.xyz; \n"; } if (fadeTechnique == FADETECH_ALPHA || fadeTechnique == FADETECH_ALPHAGROW) { vertexProgSource += //Fade out in the distance " color.w = 2.0 - (2.0 * dist / fadeRange); \n"; } else { vertexProgSource += " color.w = 1.0; \n"; } if (renderTechnique == GRASSTECH_SPRITE) { vertexProgSource += //Face the camera " vec3 dirVec = position.xyz - camPos.xyz; \n" " vec3 p = normalize(cross(vec3(0.0,1.0,0.0), dirVec)); \n" " position += vec4(p.x * gl_Normal.x, gl_Normal.y, p.z * gl_Normal.x, 0.0); \n"; } if (animate) { vertexProgSource += " if (gl_MultiTexCoord0.y == 0.0) \n" " { \n" //Wave grass in breeze " position += direction * sin(time + gl_Vertex.x * frequency); \n" " } \n"; } if (blend && animate) { vertexProgSource += " else \n" " { \n"; } else if (blend) { vertexProgSource += " if (gl_MultiTexCoord0.y != 0.0) \n" " { \n"; } if (blend) { vertexProgSource += //Blend the base of nearby grass into the terrain " color.w = clamp(color.w, 0.0, 1.0) * 4.0 * ((dist / fadeRange) - 0.1); \n" " } \n"; } if (fadeTechnique == FADETECH_GROW || fadeTechnique == FADETECH_ALPHAGROW) { vertexProgSource += " position.y -= grassHeight * clamp((2.0 * dist / fadeRange) - 1.0, 0.0, 1.0); \n"; } vertexProgSource += " gl_Position = gl_ModelViewProjectionMatrix * position; \n" " gl_FrontColor = color; \n" " gl_TexCoord[0] = gl_MultiTexCoord0; \n"; if (geom->getSceneManager()->getFogMode() == Ogre::FOG_EXP2) { vertexProgSource += " gl_FogFragCoord = clamp(exp(- gl_Fog.density * gl_Fog.density * gl_Position.z * gl_Position.z), 0.0, 1.0); \n"; } else { vertexProgSource += " gl_FogFragCoord = gl_Position.z; \n"; } vertexProgSource += "}"; } vertexShader = HighLevelGpuProgramManager::getSingleton().createProgram( vsName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, shaderLanguage, GPT_VERTEX_PROGRAM); vertexShader->setSource(vertexProgSource); if (shaderLanguage == "hlsl") { vertexShader->setParameter("target", "vs_1_1"); vertexShader->setParameter("entry_point", "main"); } else if(shaderLanguage == "cg") { vertexShader->setParameter("profiles", "vs_1_1 arbvp1"); vertexShader->setParameter("entry_point", "main"); } // GLSL can only have one entry point "main". vertexShader->load(); } //Now the vertex shader (vertexShader) has either been found or just generated //(depending on whether or not it was already generated). tmpMat->load(); for (Ogre::Technique* tech : tmpMat->getSupportedTechniques()) { //Apply the shader to the material Pass *pass = tech->getPass(0); pass->setVertexProgram(vsName); GpuProgramParametersSharedPtr params = pass->getVertexProgramParameters(); if (shaderLanguage.compare("glsl") == 0) { //glsl can use the built in gl_ModelViewProjectionMatrix params->setNamedAutoConstant("worldViewProj", GpuProgramParameters::ACT_WORLDVIEWPROJ_MATRIX); } params->setNamedAutoConstant("camPos", GpuProgramParameters::ACT_CAMERA_POSITION_OBJECT_SPACE); params->setNamedAutoConstant("fadeRange", GpuProgramParameters::ACT_CUSTOM, 1); if (animate){ params->setNamedConstant("time", 1.0f); params->setNamedConstant("frequency", 1.0f); params->setNamedConstant("direction", Ogre::Vector4::ZERO); } if (lighting){ params->setNamedAutoConstant("objSpaceLight", GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE); params->setNamedAutoConstant("lightDiffuse", GpuProgramParameters::ACT_DERIVED_LIGHT_DIFFUSE_COLOUR); params->setNamedAutoConstant("lightAmbient", GpuProgramParameters::ACT_DERIVED_AMBIENT_LIGHT_COLOUR); } if (fadeTechnique == FADETECH_GROW || fadeTechnique == FADETECH_ALPHAGROW){ params->setNamedConstant("grassHeight", maxHeight * 1.05f); } pass->getVertexProgramParameters()->setNamedConstant("fadeRange", fadeRange); } } //Now the material (tmpMat) has either been found or just created (depending on whether or not it was already //created). The appropriate vertex shader should be applied and the material is ready for use. //Apply the new material material = tmpMat; } Ogre::Technique* tech = material->getBestTechnique(); if (tech && tech->getNumPasses()) { Ogre::Pass* pass = tech->getPass(0); if (pass->hasVertexProgram()) { Ogre::GpuProgramParametersSharedPtr params = pass->getVertexProgramParameters(); if (params) { params->setIgnoreMissingParams(true); } } } } } unsigned long GrassPage::GUID = 0; void GrassPage::init(PagedGeometry *geom, const Ogre::Any &data) { sceneMgr = geom->getSceneManager(); rootNode = geom->getSceneNode(); } GrassPage::~GrassPage() { removeEntities(); } void GrassPage::addEntity(Entity *entity, const Vector3 &position, const Quaternion &rotation, const Vector3 &scale, const Ogre::ColourValue &color) { SceneNode *node = rootNode->createChildSceneNode(); node->setPosition(position); nodeList.push_back(node); // entity->setCastShadows(false); if(hasQueryFlag()) entity->setQueryFlags(getQueryFlag()); entity->setRenderQueueGroup(entity->getRenderQueueGroup()); node->attachObject(entity); } void GrassPage::removeEntities() { std::list::iterator i; for (i = nodeList.begin(); i != nodeList.end(); ++i) { SceneNode *node = *i; int numObjs = node->numAttachedObjects(); for(int j = 0; j < numObjs; j++) { Entity *ent = static_cast(node->getAttachedObject(j)); if(!ent) continue; // remove the mesh MeshManager::getSingleton().remove(ent->getMesh()->getName(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); // then the entity sceneMgr->destroyEntity(ent); // and finally the scene node sceneMgr->destroySceneNode(node); } } nodeList.clear(); } void GrassPage::setVisible(bool visible) { std::list::iterator i; for (i = nodeList.begin(); i != nodeList.end(); ++i){ SceneNode *node = *i; node->setVisible(visible); } } }