// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CSceneManager.h" #include "IVideoDriver.h" #include "IFileSystem.h" #include "SAnimatedMesh.h" #include "CMeshCache.h" #include "IGUIEnvironment.h" #include "IMaterialRenderer.h" #include "IReadFile.h" #include "IWriteFile.h" #include "os.h" #include "CSkinnedMesh.h" #include "CXMeshFileLoader.h" #include "COBJMeshFileLoader.h" #include "CB3DMeshFileLoader.h" #include "CBillboardSceneNode.h" #include "CAnimatedMeshSceneNode.h" #include "CCameraSceneNode.h" #include "CMeshSceneNode.h" #include "CDummyTransformationSceneNode.h" #include "CEmptySceneNode.h" #include "CSceneCollisionManager.h" namespace irr { namespace scene { //! constructor CSceneManager::CSceneManager(video::IVideoDriver *driver, gui::ICursorControl *cursorControl, IMeshCache *cache) : ISceneNode(0, 0), Driver(driver), CursorControl(cursorControl), ActiveCamera(0), ShadowColor(150, 0, 0, 0), AmbientLight(0, 0, 0, 0), Parameters(0), MeshCache(cache), CurrentRenderPass(ESNRP_NONE) { #ifdef _DEBUG ISceneManager::setDebugName("CSceneManager ISceneManager"); ISceneNode::setDebugName("CSceneManager ISceneNode"); #endif // root node's scene manager SceneManager = this; if (Driver) Driver->grab(); if (CursorControl) CursorControl->grab(); // create mesh cache if not there already if (!MeshCache) MeshCache = new CMeshCache(); else MeshCache->grab(); // set scene parameters Parameters = new io::CAttributes(); // create collision manager CollisionManager = new CSceneCollisionManager(this, Driver); // add file format loaders. add the least commonly used ones first, // as these are checked last // TODO: now that we have multiple scene managers, these should be // shallow copies from the previous manager if there is one. MeshLoaderList.push_back(new CXMeshFileLoader(this)); MeshLoaderList.push_back(new COBJMeshFileLoader(this)); MeshLoaderList.push_back(new CB3DMeshFileLoader(this)); } //! destructor CSceneManager::~CSceneManager() { clearDeletionList(); //! force to remove hardwareTextures from the driver //! because Scenes may hold internally data bounded to sceneNodes //! which may be destroyed twice if (Driver) Driver->removeAllHardwareBuffers(); if (CursorControl) CursorControl->drop(); if (CollisionManager) CollisionManager->drop(); u32 i; for (i = 0; i < MeshLoaderList.size(); ++i) MeshLoaderList[i]->drop(); if (ActiveCamera) ActiveCamera->drop(); ActiveCamera = 0; if (MeshCache) MeshCache->drop(); if (Parameters) Parameters->drop(); // remove all nodes before dropping the driver // as render targets may be destroyed twice removeAll(); if (Driver) Driver->drop(); } //! gets an animatable mesh. loads it if needed. returned pointer must not be dropped. IAnimatedMesh *CSceneManager::getMesh(io::IReadFile *file) { if (!file) return 0; io::path name = file->getFileName(); IAnimatedMesh *msh = MeshCache->getMeshByName(name); if (msh) return msh; msh = getUncachedMesh(file, name, name); return msh; } // load and create a mesh which we know already isn't in the cache and put it in there IAnimatedMesh *CSceneManager::getUncachedMesh(io::IReadFile *file, const io::path &filename, const io::path &cachename) { IAnimatedMesh *msh = 0; // iterate the list in reverse order so user-added loaders can override the built-in ones s32 count = MeshLoaderList.size(); for (s32 i = count - 1; i >= 0; --i) { if (MeshLoaderList[i]->isALoadableFileExtension(filename)) { // reset file to avoid side effects of previous calls to createMesh file->seek(0); msh = MeshLoaderList[i]->createMesh(file); if (msh) { MeshCache->addMesh(cachename, msh); msh->drop(); break; } } } if (!msh) os::Printer::log("Could not load mesh, file format seems to be unsupported", filename, ELL_ERROR); else os::Printer::log("Loaded mesh", filename, ELL_DEBUG); return msh; } //! returns the video driver video::IVideoDriver *CSceneManager::getVideoDriver() { return Driver; } //! adds a scene node for rendering a static mesh //! the returned pointer must not be dropped. IMeshSceneNode *CSceneManager::addMeshSceneNode(IMesh *mesh, ISceneNode *parent, s32 id, const core::vector3df &position, const core::vector3df &rotation, const core::vector3df &scale, bool alsoAddIfMeshPointerZero) { if (!alsoAddIfMeshPointerZero && !mesh) return 0; if (!parent) parent = this; IMeshSceneNode *node = new CMeshSceneNode(mesh, parent, this, id, position, rotation, scale); node->drop(); return node; } //! adds a scene node for rendering an animated mesh model IAnimatedMeshSceneNode *CSceneManager::addAnimatedMeshSceneNode(IAnimatedMesh *mesh, ISceneNode *parent, s32 id, const core::vector3df &position, const core::vector3df &rotation, const core::vector3df &scale, bool alsoAddIfMeshPointerZero) { if (!alsoAddIfMeshPointerZero && !mesh) return 0; if (!parent) parent = this; IAnimatedMeshSceneNode *node = new CAnimatedMeshSceneNode(mesh, parent, this, id, position, rotation, scale); node->drop(); return node; } //! Adds a camera scene node to the tree and sets it as active camera. //! \param position: Position of the space relative to its parent where the camera will be placed. //! \param lookat: Position where the camera will look at. Also known as target. //! \param parent: Parent scene node of the camera. Can be null. If the parent moves, //! the camera will move too. //! \return Returns pointer to interface to camera ICameraSceneNode *CSceneManager::addCameraSceneNode(ISceneNode *parent, const core::vector3df &position, const core::vector3df &lookat, s32 id, bool makeActive) { if (!parent) parent = this; ICameraSceneNode *node = new CCameraSceneNode(parent, this, id, position, lookat); if (makeActive) setActiveCamera(node); node->drop(); return node; } //! Adds a billboard scene node to the scene. A billboard is like a 3d sprite: A 2d element, //! which always looks to the camera. It is usually used for things like explosions, fire, //! lensflares and things like that. IBillboardSceneNode *CSceneManager::addBillboardSceneNode(ISceneNode *parent, const core::dimension2d &size, const core::vector3df &position, s32 id, video::SColor colorTop, video::SColor colorBottom) { if (!parent) parent = this; IBillboardSceneNode *node = new CBillboardSceneNode(parent, this, id, position, size, colorTop, colorBottom); node->drop(); return node; } //! Adds an empty scene node. ISceneNode *CSceneManager::addEmptySceneNode(ISceneNode *parent, s32 id) { if (!parent) parent = this; ISceneNode *node = new CEmptySceneNode(parent, this, id); node->drop(); return node; } //! Adds a dummy transformation scene node to the scene graph. IDummyTransformationSceneNode *CSceneManager::addDummyTransformationSceneNode( ISceneNode *parent, s32 id) { if (!parent) parent = this; IDummyTransformationSceneNode *node = new CDummyTransformationSceneNode( parent, this, id); node->drop(); return node; } //! Returns the root scene node. This is the scene node which is parent //! of all scene nodes. The root scene node is a special scene node which //! only exists to manage all scene nodes. It is not rendered and cannot //! be removed from the scene. //! \return Returns a pointer to the root scene node. ISceneNode *CSceneManager::getRootSceneNode() { return this; } //! Returns the current active camera. //! \return The active camera is returned. Note that this can be NULL, if there //! was no camera created yet. ICameraSceneNode *CSceneManager::getActiveCamera() const { return ActiveCamera; } //! Sets the active camera. The previous active camera will be deactivated. //! \param camera: The new camera which should be active. void CSceneManager::setActiveCamera(ICameraSceneNode *camera) { if (camera) camera->grab(); if (ActiveCamera) ActiveCamera->drop(); ActiveCamera = camera; } //! renders the node. void CSceneManager::render() { } //! returns the axis aligned bounding box of this node const core::aabbox3d &CSceneManager::getBoundingBox() const { _IRR_DEBUG_BREAK_IF(true) // Bounding Box of Scene Manager should never be used. static const core::aabbox3d dummy; return dummy; } //! returns if node is culled bool CSceneManager::isCulled(const ISceneNode *node) const { const ICameraSceneNode *cam = getActiveCamera(); if (!cam) { return false; } bool result = false; // has occlusion query information if (node->getAutomaticCulling() & scene::EAC_OCC_QUERY) { result = (Driver->getOcclusionQueryResult(const_cast(node)) == 0); } // can be seen by a bounding box ? if (!result && (node->getAutomaticCulling() & scene::EAC_BOX)) { core::aabbox3d tbox = node->getBoundingBox(); node->getAbsoluteTransformation().transformBoxEx(tbox); result = !(tbox.intersectsWithBox(cam->getViewFrustum()->getBoundingBox())); } // can be seen by a bounding sphere if (!result && (node->getAutomaticCulling() & scene::EAC_FRUSTUM_SPHERE)) { const core::aabbox3df nbox = node->getTransformedBoundingBox(); const float rad = nbox.getRadius(); const core::vector3df center = nbox.getCenter(); const float camrad = cam->getViewFrustum()->getBoundingRadius(); const core::vector3df camcenter = cam->getViewFrustum()->getBoundingCenter(); const float dist = (center - camcenter).getLengthSQ(); const float maxdist = (rad + camrad) * (rad + camrad); result = dist > maxdist; } // can be seen by cam pyramid planes ? if (!result && (node->getAutomaticCulling() & scene::EAC_FRUSTUM_BOX)) { SViewFrustum frust = *cam->getViewFrustum(); // transform the frustum to the node's current absolute transformation core::matrix4 invTrans(node->getAbsoluteTransformation(), core::matrix4::EM4CONST_INVERSE); // invTrans.makeInverse(); frust.transform(invTrans); core::vector3df edges[8]; node->getBoundingBox().getEdges(edges); for (s32 i = 0; i < scene::SViewFrustum::VF_PLANE_COUNT; ++i) { bool boxInFrustum = false; for (u32 j = 0; j < 8; ++j) { if (frust.planes[i].classifyPointRelation(edges[j]) != core::ISREL3D_FRONT) { boxInFrustum = true; break; } } if (!boxInFrustum) { result = true; break; } } } return result; } //! registers a node for rendering it at a specific time. u32 CSceneManager::registerNodeForRendering(ISceneNode *node, E_SCENE_NODE_RENDER_PASS pass) { u32 taken = 0; switch (pass) { // take camera if it is not already registered case ESNRP_CAMERA: { taken = 1; for (u32 i = 0; i != CameraList.size(); ++i) { if (CameraList[i] == node) { taken = 0; break; } } if (taken) { CameraList.push_back(node); } } break; case ESNRP_SKY_BOX: SkyBoxList.push_back(node); taken = 1; break; case ESNRP_SOLID: if (!isCulled(node)) { SolidNodeList.push_back(node); taken = 1; } break; case ESNRP_TRANSPARENT: if (!isCulled(node)) { TransparentNodeList.push_back(TransparentNodeEntry(node, camWorldPos)); taken = 1; } break; case ESNRP_TRANSPARENT_EFFECT: if (!isCulled(node)) { TransparentEffectNodeList.push_back(TransparentNodeEntry(node, camWorldPos)); taken = 1; } break; case ESNRP_AUTOMATIC: if (!isCulled(node)) { const u32 count = node->getMaterialCount(); taken = 0; for (u32 i = 0; i < count; ++i) { if (Driver->needsTransparentRenderPass(node->getMaterial(i))) { // register as transparent node TransparentNodeEntry e(node, camWorldPos); TransparentNodeList.push_back(e); taken = 1; break; } } // not transparent, register as solid if (!taken) { SolidNodeList.push_back(node); taken = 1; } } break; case ESNRP_GUI: if (!isCulled(node)) { GuiNodeList.push_back(node); taken = 1; } // as of yet unused case ESNRP_LIGHT: case ESNRP_SHADOW: case ESNRP_NONE: // ignore this one break; } return taken; } void CSceneManager::clearAllRegisteredNodesForRendering() { CameraList.clear(); SkyBoxList.clear(); SolidNodeList.clear(); TransparentNodeList.clear(); TransparentEffectNodeList.clear(); GuiNodeList.clear(); } //! This method is called just before the rendering process of the whole scene. //! draws all scene nodes void CSceneManager::drawAll() { if (!Driver) return; u32 i; // new ISO for scoping problem in some compilers // reset all transforms Driver->setMaterial(video::SMaterial()); Driver->setTransform(video::ETS_PROJECTION, core::IdentityMatrix); Driver->setTransform(video::ETS_VIEW, core::IdentityMatrix); Driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); for (i = video::ETS_COUNT - 1; i >= video::ETS_TEXTURE_0; --i) Driver->setTransform((video::E_TRANSFORMATION_STATE)i, core::IdentityMatrix); // TODO: This should not use an attribute here but a real parameter when necessary (too slow!) Driver->setAllowZWriteOnTransparent(Parameters->getAttributeAsBool(ALLOW_ZWRITE_ON_TRANSPARENT)); // do animations and other stuff. OnAnimate(os::Timer::getTime()); /*! First Scene Node for prerendering should be the active camera consistent Camera is needed for culling */ camWorldPos.set(0, 0, 0); if (ActiveCamera) { ActiveCamera->render(); camWorldPos = ActiveCamera->getAbsolutePosition(); } // let all nodes register themselves OnRegisterSceneNode(); // render camera scenes { CurrentRenderPass = ESNRP_CAMERA; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); for (i = 0; i < CameraList.size(); ++i) CameraList[i]->render(); CameraList.set_used(0); } // render skyboxes { CurrentRenderPass = ESNRP_SKY_BOX; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); for (i = 0; i < SkyBoxList.size(); ++i) SkyBoxList[i]->render(); SkyBoxList.set_used(0); } // render default objects { CurrentRenderPass = ESNRP_SOLID; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); SolidNodeList.sort(); // sort by textures for (i = 0; i < SolidNodeList.size(); ++i) SolidNodeList[i].Node->render(); SolidNodeList.set_used(0); } // render transparent objects. { CurrentRenderPass = ESNRP_TRANSPARENT; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); TransparentNodeList.sort(); // sort by distance from camera for (i = 0; i < TransparentNodeList.size(); ++i) TransparentNodeList[i].Node->render(); TransparentNodeList.set_used(0); } // render transparent effect objects. { CurrentRenderPass = ESNRP_TRANSPARENT_EFFECT; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); TransparentEffectNodeList.sort(); // sort by distance from camera for (i = 0; i < TransparentEffectNodeList.size(); ++i) TransparentEffectNodeList[i].Node->render(); TransparentEffectNodeList.set_used(0); } // render custom gui nodes { CurrentRenderPass = ESNRP_GUI; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); for (i = 0; i < GuiNodeList.size(); ++i) GuiNodeList[i]->render(); GuiNodeList.set_used(0); } clearDeletionList(); CurrentRenderPass = ESNRP_NONE; } //! Adds an external mesh loader. void CSceneManager::addExternalMeshLoader(IMeshLoader *externalLoader) { if (!externalLoader) return; externalLoader->grab(); MeshLoaderList.push_back(externalLoader); } //! Returns the number of mesh loaders supported by Irrlicht at this time u32 CSceneManager::getMeshLoaderCount() const { return MeshLoaderList.size(); } //! Retrieve the given mesh loader IMeshLoader *CSceneManager::getMeshLoader(u32 index) const { if (index < MeshLoaderList.size()) return MeshLoaderList[index]; else return 0; } //! Returns a pointer to the scene collision manager. ISceneCollisionManager *CSceneManager::getSceneCollisionManager() { return CollisionManager; } //! Returns a pointer to the mesh manipulator. IMeshManipulator *CSceneManager::getMeshManipulator() { return Driver->getMeshManipulator(); } //! Adds a scene node to the deletion queue. void CSceneManager::addToDeletionQueue(ISceneNode *node) { if (!node) return; node->grab(); DeletionList.push_back(node); } //! clears the deletion list void CSceneManager::clearDeletionList() { if (DeletionList.empty()) return; for (u32 i = 0; i < DeletionList.size(); ++i) { DeletionList[i]->remove(); DeletionList[i]->drop(); } DeletionList.clear(); } //! Returns the first scene node with the specified name. ISceneNode *CSceneManager::getSceneNodeFromName(const char *name, ISceneNode *start) { if (start == 0) start = getRootSceneNode(); auto startName = start->getName(); if (startName.has_value() && startName == name) return start; ISceneNode *node = 0; const ISceneNodeList &list = start->getChildren(); ISceneNodeList::const_iterator it = list.begin(); for (; it != list.end(); ++it) { node = getSceneNodeFromName(name, *it); if (node) return node; } return 0; } //! Returns the first scene node with the specified id. ISceneNode *CSceneManager::getSceneNodeFromId(s32 id, ISceneNode *start) { if (start == 0) start = getRootSceneNode(); if (start->getID() == id) return start; ISceneNode *node = 0; const ISceneNodeList &list = start->getChildren(); ISceneNodeList::const_iterator it = list.begin(); for (; it != list.end(); ++it) { node = getSceneNodeFromId(id, *it); if (node) return node; } return 0; } //! Returns the first scene node with the specified type. ISceneNode *CSceneManager::getSceneNodeFromType(scene::ESCENE_NODE_TYPE type, ISceneNode *start) { if (start == 0) start = getRootSceneNode(); if (start->getType() == type || ESNT_ANY == type) return start; ISceneNode *node = 0; const ISceneNodeList &list = start->getChildren(); ISceneNodeList::const_iterator it = list.begin(); for (; it != list.end(); ++it) { node = getSceneNodeFromType(type, *it); if (node) return node; } return 0; } //! returns scene nodes by type. void CSceneManager::getSceneNodesFromType(ESCENE_NODE_TYPE type, core::array &outNodes, ISceneNode *start) { if (start == 0) start = getRootSceneNode(); if (start->getType() == type || ESNT_ANY == type) outNodes.push_back(start); const ISceneNodeList &list = start->getChildren(); ISceneNodeList::const_iterator it = list.begin(); for (; it != list.end(); ++it) { getSceneNodesFromType(type, outNodes, *it); } } //! Posts an input event to the environment. Usually you do not have to //! use this method, it is used by the internal engine. bool CSceneManager::postEventFromUser(const SEvent &event) { bool ret = false; ICameraSceneNode *cam = getActiveCamera(); if (cam) ret = cam->OnEvent(event); return ret; } //! Removes all children of this scene node void CSceneManager::removeAll() { ISceneNode::removeAll(); setActiveCamera(0); // Make sure the driver is reset, might need a more complex method at some point if (Driver) Driver->setMaterial(video::SMaterial()); } //! Clears the whole scene. All scene nodes are removed. void CSceneManager::clear() { removeAll(); } //! Returns interface to the parameters set in this scene. io::IAttributes *CSceneManager::getParameters() { return Parameters; } //! Returns current render pass. E_SCENE_NODE_RENDER_PASS CSceneManager::getSceneNodeRenderPass() const { return CurrentRenderPass; } //! Returns an interface to the mesh cache which is shared between all existing scene managers. IMeshCache *CSceneManager::getMeshCache() { return MeshCache; } //! Creates a new scene manager. ISceneManager *CSceneManager::createNewSceneManager(bool cloneContent) { CSceneManager *manager = new CSceneManager(Driver, CursorControl, MeshCache); if (cloneContent) manager->cloneMembers(this, manager); return manager; } //! Sets ambient color of the scene void CSceneManager::setAmbientLight(const video::SColorf &ambientColor) { AmbientLight = ambientColor; } //! Returns ambient color of the scene const video::SColorf &CSceneManager::getAmbientLight() const { return AmbientLight; } //! Get a skinned mesh, which is not available as header-only code ISkinnedMesh *CSceneManager::createSkinnedMesh() { return new CSkinnedMesh(); } // creates a scenemanager ISceneManager *createSceneManager(video::IVideoDriver *driver, gui::ICursorControl *cursorcontrol) { return new CSceneManager(driver, cursorcontrol, nullptr); } } // end namespace scene } // end namespace irr