mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
437 lines
11 KiB
C++
437 lines
11 KiB
C++
#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<GuiControl*>(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<SimObject*>(*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<SimGroup*>(*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<SimObject*>(*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++)
|
|
{
|
|
//Inform our objects we're saving, so if they do any special stuff
|
|
//they can do it before the actual write-out
|
|
SimGroup* sg = dynamic_cast<SimGroup*>(*itr);
|
|
if (sg)
|
|
{
|
|
ConsoleValue vars[3];
|
|
vars[2].setString(mLevelAssetId);
|
|
sg->callOnChildren("onSaving", 3, vars);
|
|
}
|
|
|
|
SceneObject* scO = dynamic_cast<SceneObject*>(*itr);
|
|
if (scO)
|
|
{
|
|
scO->onSaving_callback(mLevelAssetId);
|
|
}
|
|
|
|
SimObject* sO = static_cast<SimObject*>(*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<ObjectRenderInst>();
|
|
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();
|
|
}
|