//----------------------------------------------------------------------------- // Copyright (c) 2026 Jeff Raab, LLC // Portions Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "T3D/examples/shapeDatablockExample.h" #include "math/mathIO.h" #include "sim/netConnection.h" #include "scene/sceneRenderState.h" #include "console/consoleTypes.h" #include "core/resourceManager.h" #include "core/stream/bitStream.h" #include "gfx/gfxTransformSaver.h" #include "renderInstance/renderPassManager.h" #include "lighting/lightQuery.h" IMPLEMENT_CO_DATABLOCK_V1(ShapeDatablockExampleData); ShapeDatablockExampleData::ShapeDatablockExampleData() { shapeAssetRef.assetPtr.registerRefreshNotify(this); } ShapeDatablockExampleData::ShapeDatablockExampleData(const ShapeDatablockExampleData& other, bool temp_clone) : GameBaseData(other, temp_clone) { shapeAssetRef = other.shapeAssetRef; } ShapeDatablockExampleData::~ShapeDatablockExampleData() { } void ShapeDatablockExampleData::initPersistFields() { docsURL; addGroup("Shapes"); ADD_FIELD("shapeAsset", TypeShapeAssetRef, Offset(shapeAssetRef, ShapeDatablockExampleData)) .doc("The shape asset to render."); endGroup("Shapes"); Parent::initPersistFields(); } bool ShapeDatablockExampleData::preload(bool server, String& errorStr) { if (!shapeAssetRef.isNull()) { Resource shape = shapeAssetRef.assetPtr->getShapeResource(); if (shape) { TSShapeInstance* pDummy = new TSShapeInstance(shape, !server); delete pDummy; if (!server && !shapeAssetRef.assetPtr->preloadMaterialList() && NetConnection::filesWereDownloaded()) return false; } else { errorStr = String::ToString("ShapeDatablockExampleData(%s)::preload: Couldn't load shape asset\"%s\"", getName(), shapeAssetRef.assetId); return false; } } return true; } void ShapeDatablockExampleData::packData(BitStream* stream) { Parent::packData(stream); AssetDatabase.packDataAsset(stream, shapeAssetRef.assetId); } void ShapeDatablockExampleData::unpackData(BitStream* stream) { Parent::unpackData(stream); shapeAssetRef = AssetDatabase.unpackDataAsset(stream); } IMPLEMENT_CO_NETOBJECT_V1(ShapeDatablockExample); ConsoleDocClass(ShapeDatablockExample, "@brief An example scene object which renders a shape that utilizes a datablock.\n\n" "This class implements a basic SceneObject that can exist in the world at a " "3D position and render itself. There are several valid ways to render an " "object in Torque. This class makes use of the 'TS' (three space) shape " "system, while containing the shape data to be rendered in a datablock. " "TS manages loading the various mesh formats supported by Torque as " "well was rendering those meshes (including LOD and animation...though this " "example doesn't include any animation over time).\n\n" "See the C++ code for implementation details.\n\n" "@ingroup Examples\n"); //----------------------------------------------------------------------------- // Object setup and teardown //----------------------------------------------------------------------------- ShapeDatablockExample::ShapeDatablockExample() { // Flag this object so that it will always // be sent across the network to clients mNetFlags.set(Ghostable | ScopeAlways); // Set it as a "static" object. mTypeMask |= StaticObjectType | StaticShapeObjectType; mDataBlock = NULL; // Make sure to initialize our TSShapeInstance to NULL mShapeInstance = NULL; } ShapeDatablockExample::~ShapeDatablockExample() { } //----------------------------------------------------------------------------- // Object Editing //----------------------------------------------------------------------------- void ShapeDatablockExample::initPersistFields() { docsURL; Parent::initPersistFields(); } bool ShapeDatablockExample::onAdd() { if (!Parent::onAdd()) return false; // Set up a 1x1x1 bounding box mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f), Point3F(0.5f, 0.5f, 0.5f)); resetWorldBox(); // Add this object to the scene addToScene(); if (mDataBlock && !onNewDataBlock(mDataBlock, false)) return false; return true; } bool ShapeDatablockExample::onNewDataBlock(GameBaseData* dptr, bool reload) { // EDITOR FEATURE: Remove us from old datablock's reload signal and // add us to the new one. if (!reload) { if (mDataBlock) mDataBlock->mReloadSignal.remove(this, &ShapeDatablockExample::_onDatablockModified); if (dptr) dptr->mReloadSignal.notify(this, &ShapeDatablockExample::_onDatablockModified); } mDataBlock = dynamic_cast(dptr); if (!mDataBlock) return false; if (mShapeInstance) SAFE_DELETE(mShapeInstance); if (mDataBlock->shapeAssetRef.isNull()) return false; Resource shape = mDataBlock->shapeAssetRef.assetPtr->getShapeResource(); mShapeInstance = new TSShapeInstance(shape); #ifdef TORQUE_AFX_ENABLED // Don't set mask when new datablock is a temp-clone. if (mDataBlock->isTempClone()) return true; #endif setMaskBits(DataBlockMask); return true; } void ShapeDatablockExample::_onDatablockModified() { AssertFatal(mDataBlock, "ShapeDatablockExample::onDatablockModified - mDataBlock is NULL."); onNewDataBlock(mDataBlock, true); } void ShapeDatablockExample::onRemove() { // Remove this object from the scene removeFromScene(); // Remove our TSShapeInstance if (mShapeInstance) SAFE_DELETE(mShapeInstance); Parent::onRemove(); } void ShapeDatablockExample::setTransform(const MatrixF& mat) { // Let SceneObject handle all of the matrix manipulation Parent::setTransform(mat); // Dirty our network mask so that the new transform gets // transmitted to the client object setMaskBits(TransformMask); } U32 ShapeDatablockExample::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) { // Allow the Parent to get a crack at writing its info U32 retMask = Parent::packUpdate(conn, mask, stream); // Write our transform information if (stream->writeFlag(mask & TransformMask)) { mathWrite(*stream, getTransform()); mathWrite(*stream, getScale()); } // Write out any of the updated editable properties if (stream->writeFlag(mask & UpdateMask)) { } return retMask; } void ShapeDatablockExample::unpackUpdate(NetConnection* conn, BitStream* stream) { // Let the Parent read any info it sent Parent::unpackUpdate(conn, stream); if (stream->readFlag()) // TransformMask { mathRead(*stream, &mObjToWorld); mathRead(*stream, &mObjScale); setTransform(mObjToWorld); } if (stream->readFlag()) // UpdateMask { } } void ShapeDatablockExample::prepRenderImage(SceneRenderState* state) { // Make sure we have a TSShapeInstance if (!mShapeInstance) return; // Calculate the distance of this object from the camera Point3F cameraOffset; getRenderTransform().getColumn(3, &cameraOffset); cameraOffset -= state->getDiffuseCameraPosition(); F32 dist = cameraOffset.len(); if (dist < 0.01f) dist = 0.01f; // Set up the LOD for the shape F32 invScale = (1.0f / getMax(getMax(mObjScale.x, mObjScale.y), mObjScale.z)); mShapeInstance->setDetailFromDistance(state, dist * invScale); // Make sure we have a valid level of detail if (mShapeInstance->getCurrentDetail() < 0) return; // GFXTransformSaver is a handy helper class that restores // the current GFX matrices to their original values when // it goes out of scope at the end of the function GFXTransformSaver saver; // Set up our TS render state TSRenderState rdata; rdata.setSceneState(state); rdata.setFadeOverride(1.0f); // We might have some forward lit materials // so pass down a query to gather lights. LightQuery query; query.init(getWorldSphere()); rdata.setLightQuery(&query); // Set the world matrix to the objects render transform MatrixF mat = getRenderTransform(); mat.scale(mObjScale); GFX->setWorldMatrix(mat); // Animate the the shape mShapeInstance->animate(); // Allow the shape to submit the RenderInst(s) for itself mShapeInstance->render(rdata); }