#include "SubScene.h" #include "gameMode.h" #include "console/script.h" #include "scene/sceneRenderState.h" #include "renderInstance/renderPassManager.h" #include "gfx/gfxDrawUtil.h" #include "gfx/gfxTransformSaver.h" #include "gui/editor/inspector/group.h" IMPLEMENT_CO_NETOBJECT_V1(SubScene); S32 SubScene::mUnloadTimeoutMs = 5000; SubScene::SubScene() : mLevelAssetId(StringTable->EmptyString()), mGameModesNames(StringTable->EmptyString()), mScopeDistance(-1), mLoaded(false), mGlobalLayer(false) { mNetFlags.set(Ghostable | ScopeAlways); mTypeMask |= StaticObjectType; } SubScene::~SubScene() { } bool SubScene::onAdd() { if (!Parent::onAdd()) return false; return true; } void SubScene::onRemove() { if (isClientObject()) removeFromScene(); Parent::onRemove(); } void SubScene::initPersistFields() { addGroup("SubScene"); addField("isGlobalLayer", TypeBool, Offset(mGlobalLayer, SubScene), ""); INITPERSISTFIELD_LEVELASSET(Level, SubScene, "The level asset to load."); addField("gameModes", TypeGameModeList, Offset(mGameModesNames, SubScene), "The game modes that this subscene is associated with."); endGroup("SubScene"); Parent::initPersistFields(); } void SubScene::consoleInit() { Parent::consoleInit(); Con::addVariable("$SubScene::UnloadTimeoutMS", TypeBool, &SubScene::mUnloadTimeoutMs, "The amount of time in milliseconds it takes for a SubScene to be unloaded if it's inactive.\n" "@ingroup Editors\n"); } void SubScene::addObject(SimObject* object) { SceneObject::addObject(object); } void SubScene::removeObject(SimObject* object) { SceneObject::removeObject(object); } U32 SubScene::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) { U32 retMask = Parent::packUpdate(conn, mask, stream); stream->writeFlag(mGlobalLayer); return retMask; } void SubScene::unpackUpdate(NetConnection* conn, BitStream* stream) { Parent::unpackUpdate(conn, stream); mGlobalLayer = stream->readFlag(); } void SubScene::onInspect(GuiInspector* inspector) { Parent::onInspect(inspector); //Put the SubScene group before everything that'd be SubScene-effecting, for orginazational purposes GuiInspectorGroup* subsceneGrp = inspector->findExistentGroup(StringTable->insert("SubScene")); if (!subsceneGrp) return; GuiControl* stack = dynamic_cast(subsceneGrp->findObjectByInternalName(StringTable->insert("Stack"))); //Save button GuiInspectorField* saveFieldGui = subsceneGrp->createInspectorField(); saveFieldGui->init(inspector, subsceneGrp); saveFieldGui->setSpecialEditField(true); saveFieldGui->setTargetObject(this); StringTableEntry fldnm = StringTable->insert("SaveSubScene"); saveFieldGui->setSpecialEditVariableName(fldnm); saveFieldGui->setInspectorField(NULL, fldnm); saveFieldGui->setDocs(""); stack->addObject(saveFieldGui); GuiButtonCtrl* saveButton = new GuiButtonCtrl(); saveButton->registerObject(); saveButton->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile"); saveButton->setText("Save SubScene"); saveButton->resize(Point2I::Zero, saveFieldGui->getExtent()); saveButton->setHorizSizing(GuiControl::horizResizeWidth); saveButton->setVertSizing(GuiControl::vertResizeHeight); char szBuffer[512]; dSprintf(szBuffer, 512, "%d.save();", this->getId()); saveButton->setConsoleCommand(szBuffer); saveFieldGui->addObject(saveButton); } void SubScene::inspectPostApply() { Parent::inspectPostApply(); setMaskBits(-1); } bool SubScene::testBox(const Box3F& testBox) { if (mGlobalLayer) return true; bool isOverlapped = getWorldBox().isOverlapped(testBox); return getWorldBox().isOverlapped(testBox); } void SubScene::write(Stream& stream, U32 tabStop, U32 flags) { MutexHandle handle; handle.lock(mMutex); // export selected only? if ((flags & SelectedOnly) && !isSelected()) { for (U32 i = 0; i < size(); i++) (*this)[i]->write(stream, tabStop, flags); return; } stream.writeTabs(tabStop); char buffer[2048]; const U32 bufferWriteLen = dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() && !(flags & NoName) ? getName() : ""); stream.write(bufferWriteLen, buffer); writeFields(stream, tabStop + 1); //The only meaningful difference between this and simSet for writing is we skip the children, since they're just the levelAsset contents stream.writeTabs(tabStop); stream.write(4, "};\r\n"); } void SubScene::processTick(const Move* move) { } void SubScene::_onFileChanged(const Torque::Path& path) { if(mLevelAsset.isNull() || Torque::Path(mLevelAsset->getLevelPath()) != path) return; AssertFatal(path == mLevelAsset->getLevelPath(), "Prefab::_onFileChanged - path does not match filename."); _closeFile(false); _loadFile(false); setMaskBits(U32_MAX); } void SubScene::_closeFile(bool removeFileNotify) { AssertFatal(isServerObject(), "Trying to close out a subscene file on the client is bad!"); U32 count = size(); for (SimSetIterator itr(this); *itr; ++itr) { SimObject* child = dynamic_cast(*itr); if (child) child->safeDeleteObject(); } if (removeFileNotify && mLevelAsset.notNull() && mLevelAsset->getLevelPath() != StringTable->EmptyString()) { Torque::FS::RemoveChangeNotification(mLevelAsset->getLevelPath(), this, &SubScene::_onFileChanged); } mGameModesList.clear(); } void SubScene::_loadFile(bool addFileNotify) { AssertFatal(isServerObject(), "Trying to load a SubScene file on the client is bad!"); if(mLevelAsset.isNull() || mLevelAsset->getLevelPath() == StringTable->EmptyString()) return; String evalCmd = String::ToString("exec(\"%s\");", mLevelAsset->getLevelPath()); String instantGroup = Con::getVariable("InstantGroup"); Con::setIntVariable("InstantGroup", this->getId()); Con::evaluate((const char*)evalCmd.c_str(), false, mLevelAsset->getLevelPath()); Con::setVariable("InstantGroup", instantGroup.c_str()); if (addFileNotify) Torque::FS::AddChangeNotification(mLevelAsset->getLevelPath(), this, &SubScene::_onFileChanged); } void SubScene::load() { mStartUnloadTimerMS = -1; //reset unload timers //no need to load multiple times if (mLoaded) return; _loadFile(true); mLoaded = true; GameMode::findGameModes(mGameModesNames, &mGameModesList); for (U32 i = 0; i < mGameModesList.size(); i++) { mGameModesList[i]->onSubsceneLoaded_callback(); } } void SubScene::unload() { if (!mLoaded) return; if (isSelected()) { mStartUnloadTimerMS = Sim::getCurrentTime(); return; //if a child is selected, then we don't want to unload } //scan down through our child objects, see if any are marked as selected, //if so, skip unloading and reset the timer for (SimSetIterator itr(this); *itr; ++itr) { SimGroup* childGrp = dynamic_cast(*itr); if (childGrp && childGrp->isSelected()) { mStartUnloadTimerMS = Sim::getCurrentTime(); return; //if a child is selected, then we don't want to unload } for (SimSetIterator cldItr(childGrp); *cldItr; ++cldItr) { SimObject* chldChld = dynamic_cast(*cldItr); if (chldChld && chldChld->isSelected()) { mStartUnloadTimerMS = Sim::getCurrentTime(); return; //if a child is selected, then we don't want to unload } } } _closeFile(true); mLoaded = false; for (U32 i = 0; i < mGameModesList.size(); i++) { mGameModesList[i]->onSubsceneUnloaded_callback(); } } bool SubScene::save() { //if there's nothing TO save, don't bother if (size() == 0 || !isLoaded()) return false; if (mLevelAsset.isNull()) return false; //If we're flagged for unload, push back the unload timer so we can't accidentally trip be saving partway through an unload if (mStartUnloadTimerMS != -1) mStartUnloadTimerMS = Sim::getCurrentTime(); StringTableEntry levelPath = mLevelAsset->getLevelPath(); for (SimGroup::iterator itr = begin(); itr != end(); itr++) { //Just in case there's a valid callback the scene object would like to invoke for saving SceneObject* gc = dynamic_cast(*itr); if (gc) { gc->onSaving_callback(mLevelAssetId); } SimObject* sO = static_cast(*itr); if (!sO->save(levelPath)) { Con::errorf("SubScene::save() - error, failed to write object %s to file: %s", sO->getIdString(), levelPath); return false; } } //process our gameModeList and write it out to the levelAsset for metadata stashing bool saveSuccess = false; //Get the level asset if (mLevelAsset.isNull()) return saveSuccess; //update the gamemode list as well mLevelAsset->setDataField(StringTable->insert("gameModesNames"), NULL, StringTable->insert(mGameModesNames)); //Finally, save saveSuccess = mLevelAsset->saveAsset(); return saveSuccess; } void SubScene::_onSelected() { if (!isLoaded() && isServerObject()) load(); } void SubScene::_onUnselected() { } void SubScene::prepRenderImage(SceneRenderState* state) { // only render if selected or render flag is set if (/*!smRenderTriggers && */!isSelected()) return; ObjectRenderInst* ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind(this, &SubScene::renderObject); ri->type = RenderPassManager::RIT_Editor; ri->translucentSort = true; ri->defaultKey = 1; state->getRenderPass()->addInst(ri); } void SubScene::renderObject(ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* overrideMat) { if (overrideMat) return; GFXStateBlockDesc desc; desc.setZReadWrite(true, false); desc.setBlend(true); // Trigger polyhedrons are set up with outward facing normals and CCW ordering // so can't enable backface culling. desc.setCullMode(GFXCullNone); GFXTransformSaver saver; MatrixF mat = getRenderTransform(); GFX->multWorld(mat); GFXDrawUtil* drawer = GFX->getDrawUtil(); //Box3F scale = getScale() //Box3F bounds = Box3F(-m) Point3F scale = getScale(); Box3F bounds = Box3F(-scale/2, scale/2); ColorI boundsColor = ColorI(135, 206, 235, 50); if (mGlobalLayer) boundsColor = ColorI(200, 100, 100, 25); else if (mLoaded) boundsColor = ColorI(50, 200, 50, 50); drawer->drawCube(desc, bounds, boundsColor); // Render wireframe. desc.setFillModeWireframe(); drawer->drawCube(desc, bounds, ColorI::BLACK); } DefineEngineMethod(SubScene, save, bool, (),, "Save out the subScene.\n") { return object->save(); } DefineEngineMethod(SubScene, load, void, (), , "Loads the SubScene's level file.\n") { object->load(); } DefineEngineMethod(SubScene, unload, void, (), , "Unloads the SubScene's level file.\n") { object->unload(); }