/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Fabian 'x3n' Landau * Reto Grieder (physics) * Co-authors: * ... * */ /** @file Scene.cc @brief Implementation of Scene Class */ #include "Scene.h" #include #include #include #include #if OGRE_VERSION >= 0x010900 # include #endif #include #include #include #include #include "core/CoreIncludes.h" #include "core/GameMode.h" #include "core/GUIManager.h" #include "core/XMLPort.h" #include "core/command/ConsoleCommandIncludes.h" #include "tools/BulletConversions.h" #include "tools/BulletDebugDrawer.h" #include "tools/DebugDrawer.h" #include "Radar.h" #include "worldentities/WorldEntity.h" #include "Level.h" #include "RenderQueueListener.h" #include "graphics/GlobalShader.h" namespace orxonox { RegisterClass(Scene); /** @brief Constructor, it sets common standard paramters for a scene depending on whether it will be rendered or not. It also makes sure we user our own render queue listener for rendering instead of the standard listener provided by Ogre */ SetConsoleCommand("Scene", "debugDrawPhysics", &Scene::consoleCommand_debugDrawPhysics).addShortcut().defaultValue(1, true).defaultValue(2, 0.5f); Scene::Scene(Context* context) : BaseObject(context), Synchronisable(context), Context(context) { RegisterObject(Scene); this->setScene(WeakPtr(this), this->getObjectID()); // store a weak-pointer to itself (a strong-pointer would create a recursive dependency) this->bShadows_ = true; this->bDebugDrawPhysics_ = false; this->debugDrawer_ = nullptr; this->soundReferenceDistance_ = 20.0; this->bIsUpdatingPhysics_ = false; if (GameMode::showsGraphics()) { assert(Ogre::Root::getSingletonPtr()); this->sceneManager_ = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); this->rootSceneNode_ = this->sceneManager_->getRootSceneNode(); this->renderQueueListener_ = new RenderQueueListener(); this->sceneManager_->addRenderQueueListener(this->renderQueueListener_);//add our own renderQueueListener #if OGRE_VERSION >= 0x010900 this->sceneManager_->addRenderQueueListener(GraphicsManager::getInstance().getOverlaySystem()); #endif this->radar_ = new Radar(); this->glowShader_ = new GlobalShader(this); this->glowShader_->setScene(WeakPtr(this), this->getObjectID()); // avoid circular reference this->glowShader_->getShader().setCompositorName("Glow"); } else { // create a dummy SceneManager of our own since we don't have Ogre::Root. this->sceneManager_ = new Ogre::DefaultSceneManager(""); this->rootSceneNode_ = this->sceneManager_->getRootSceneNode(); this->renderQueueListener_ = nullptr; this->radar_ = nullptr; this->glowShader_ = nullptr; } // No physics yet, XMLPort will do that. const float defaultMaxWorldSize = 100000.0f; this->negativeWorldRange_ = Vector3::UNIT_SCALE * -defaultMaxWorldSize; this->positiveWorldRange_ = Vector3::UNIT_SCALE * defaultMaxWorldSize; this->gravity_ = Vector3::ZERO; this->physicalWorld_ = nullptr; this->solver_ = nullptr; this->dispatcher_ = nullptr; this->collisionConfig_ = nullptr; this->broadphase_ = nullptr; this->registerVariables(); } Scene::~Scene() { if (this->isInitialized()) { this->setPhysicalWorld(false); if (this->radar_) this->radar_->destroy(); if (this->glowShader_) this->glowShader_->destroy(); if (GameMode::showsGraphics()) { #if OGRE_VERSION >= 0x010900 this->sceneManager_->removeRenderQueueListener(GraphicsManager::getInstance().getOverlaySystem()); #endif this->sceneManager_->removeRenderQueueListener(this->renderQueueListener_); delete this->renderQueueListener_; Ogre::Root::getSingleton().destroySceneManager(this->sceneManager_); } else delete this->sceneManager_; } } void Scene::XMLPort(Element& xmlelement, XMLPort::Mode mode) { SUPER(Scene, XMLPort, xmlelement, mode); XMLPortParam(Scene, "skybox", setSkybox, getSkybox, xmlelement, mode); XMLPortParam(Scene, "ambientlight", setAmbientLight, getAmbientLight, xmlelement, mode).defaultValues(ColourValue(0.2f, 0.2f, 0.2f, 1.0f)); XMLPortParam(Scene, "shadow", setShadow, getShadow, xmlelement, mode).defaultValues(true); XMLPortParam(Scene, "soundReferenceDistance", setSoundReferenceDistance, getSoundReferenceDistance, xmlelement, mode); XMLPortParam(Scene, "gravity", setGravity, getGravity, xmlelement, mode); XMLPortParam(Scene, "negativeWorldRange", setNegativeWorldRange, getNegativeWorldRange, xmlelement, mode); XMLPortParam(Scene, "positiveWorldRange", setPositiveWorldRange, getPositiveWorldRange, xmlelement, mode); XMLPortParam(Scene, "hasPhysics", setPhysicalWorld, hasPhysics, xmlelement, mode).defaultValues(true); XMLPortObjectExtended(Scene, BaseObject, "", addObject, getObject, xmlelement, mode, true, false); } void Scene::registerVariables() { registerVariable(this->skybox_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_applySkybox)); registerVariable(this->ambientLight_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_applyAmbientLight)); registerVariable(this->negativeWorldRange_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_negativeWorldRange)); registerVariable(this->positiveWorldRange_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_positiveWorldRange)); registerVariable(this->gravity_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_gravity)); registerVariable(this->bHasPhysics_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_hasPhysics)); registerVariable(this->bShadows_, VariableDirection::ToClient, new NetworkCallback(this, &Scene::networkcallback_applyShadows)); } void Scene::setNegativeWorldRange(const Vector3& range) { if (range.length() < 10.0f) { orxout(internal_warning) << "Setting the negative world range to a very small value: " << multi_cast(range) << endl; } if (this->hasPhysics()) { orxout(internal_warning) << "Attempting to set the physical world range at run time. " << "This causes a complete physical reload which might take some time." << endl; this->setPhysicalWorld(false); this->negativeWorldRange_ = range; this->setPhysicalWorld(true); } else this->negativeWorldRange_ = range; } void Scene::setPositiveWorldRange(const Vector3& range) { if (range.length() < 10.0f) { orxout(internal_warning) << "Setting the positive world range to a very small value: " << multi_cast(range) << endl; } if (this->hasPhysics()) { orxout(internal_warning) << "Attempting to set the physical world range at run time. " << "This causes a complete physical reload which might take some time." << endl; this->setPhysicalWorld(false); this->positiveWorldRange_ = range; this->setPhysicalWorld(true); } else this->positiveWorldRange_ = range; } void Scene::setGravity(const Vector3& gravity) { this->gravity_ = gravity; if (this->hasPhysics()) this->physicalWorld_->setGravity(multi_cast(this->gravity_)); } void Scene::setPhysicalWorld(bool wantPhysics) { this->bHasPhysics_ = wantPhysics; if (wantPhysics && !hasPhysics()) { // Note: These are all little known default classes and values. // It would require further investigation to properly dertermine the right choices. this->broadphase_ = new bt32BitAxisSweep3( multi_cast(this->negativeWorldRange_), multi_cast(this->positiveWorldRange_)); this->collisionConfig_ = new btDefaultCollisionConfiguration(); this->dispatcher_ = new btCollisionDispatcher(this->collisionConfig_); this->solver_ = new btSequentialImpulseConstraintSolver(); this->physicalWorld_ = new btDiscreteDynamicsWorld(this->dispatcher_, this->broadphase_, this->solver_, this->collisionConfig_); this->physicalWorld_->setGravity(multi_cast(this->gravity_)); if (GameMode::showsGraphics() && this->sceneManager_) { this->debugDrawer_ = new BulletDebugDrawer(this->sceneManager_); this->physicalWorld_->setDebugDrawer(this->debugDrawer_); } // also set the collision callback variable. // Note: This is a global variable which we assign a static function. // TODO: Check whether this (or anything about Bullet) works with multiple physics engine instances. gContactAddedCallback = &Scene::collisionCallback; } else if (!wantPhysics && hasPhysics()) { // Remove all WorldEntities and shove them to the queue since they would still like to be in a physical world. for (WorldEntity* object : this->physicalObjects_) { this->physicalWorld_->removeRigidBody(object->physicalBody_); this->physicalObjectQueue_.insert(object); } this->physicalObjects_.clear(); delete this->debugDrawer_; delete this->physicalWorld_; delete this->solver_; delete this->dispatcher_; delete this->collisionConfig_; delete this->broadphase_; this->physicalWorld_ = nullptr; this->solver_ = nullptr; this->dispatcher_ = nullptr; this->collisionConfig_ = nullptr; this->broadphase_ = nullptr; } } void Scene::tick(float dt) { if (!GameMode::showsGraphics()) { // We need to update the scene nodes if we don't render this->rootSceneNode_->_update(true, false); } if (this->hasPhysics()) { // TODO: This here is bad practice! It will slow down the first tick() by ages. // Rather have an initialise() method as well, called by the Level after everything has been loaded. if (this->physicalObjectQueue_.size() > 0) { // Add all scheduled WorldEntities for (WorldEntity* object : this->physicalObjectQueue_) { this->physicalWorld_->addRigidBody(object->physicalBody_); this->physicalObjects_.insert(object); } this->physicalObjectQueue_.clear(); } // Note: 60 means that Bullet will do physics correctly down to 1 frames per seconds. // Under that mark, the simulation will "loose time" and get unusable. this->bIsUpdatingPhysics_ = true; this->physicalWorld_->stepSimulation(dt, 60); this->bIsUpdatingPhysics_ = false; if (this->bDebugDrawPhysics_) this->physicalWorld_->debugDrawWorld(); } } void Scene::setSkybox(const std::string& skybox) { try { if (GameMode::showsGraphics() && this->sceneManager_) this->sceneManager_->setSkyBox(true, skybox); } catch (const Ogre::Exception&) { orxout(internal_error) << "Could not load skybox '" << skybox << "':" << endl; orxout(internal_error) << Exception::handleMessage() << endl; } this->skybox_ = skybox; } void Scene::setAmbientLight(const ColourValue& colour) { if (GameMode::showsGraphics() && this->sceneManager_) this->sceneManager_->setAmbientLight(colour); this->ambientLight_ = colour; } void Scene::setShadow(bool bShadow) { if (GameMode::showsGraphics() && this->sceneManager_) { if (bShadow) this->sceneManager_->setShadowTechnique(Ogre::SHADOWTYPE_STENCIL_ADDITIVE); else this->sceneManager_->setShadowTechnique(Ogre::SHADOWTYPE_NONE); } this->bShadows_ = bShadow; } void Scene::addObject(BaseObject* object) { this->objects_.push_back(object); } BaseObject* Scene::getObject(unsigned int index) const { unsigned int i = 0; for (BaseObject* object : this->objects_) { if (i == index) return object; ++i; } return nullptr; } void Scene::addPhysicalObject(WorldEntity* object) { if (object) { std::set::iterator it = this->physicalObjects_.find(object); if (it != this->physicalObjects_.end()) return; this->physicalObjectQueue_.insert(object); } } void Scene::removePhysicalObject(WorldEntity* object) { // check queue first std::set::iterator it = this->physicalObjectQueue_.find(object); if (it != this->physicalObjectQueue_.end()) { this->physicalObjectQueue_.erase(it); return; } it = this->physicalObjects_.find(object); if (it == this->physicalObjects_.end()) return; this->physicalObjects_.erase(it); if (this->hasPhysics()) this->physicalWorld_->removeRigidBody(object->physicalBody_); } /*static*/ bool Scene::collisionCallback(btManifoldPoint& cp, const btCollisionObject* colObj0, int partId0, int index0, const btCollisionObject* colObj1, int partId1, int index1) { // get the WorldEntity pointers StrongPtr object0 = static_cast(colObj0->getUserPointer()); StrongPtr object1 = static_cast(colObj1->getUserPointer()); // get the CollisionShape pointers const btCollisionShape* cs0 = colObj0->getCollisionShape(); const btCollisionShape* cs1 = colObj1->getCollisionShape(); // false means that bullet will assume we didn't modify the contact bool modified = false; if (object0->isCollisionCallbackActive()) modified |= object0->collidesAgainst(object1, cs1, cp); if (object1->isCollisionCallbackActive()) modified |= object1->collidesAgainst(object0, cs0, cp); return modified; } void Scene::setDebugDrawPhysics(bool bDraw, bool bFill, float fillAlpha) { this->bDebugDrawPhysics_ = bDraw; if (this->debugDrawer_) this->debugDrawer_->configure(bFill, fillAlpha); } /*static*/ void Scene::consoleCommand_debugDrawPhysics(bool bDraw, bool bFill, float fillAlpha) { for (Scene* scene : ObjectList()) scene->setDebugDrawPhysics(bDraw, bFill, fillAlpha); } }