// 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 "IrrCompileConfig.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, io::IFileSystem* fs, gui::ICursorControl* cursorControl, IMeshCache* cache, gui::IGUIEnvironment* gui) : ISceneNode(0, 0), Driver(driver), FileSystem(fs), GUIEnvironment(gui), 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 (FileSystem) FileSystem->grab(); if (CursorControl) CursorControl->grab(); if (GUIEnvironment) GUIEnvironment->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, FileSystem)); MeshLoaderList.push_back(new COBJMeshFileLoader(this, FileSystem)); 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 (FileSystem) FileSystem->drop(); if (CursorControl) CursorControl->drop(); if (CollisionManager) CollisionManager->drop(); if (GUIEnvironment) GUIEnvironment->drop(); u32 i; for (i=0; idrop(); 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 animateable mesh. loads it if needed. returned pointer must not be dropped. IAnimatedMesh* CSceneManager::getMesh(const io::path& filename, const io::path& alternativeCacheName) { io::path cacheName = alternativeCacheName.empty() ? filename : alternativeCacheName; IAnimatedMesh* msh = MeshCache->getMeshByName(cacheName); if (msh) return msh; io::IReadFile* file = FileSystem->createAndOpenFile(filename); if (!file) { os::Printer::log("Could not load mesh, because file could not be opened: ", filename, ELL_ERROR); return 0; } msh = getUncachedMesh(file, filename, cacheName); file->drop(); return msh; } //! gets an animateable 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; } //! returns the GUI Environment gui::IGUIEnvironment* CSceneManager::getGUIEnvironment() { return GUIEnvironment; } //! Get the active FileSystem /** \return Pointer to the FileSystem This pointer should not be dropped. See IReferenceCounted::drop() for more information. */ io::IFileSystem* CSceneManager::getFileSystem() { return FileSystem; } //! 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; igetMaterialCount(); taken = 0; for (u32 i=0; ineedsTransparentRenderPass(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; } #ifdef _IRR_SCENEMANAGER_DEBUG s32 index = Parameters->findAttribute("calls"); Parameters->setAttribute(index, Parameters->getAttributeAsInt(index)+1); if (!taken) { index = Parameters->findAttribute("culled"); Parameters->setAttribute(index, Parameters->getAttributeAsInt(index)+1); } #endif 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; #ifdef _IRR_SCENEMANAGER_DEBUG // reset attributes Parameters->setAttribute("culled", 0); Parameters->setAttribute("calls", 0); Parameters->setAttribute("drawn_solid", 0); Parameters->setAttribute("drawn_transparent", 0); Parameters->setAttribute("drawn_transparent_effect", 0); #endif 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; irender(); CameraList.set_used(0); } // render skyboxes { CurrentRenderPass = ESNRP_SKY_BOX; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); for (i=0; irender(); 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; irender(); #ifdef _IRR_SCENEMANAGER_DEBUG Parameters->setAttribute("drawn_solid", (s32) SolidNodeList.size() ); #endif 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; irender(); #ifdef _IRR_SCENEMANAGER_DEBUG Parameters->setAttribute ( "drawn_transparent", (s32) TransparentNodeList.size() ); #endif 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; irender(); #ifdef _IRR_SCENEMANAGER_DEBUG Parameters->setAttribute("drawn_transparent_effect", (s32) TransparentEffectNodeList.size()); #endif TransparentEffectNodeList.set_used(0); } // render custom gui nodes { CurrentRenderPass = ESNRP_GUI; Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); for (i=0; irender(); #ifdef _IRR_SCENEMANAGER_DEBUG Parameters->setAttribute("drawn_gui_nodes", (s32) GuiNodeList.size()); #endif 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; iremove(); 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(); if (!strcmp(start->getName(),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, FileSystem, CursorControl, MeshCache, GUIEnvironment); 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(); } //! Returns a mesh writer implementation if available IMeshWriter* CSceneManager::createMeshWriter(EMESH_WRITER_TYPE type) { return 0; } // creates a scenemanager ISceneManager* createSceneManager(video::IVideoDriver* driver, io::IFileSystem* fs, gui::ICursorControl* cursorcontrol, gui::IGUIEnvironment *guiEnvironment) { return new CSceneManager(driver, fs, cursorcontrol, 0, guiEnvironment ); } } // end namespace scene } // end namespace irr