Torque3D/Engine/source/gui/editor/guiShapeEdPreview.cpp
JeffR 976c0bca79 Fixed uninitialized values for renderMeshExample and renderShapeExample which would cause a crash on creation
Added utility method to prefab to be able to get the internal simGroup that contains it's children
Adjusted logic for mounting items in GuiShapeEdPreview to utilize assetIds for the shapes
Moved the Asset and AssetBrowser editor settings populate functions to the AssetBrowser script to better organize things
Fixed command usage for General, Player and Observer spawn point creator entries to use the correct callback commands
Fixed logic for creator callback commands that don't just route through the class name based structure
Added RMB context menu actions for opening asset file or folder locations in OS file explorer
Fixed lookup of animation assets when editing a shape's animations in the shape editor so it provides the assetId of the anim if it exists
Fixes handling of mounting in the shape editor so it utilizes assets and the asset browser like everything else
2022-04-06 01:08:20 -05:00

1949 lines
62 KiB
C++

//-----------------------------------------------------------------------------
// 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 "console/consoleTypes.h"
#include "console/console.h"
#include "console/engineAPI.h"
#include "gui/core/guiCanvas.h"
#include "gui/editor/guiShapeEdPreview.h"
#include "renderInstance/renderPassManager.h"
#include "lighting/lightManager.h"
#include "lighting/lightInfo.h"
#include "core/resourceManager.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "collision/concretePolyList.h"
#include "T3D/assets/ShapeAsset.h"
#include "T3D/assets/ShapeAnimationAsset.h"
#ifdef TORQUE_COLLADA
#include "collision/optimizedPolyList.h"
#include "ts/collada/colladaUtils.h"
#endif
static const F32 sMoveScaler = 50.0f;
static const F32 sZoomScaler = 200.0f;
static const S32 sNodeRectSize = 16;
IMPLEMENT_CONOBJECT( GuiShapeEdPreview );
ConsoleDocClass( GuiShapeEdPreview,
"@brief This control provides the 3D view for the Shape Editor tool, and is "
"not intended for general purpose use.\n"
"@ingroup GuiControls\n"
"@internal"
);
IMPLEMENT_CALLBACK( GuiShapeEdPreview, onThreadPosChanged, void, ( F32 pos, bool inTransition ), ( pos, inTransition),
"Called when the position of the active thread has changed, such as during "
"playback." );
GuiShapeEdPreview::GuiShapeEdPreview()
: mOrbitDist( 5.0f ),
mMoveSpeed ( 1.0f ),
mZoomSpeed ( 1.0f ),
mGridDimension( 30, 30 ),
mModel( NULL ),
mModelName(StringTable->EmptyString()),
mRenderGhost( false ),
mRenderNodes( false ),
mRenderBounds( false ),
mRenderObjBox( false ),
mRenderColMeshes( false ),
mRenderMounts( true ),
mSunDiffuseColor( 255, 255, 255, 255 ),
mSelectedNode( -1 ),
mSunAmbientColor( 140, 140, 140, 255 ),
mHoverNode( -1 ),
mSelectedObject( -1 ),
mUsingAxisGizmo( false ),
mSelectedObjDetail( 0 ),
mEditingSun( false ),
mGizmoDragID( 0 ),
mTimeScale( 1.0f ),
mActiveThread( -1 ),
mFakeSun( NULL ),
mLastRenderTime( 0 ),
mCameraRot( 0, 0, 3.9f ),
mSunRot( 45.0f, 0, 135.0f ),
mRenderCameraAxes( false ),
mOrbitPos( 0, 0, 0 ),
mFixedDetail( true ),
mCurrentDL( 0 ),
mDetailSize( 0 ),
mDetailPolys( 0 ),
mPixelSize( 0 ),
mNumMaterials( 0 ),
mNumDrawCalls( 0 ),
mNumBones( 0 ),
mNumWeights( 0 ),
mColMeshes( 0 ),
mColPolys( 0 )
{
mActive = true;
// By default don't do dynamic reflection
// updates for this viewport.
mReflectPriority = 0.0f;
}
GuiShapeEdPreview::~GuiShapeEdPreview()
{
SAFE_DELETE( mModel );
SAFE_DELETE( mFakeSun );
}
void GuiShapeEdPreview::initPersistFields()
{
addGroup( "Rendering" );
addField( "editSun", TypeBool, Offset( mEditingSun, GuiShapeEdPreview ),
"If true, dragging the gizmo will rotate the sun direction" );
addField( "selectedNode", TypeS32, Offset( mSelectedNode, GuiShapeEdPreview ),
"Index of the selected node, or -1 if none" );
addField( "selectedObject", TypeS32, Offset( mSelectedObject, GuiShapeEdPreview ),
"Index of the selected object, or -1 if none" );
addField( "selectedObjDetail", TypeS32, Offset( mSelectedObjDetail, GuiShapeEdPreview ),
"Index of the selected object detail mesh, or 0 if none" );
addField( "gridDimension", TypePoint2I, Offset( mGridDimension, GuiShapeEdPreview ),
"Grid dimensions (number of rows and columns) in the form \"rows cols\"" );
addField( "renderGrid", TypeBool, Offset( mRenderGridPlane, EditTSCtrl ),
"Flag indicating whether to draw the grid" );
addField( "renderNodes", TypeBool, Offset( mRenderNodes, GuiShapeEdPreview ),
"Flag indicating whether to render the shape nodes" );
addField( "renderGhost", TypeBool, Offset( mRenderGhost, GuiShapeEdPreview ),
"Flag indicating whether to render the shape in 'ghost' mode (transparent)" );
addField( "renderBounds", TypeBool, Offset( mRenderBounds, GuiShapeEdPreview ),
"Flag indicating whether to render the shape bounding box" );
addField( "renderObjBox", TypeBool, Offset( mRenderObjBox, GuiShapeEdPreview ),
"Flag indicating whether to render the selected object's bounding box" );
addField( "renderColMeshes", TypeBool, Offset( mRenderColMeshes, GuiShapeEdPreview ),
"Flag indicating whether to render the shape's collision geometry" );
addField( "renderMounts", TypeBool, Offset( mRenderMounts, GuiShapeEdPreview ),
"Flag indicating whether to render mounted objects" );
endGroup( "Rendering" );
addGroup( "Sun" );
addProtectedField( "sunDiffuse", TypeColorI, Offset( mSunDiffuseColor, GuiShapeEdPreview ), &setFieldSunDiffuse, &defaultProtectedGetFn,
"Ambient color for the sun" );
addProtectedField( "sunAmbient", TypeColorI, Offset( mSunAmbientColor, GuiShapeEdPreview ), &setFieldSunAmbient, &defaultProtectedGetFn,
"Diffuse color for the sun" );
addProtectedField( "sunAngleX", TypeF32, Offset( mSunRot.x, GuiShapeEdPreview ), &setFieldSunAngleX, &defaultProtectedGetFn,
"X-axis rotation angle for the sun" );
addProtectedField( "sunAngleZ", TypeF32, Offset( mSunRot.z, GuiShapeEdPreview ), &setFieldSunAngleZ, &defaultProtectedGetFn,
"Z-axis rotation angle for the sun" );
endGroup( "Sun" );
addGroup( "Animation" );
addField( "activeThread", TypeS32, Offset( mActiveThread, GuiShapeEdPreview ),
"Index of the active thread, or -1 if none" );
addProtectedField( "threadPos", TypeF32, 0, &setFieldThreadPos, &getFieldThreadPos,
"Current position of the active thread (0-1)" );
addProtectedField( "threadDirection", TypeS32, 0, &setFieldThreadDir, &getFieldThreadDir,
"Playback direction of the active thread" );
addProtectedField( "threadPingPong", TypeBool, 0, &setFieldThreadPingPong, &getFieldThreadPingPong,
"'PingPong' mode of the active thread" );
endGroup( "Animation" );
addGroup( "Detail Stats" );
addField( "fixedDetail", TypeBool, Offset( mFixedDetail, GuiShapeEdPreview ),
"If false, the current detail is selected based on camera distance" );
addField( "orbitDist", TypeF32, Offset( mOrbitDist, GuiShapeEdPreview ),
"The current distance from the camera to the model" );
addProtectedField( "currentDL", TypeS32, Offset( mCurrentDL, GuiShapeEdPreview ), &setFieldCurrentDL, &defaultProtectedGetFn,
"The current detail level" );
addProtectedField( "detailSize", TypeS32, Offset( mDetailSize, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The size of the current detail" );
addProtectedField( "detailPolys", TypeS32, Offset( mDetailPolys, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"Number of polygons in the current detail" );
addProtectedField( "pixelSize", TypeF32, Offset( mPixelSize, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The current pixel size of the model" );
addProtectedField( "numMaterials", TypeS32, Offset( mNumMaterials, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The number of materials in the current detail level" );
addProtectedField( "numDrawCalls", TypeS32, Offset( mNumDrawCalls, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The number of draw calls in the current detail level" );
addProtectedField( "numBones", TypeS32, Offset( mNumBones, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The number of bones in the current detail level (skins only)" );
addProtectedField( "numWeights", TypeS32, Offset( mNumWeights, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The number of vertex weights in the current detail level (skins only)" );
addProtectedField( "colMeshes", TypeS32, Offset( mColMeshes, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The number of collision meshes in the shape" );
addProtectedField( "colPolys", TypeS32, Offset( mColPolys, GuiShapeEdPreview ), &defaultProtectedSetFn, &defaultProtectedGetFn,
"The total number of collision polygons (all meshes) in the shape" );
endGroup( "Detail Stats" );
Parent::initPersistFields();
}
bool GuiShapeEdPreview::setFieldCurrentDL( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui )
gui->setCurrentDetail( mFloor( dAtof( data ) + 0.5f ) );
return false;
}
bool GuiShapeEdPreview::setFieldSunDiffuse( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui )
{
Con::setData( TypeColorI, &gui->mSunDiffuseColor, 0, 1, &data );
gui->updateSun();
}
return false;
}
bool GuiShapeEdPreview::setFieldSunAmbient( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui )
{
Con::setData( TypeColorI, &gui->mSunAmbientColor, 0, 1, &data );
gui->updateSun();
}
return false;
}
bool GuiShapeEdPreview::setFieldSunAngleX( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui )
{
Con::setData( TypeF32, &gui->mSunRot.x, 0, 1, &data );
gui->updateSun();
}
return false;
}
bool GuiShapeEdPreview::setFieldSunAngleZ( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui )
{
Con::setData( TypeF32, &gui->mSunRot.z, 0, 1, &data );
gui->updateSun();
}
return false;
}
bool GuiShapeEdPreview::setFieldThreadPos( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui && ( gui->mActiveThread >= 0 ) && gui->mThreads[gui->mActiveThread].key )
gui->mModel->setPos( gui->mThreads[gui->mActiveThread].key, dAtof( data ) );
return false;
}
const char *GuiShapeEdPreview::getFieldThreadPos( void *object, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui && ( gui->mActiveThread >= 0 ) && gui->mThreads[gui->mActiveThread].key )
return Con::getFloatArg( gui->mModel->getPos( gui->mThreads[gui->mActiveThread].key ) );
else
return "0";
}
bool GuiShapeEdPreview::setFieldThreadDir( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui && ( gui->mActiveThread >= 0 ) )
{
Thread& thread = gui->mThreads[gui->mActiveThread];
Con::setData( TypeS32, &(thread.direction), 0, 1, &data );
if ( thread.key )
gui->mModel->setTimeScale( thread.key, gui->mTimeScale * thread.direction );
}
return false;
}
const char *GuiShapeEdPreview::getFieldThreadDir( void *object, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui && ( gui->mActiveThread >= 0 ) )
return Con::getIntArg( gui->mThreads[gui->mActiveThread].direction );
else
return "0";
}
bool GuiShapeEdPreview::setFieldThreadPingPong( void *object, const char *index, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui && ( gui->mActiveThread >= 0 ) )
Con::setData( TypeBool, &(gui->mThreads[gui->mActiveThread].pingpong), 0, 1, &data );
return false;
}
const char *GuiShapeEdPreview::getFieldThreadPingPong( void *object, const char *data )
{
GuiShapeEdPreview* gui = static_cast<GuiShapeEdPreview*>( object );
if ( gui && ( gui->mActiveThread >= 0 ) )
return Con::getIntArg( gui->mThreads[gui->mActiveThread].pingpong );
else
return "0";
}
bool GuiShapeEdPreview::onWake()
{
if (!Parent::onWake())
return false;
if (!mFakeSun )
mFakeSun = LIGHTMGR->createLightInfo();
mFakeSun->setRange( 2000000.0f );
updateSun();
mGizmoProfile->mode = MoveMode;
return( true );
}
void GuiShapeEdPreview::setDisplayType( S32 type )
{
Parent::setDisplayType( type );
mOrthoCamTrans.set( 0, 0, 0 );
}
//-----------------------------------------------------------------------------
void GuiShapeEdPreview::setCurrentDetail(S32 dl)
{
if ( mModel )
{
TSShape* shape = mModel->getShape();
S32 smallest = shape->mSmallestVisibleDL;
shape->mSmallestVisibleDL = shape->details.size() - 1;
mModel->setCurrentDetail( dl );
shape->mSmallestVisibleDL = smallest;
// Match the camera distance to this detail if necessary
//@todo if ( !gui->mFixedDetail )
}
}
bool GuiShapeEdPreview::setObjectModel(const char* modelName)
{
SAFE_DELETE( mModel );
unmountAll();
mThreads.clear();
mActiveThread = -1;
ResourceManager::get().getChangedSignal().remove(this, &GuiShapeEdPreview::_onResourceChanged);
if (modelName && modelName[0])
{
Resource<TSShape> model = ResourceManager::get().load( modelName );
if (! bool( model ))
{
Con::warnf( avar("GuiShapeEdPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName ));
return false;
}
mModel = new TSShapeInstance( model, true );
AssertFatal( mModel, avar("GuiShapeEdPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName ));
TSShape* shape = mModel->getShape();
// Initialize camera values:
mOrbitPos = shape->center;
// Set camera move and zoom speed according to model size
mMoveSpeed = shape->mRadius / sMoveScaler;
mZoomSpeed = shape->mRadius / sZoomScaler;
// Reset node selection
mHoverNode = -1;
mSelectedNode = -1;
mSelectedObject = -1;
mSelectedObjDetail = 0;
mProjectedNodes.setSize( shape->nodes.size() );
// Reset detail stats
mCurrentDL = 0;
// the first time recording
mLastRenderTime = Platform::getVirtualMilliseconds();
mModelName = StringTable->insert(modelName);
//Now to reflect changes when the model file is changed.
ResourceManager::get().getChangedSignal().notify(this, &GuiShapeEdPreview::_onResourceChanged);
}
else
{
mModelName = StringTable->EmptyString();
}
return true;
}
bool GuiShapeEdPreview::setObjectShapeAsset(const char* assetId)
{
SAFE_DELETE(mModel);
unmountAll();
mThreads.clear();
mActiveThread = -1;
StringTableEntry modelName = StringTable->EmptyString();
if (AssetDatabase.isDeclaredAsset(assetId))
{
StringTableEntry id = StringTable->insert(assetId);
StringTableEntry assetType = AssetDatabase.getAssetType(id);
if (assetType == StringTable->insert("ShapeAsset"))
{
ShapeAsset* asset = AssetDatabase.acquireAsset<ShapeAsset>(id);
modelName = asset->getShapeFilePath();
AssetDatabase.releaseAsset(id);
}
else if (assetType == StringTable->insert("ShapeAnimationAsset"))
{
ShapeAnimationAsset* asset = AssetDatabase.acquireAsset<ShapeAnimationAsset>(id);
modelName = asset->getAnimationPath();
AssetDatabase.releaseAsset(id);
}
}
return setObjectModel(modelName);
}
void GuiShapeEdPreview::_onResourceChanged(const Torque::Path& path)
{
if (path != Torque::Path(mModelName))
return;
setObjectModel(path.getFullPath());
}
void GuiShapeEdPreview::addThread()
{
if ( mModel )
{
mThreads.increment();
if ( mActiveThread == -1 )
mActiveThread = 0;
}
}
void GuiShapeEdPreview::removeThread(S32 slot)
{
if ( slot < mThreads.size() )
{
if ( mThreads[slot].key )
mModel->destroyThread( mThreads[slot].key );
mThreads.erase( slot );
if ( mActiveThread >= mThreads.size() )
mActiveThread = mThreads.size() - 1;
}
}
void GuiShapeEdPreview::setTimeScale( F32 scale )
{
// Update time scale for all threads
mTimeScale = scale;
for ( S32 i = 0; i < mThreads.size(); i++ )
{
if ( mThreads[i].key )
mModel->setTimeScale( mThreads[i].key, mTimeScale * mThreads[i].direction );
}
}
void GuiShapeEdPreview::setActiveThreadSequence(const char* seqName, F32 duration, F32 pos, bool play)
{
if ( mActiveThread == -1 )
return;
setThreadSequence(mThreads[mActiveThread], mModel, seqName, duration, pos, play);
}
void GuiShapeEdPreview::setThreadSequence(GuiShapeEdPreview::Thread& thread, TSShapeInstance* shape, const char* seqName, F32 duration, F32 pos, bool play)
{
thread.seqName = seqName;
S32 seq = shape->getShape()->findSequence( thread.seqName );
if ( thread.key && ( shape->getSequence(thread.key) == seq ) )
return;
if ( seq == -1 )
{
// This thread is now set to an invalid sequence, so the key must be
// removed, but we keep the thread info around in case the user changes
// back to a valid sequence
if ( thread.key )
{
shape->destroyThread( thread.key );
thread.key = NULL;
}
}
else
{
// Add a TSThread key if one does not already exist
if ( !thread.key )
{
thread.key = shape->addThread();
shape->setTimeScale( thread.key, mTimeScale * thread.direction );
}
// Transition to slider or synched position?
if ( pos == -1.0f )
pos = shape->getPos( thread.key );
if ( duration == 0.0f )
{
// No transition => go straight to new sequence
shape->setSequence( thread.key, seq, pos );
}
else
{
// Get the current position if transitioning to the sync position
shape->setTimeScale( thread.key, thread.direction >= 0 ? 1 : -1 );
shape->transitionToSequence( thread.key, seq, pos, duration, play );
shape->setTimeScale( thread.key, mTimeScale * thread.direction );
}
}
}
const char* GuiShapeEdPreview::getThreadSequence() const
{
return ( mActiveThread >= 0 ) ? mThreads[mActiveThread].seqName : "";
}
void GuiShapeEdPreview::refreshThreadSequences()
{
S32 oldActive = mActiveThread;
for ( S32 i = 0; i < mThreads.size(); i++ )
{
Thread& thread = mThreads[i];
if ( !thread.key )
continue;
// Detect changed (or removed) sequence indices
if ( mModel->getSequence(thread.key) != mModel->getShape()->findSequence( thread.seqName ) )
{
mActiveThread = i;
setThreadSequence( thread, mModel, thread.seqName, 0.0f, mModel->getPos( thread.key ), false );
}
}
mActiveThread = oldActive;
}
//-----------------------------------------------------------------------------
// MOUNTING
bool GuiShapeEdPreview::mountShape(const char* shapeAssetId, const char* nodeName, const char* mountType, S32 slot)
{
if ( !shapeAssetId || !shapeAssetId[0] )
return false;
if (!AssetDatabase.isDeclaredAsset(shapeAssetId))
return false;
ShapeAsset* model = AssetDatabase.acquireAsset<ShapeAsset>(shapeAssetId);
if (model == nullptr || !model->getShapeResource())
return false;
TSShapeInstance* tsi = new TSShapeInstance(model->getShapeResource(), true );
if ( slot == -1 )
{
slot = mMounts.size();
mMounts.push_back( new MountedShape );
}
else
{
// Check if we are switching shapes
if ( mMounts[slot]->mShape->getShape() != tsi->getShape() )
{
delete mMounts[slot]->mShape;
mMounts[slot]->mShape = NULL;
mMounts[slot]->mThread.init();
}
else
{
// Keep using the existing shape
delete tsi;
tsi = mMounts[slot]->mShape;
}
}
MountedShape* mount = mMounts[slot];
mount->mShape = tsi;
if ( dStrEqual( mountType, "Wheel" ) )
mount->mType = MountedShape::Wheel;
else if ( dStrEqual( mountType, "Image" ) )
mount->mType = MountedShape::Image;
else
mount->mType = MountedShape::Object;
setMountNode( slot, nodeName);
return true;
}
void GuiShapeEdPreview::setMountNode(S32 mountSlot, const char* nodeName)
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
mount->mNode = mModel ? mModel->getShape()->findNode( nodeName ) : -1;
mount->mTransform.identity();
switch ( mount->mType )
{
case MountedShape::Image:
{
// Mount point is either the node called 'mountPoint' or the origin
S32 node = mount->mShape->getShape()->findNode( "mountPoint" );
if ( node != -1 )
{
mount->mShape->getShape()->getNodeWorldTransform( node, &mount->mTransform );
mount->mTransform.inverse();
}
}
break;
case MountedShape::Wheel:
// Rotate shape according to node's x position (left or right)
{
F32 rotAngle = M_PI_F/2;
if ( mount->mNode != -1 )
{
MatrixF hubMat;
mModel->getShape()->getNodeWorldTransform( mount->mNode, &hubMat );
if ( hubMat.getPosition().x < 0 )
rotAngle = -M_PI_F/2;
}
mount->mTransform.set( EulerF( 0, 0, rotAngle ) );
}
break;
default:
// No mount transform (use origin)
break;
}
}
}
const char* GuiShapeEdPreview::getMountThreadSequence(S32 mountSlot) const
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
return mount->mThread.seqName;
}
else
return "";
}
void GuiShapeEdPreview::setMountThreadSequence(S32 mountSlot, const char* seqName)
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
setThreadSequence( mount->mThread, mount->mShape, seqName );
}
}
F32 GuiShapeEdPreview::getMountThreadPos(S32 mountSlot) const
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
if ( mount->mThread.key )
return mount->mShape->getPos( mount->mThread.key );
}
return 0;
}
void GuiShapeEdPreview::setMountThreadPos(S32 mountSlot, F32 pos)
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
if ( mount->mThread.key )
mount->mShape->setPos( mount->mThread.key, pos );
}
}
F32 GuiShapeEdPreview::getMountThreadDir(S32 mountSlot) const
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
return mount->mThread.direction;
}
return 0;
}
void GuiShapeEdPreview::setMountThreadDir(S32 mountSlot, F32 dir)
{
if ( mountSlot < mMounts.size() )
{
MountedShape* mount = mMounts[mountSlot];
mount->mThread.direction = dir;
if ( mount->mThread.key )
mount->mShape->setTimeScale( mount->mThread.key, mTimeScale * mount->mThread.direction );
}
}
void GuiShapeEdPreview::unmountShape(S32 mountSlot)
{
if ( mountSlot < mMounts.size() )
{
delete mMounts[mountSlot];
mMounts.erase( mountSlot );
}
}
void GuiShapeEdPreview::unmountAll()
{
for ( S32 i = 0; i < mMounts.size(); i++)
delete mMounts[i];
mMounts.clear();
}
void GuiShapeEdPreview::refreshShape()
{
if ( mModel )
{
// Nodes or details may have changed => refresh the shape instance
mModel->setMaterialList( mModel->mMaterialList );
mModel->initNodeTransforms();
mModel->initMeshObjects();
TSShape* shape = mModel->getShape();
mProjectedNodes.setSize( shape->nodes.size() );
if ( mSelectedObject >= shape->objects.size() )
{
mSelectedObject = -1;
mSelectedObjDetail = 0;
}
// Re-compute the collision mesh stats
mColMeshes = 0;
mColPolys = 0;
for ( S32 i = 0; i < shape->details.size(); i++ )
{
const TSShape::Detail& det = shape->details[i];
const String& detName = shape->getName( det.nameIndex );
if ( ( det.subShapeNum < 0 ) || !detName.startsWith( "collision-" ) )
continue;
mColPolys += det.polyCount;
S32 od = det.objectDetailNum;
S32 start = shape->subShapeFirstObject[det.subShapeNum];
S32 end = start + shape->subShapeNumObjects[det.subShapeNum];
for ( S32 j = start; j < end; j++ )
{
const TSShape::Object &obj = shape->objects[j];
const TSMesh* mesh = ( od < obj.numMeshes ) ? shape->meshes[obj.startMeshIndex + od] : NULL;
if ( mesh )
mColMeshes++;
}
}
}
}
void GuiShapeEdPreview::updateSun()
{
if ( mFakeSun )
{
// Update sun colors
mFakeSun->setColor( mSunDiffuseColor );
mFakeSun->setAmbient( mSunAmbientColor );
// Determine the new sun direction and position
Point3F vec;
MatrixF xRot, zRot;
xRot.set( EulerF( mDegToRad(mSunRot.x), 0.0f, 0.0f ));
zRot.set( EulerF( 0.0f, 0.0f, mDegToRad(mSunRot.z) ));
zRot.mul( xRot );
zRot.getColumn( 1, &vec );
mFakeSun->setDirection( vec );
//mFakeSun->setPosition( vec * -10000.0f );
}
}
void GuiShapeEdPreview::updateNodeTransforms()
{
if ( mModel )
mModel->mDirtyFlags[0] |= TSShapeInstance::TransformDirty;
}
bool GuiShapeEdPreview::getMeshHidden( const char* name ) const
{
if ( mModel )
{
S32 objIndex = mModel->getShape()->findObject( name );
if ( objIndex != -1 )
return mModel->mMeshObjects[objIndex].forceHidden;
}
return false;
}
void GuiShapeEdPreview::setMeshHidden( const char* name, bool hidden )
{
if ( mModel )
{
S32 objIndex = mModel->getShape()->findObject( name );
if ( objIndex != -1 )
mModel->setMeshForceHidden( objIndex, hidden );
}
}
void GuiShapeEdPreview::setAllMeshesHidden( bool hidden )
{
if ( mModel )
{
for ( S32 i = 0; i < mModel->mMeshObjects.size(); i++ )
mModel->setMeshForceHidden( i, hidden );
}
}
void GuiShapeEdPreview::get3DCursor( GuiCursor *&cursor,
bool &visible,
const Gui3DMouseEvent &event_ )
{
cursor = NULL;
visible = false;
GuiCanvas *root = getRoot();
if ( !root )
return;
S32 currCursor = PlatformCursorController::curArrow;
if ( root->mCursorChanged == currCursor )
return;
PlatformWindow *window = root->getPlatformWindow();
PlatformCursorController *controller = window->getCursorController();
// We've already changed the cursor,
// so set it back before we change it again.
if ( root->mCursorChanged != -1 )
controller->popCursor();
// Now change the cursor shape
controller->pushCursor( currCursor );
root->mCursorChanged = currCursor;
}
void GuiShapeEdPreview::fitToShape()
{
if ( !mModel )
return;
// Determine the shape bounding box given the current camera rotation
MatrixF camRotMatrix( smCamMatrix );
camRotMatrix.setPosition( Point3F::Zero );
camRotMatrix.inverse();
Box3F bounds;
computeSceneBounds( bounds );
mOrbitPos = bounds.getCenter();
camRotMatrix.mul( bounds );
// Estimate the camera distance to fill the view by comparing the radii
// of the box and the viewport
F32 len_x = bounds.len_x();
F32 len_z = bounds.len_z();
F32 shapeRadius = mSqrt( len_x*len_x + len_z*len_z ) / 2;
F32 viewRadius = 0.45f * getMin( getExtent().x, getExtent().y );
// Set camera parameters
if ( mDisplayType == DisplayTypePerspective )
{
mOrbitDist = ( shapeRadius / viewRadius ) * mSaveWorldToScreenScale.y;
}
else
{
mOrthoCamTrans.set( 0, 0, 0 );
mOrthoFOV = shapeRadius * viewRadius / 320;
}
}
void GuiShapeEdPreview::setOrbitPos( const Point3F& pos )
{
mOrbitPos = pos;
}
void GuiShapeEdPreview::exportToCollada( const String& path )
{
#ifdef TORQUE_COLLADA
if ( mModel )
{
MatrixF orientation( true );
orientation.setPosition( mModel->getShape()->mBounds.getCenter() );
orientation.inverse();
OptimizedPolyList polyList;
polyList.setBaseTransform( orientation );
mModel->buildPolyList( &polyList, mCurrentDL );
for ( S32 i = 0; i < mMounts.size(); i++ )
{
MountedShape* mount = mMounts[i];
MatrixF mat( true );
if ( mount->mNode != -1 )
{
mat = mModel->mNodeTransforms[ mount->mNode ];
mat *= mount->mTransform;
}
polyList.setTransform( &mat, Point3F::One );
mount->mShape->buildPolyList( &polyList, 0 );
}
// Use a ColladaUtils function to do the actual export to a Collada file
ColladaUtils::exportToCollada( path, polyList );
}
#endif
}
//-----------------------------------------------------------------------------
// Camera control and Node editing
// - moving the mouse over a node will highlight (but not select) it
// - left clicking on a node will select it, the gizmo will appear
// - left clicking on no node will unselect the current node
// - left dragging the gizmo will translate/rotate the node
// - middle drag translates the view
// - right drag rotates the view
// - mouse wheel zooms the view
// - holding shift while changing the view speeds them up
void GuiShapeEdPreview::handleMouseDown(const GuiEvent& event, GizmoMode mode)
{
if (!mActive || !mVisible || !mAwake )
return;
mouseLock();
mLastMousePos = event.mousePoint;
if ( mRenderNodes && ( mode == NoneMode ) )
{
mGizmoDragID++;
make3DMouseEvent( mLastEvent, event );
// Check gizmo first
mUsingAxisGizmo = false;
if ( mSelectedNode != -1 )
{
mGizmo->on3DMouseDown( mLastEvent );
if ( mGizmo->getSelection() != Gizmo::None )
{
mUsingAxisGizmo = true;
return;
}
}
// Check if we have clicked on a node
S32 selected = collideNode( mLastEvent );
if ( selected != mSelectedNode )
{
mSelectedNode = selected;
Con::executef( this, "onNodeSelected", Con::getIntArg( mSelectedNode ));
}
}
//if ( mode == RotateMode )
// mRenderCameraAxes = true;
}
void GuiShapeEdPreview::handleMouseUp(const GuiEvent& event, GizmoMode mode)
{
mouseUnlock();
mUsingAxisGizmo = false;
if ( mRenderNodes && ( mode == NoneMode ) )
{
make3DMouseEvent( mLastEvent, event );
mGizmo->on3DMouseUp( mLastEvent );
}
//if ( mode == RotateMode )
// mRenderCameraAxes = false;
}
void GuiShapeEdPreview::handleMouseMove(const GuiEvent& event, GizmoMode mode)
{
if ( mRenderNodes && ( mode == NoneMode ) )
{
make3DMouseEvent( mLastEvent, event );
if ( mSelectedNode != -1 )
{
// Check if the mouse is hovering over an axis
mGizmo->on3DMouseMove( mLastEvent );
if ( mGizmo->getSelection() != Gizmo::None )
return;
}
// Check if we are over another node
mHoverNode = collideNode( mLastEvent );
}
}
void GuiShapeEdPreview::handleMouseDragged(const GuiEvent& event, GizmoMode mode)
{
// For non-perspective views, ignore rotation, and let EditTSCtrl handle
// translation
if ( mDisplayType != DisplayTypePerspective )
{
if ( mode == MoveMode )
{
Parent::onRightMouseDragged( event );
return;
}
else if ( mode == RotateMode )
return;
}
Point2F delta( event.mousePoint.x - mLastMousePos.x, event.mousePoint.y - mLastMousePos.y );
mLastMousePos = event.mousePoint;
// Use shift to increase speed
delta.x *= ( event.modifier & SI_SHIFT ) ? 0.05f : 0.01f;
delta.y *= ( event.modifier & SI_SHIFT ) ? 0.05f : 0.01f;
if ( mode == NoneMode )
{
if ( mEditingSun )
{
mSunRot.x += mRadToDeg( delta.y );
mSunRot.z += mRadToDeg( delta.x );
updateSun();
}
else if ( mRenderNodes )
{
make3DMouseEvent( mLastEvent, event );
if ( mUsingAxisGizmo )
{
// Use gizmo to modify the transform of the selected node
mGizmo->on3DMouseDragged( mLastEvent );
switch ( mGizmoProfile->mode )
{
case MoveMode:
// Update node transform
if ( mSelectedNode != -1 )
{
Point3F pos = mModel->mNodeTransforms[mSelectedNode].getPosition() + mGizmo->getOffset();
mModel->mNodeTransforms[mSelectedNode].setPosition( pos );
}
break;
case RotateMode:
// Update node transform
if ( mSelectedNode != -1 )
{
EulerF rot = mGizmo->getDeltaRot();
mModel->mNodeTransforms[mSelectedNode].mul( MatrixF( rot ) );
}
break;
default:
break;
}
// Notify the change in node transform
const char* name = mModel->getShape()->getNodeName(mSelectedNode).c_str();
const Point3F pos = mModel->mNodeTransforms[mSelectedNode].getPosition();
AngAxisF aa(mModel->mNodeTransforms[mSelectedNode]);
char buffer[256];
dSprintf(buffer, sizeof(buffer), "%g %g %g %g %g %g %g",
pos.x, pos.y, pos.z, aa.axis.x, aa.axis.y, aa.axis.z, aa.angle);
Con::executef(this, "onEditNodeTransform", name, buffer, Con::getIntArg(mGizmoDragID));
}
}
}
else
{
switch ( mode )
{
case MoveMode:
{
VectorF offset(-delta.x, 0, delta.y );
smCamMatrix.mulV( offset );
mOrbitPos += offset * mMoveSpeed;
}
break;
case RotateMode:
mCameraRot.x += delta.y;
mCameraRot.z += delta.x;
break;
default:
break;
}
}
}
void GuiShapeEdPreview::on3DMouseWheelUp(const Gui3DMouseEvent& event)
{
if ( mDisplayType == DisplayTypePerspective )
{
// Use shift and ctrl to increase speed
F32 mod = ( event.modifier & SI_SHIFT ) ? ( ( event.modifier & SI_CTRL ) ? 4.0 : 1.0 ) : 0.25f;
mOrbitDist -= mFabs(event.fval) * mZoomSpeed * mod;
}
}
void GuiShapeEdPreview::on3DMouseWheelDown(const Gui3DMouseEvent& event)
{
if ( mDisplayType == DisplayTypePerspective )
{
// Use shift and ctrl to increase speed
F32 mod = ( event.modifier & SI_SHIFT ) ? ( ( event.modifier & SI_CTRL ) ? 4.0 : 1.0 ) : 0.25f;
mOrbitDist += mFabs(event.fval) * mZoomSpeed * mod;
}
}
//-----------------------------------------------------------------------------
// NODE PICKING
void GuiShapeEdPreview::updateProjectedNodePoints()
{
if ( mModel )
{
// Project the 3D node position to get the 2D screen coordinates
for ( S32 i = 0; i < mModel->mNodeTransforms.size(); i++)
project( mModel->mNodeTransforms[i].getPosition(), &mProjectedNodes[i] );
}
}
S32 GuiShapeEdPreview::collideNode(const Gui3DMouseEvent& event) const
{
// Check if the given position is inside the screen rectangle of
// any shape node
S32 nodeIndex = -1;
F32 minZ = 0;
for ( S32 i = 0; i < mProjectedNodes.size(); i++)
{
const Point3F& pt = mProjectedNodes[i];
if ( pt.z > 1.0f )
continue;
RectI rect( pt.x - sNodeRectSize/2, pt.y - sNodeRectSize/2, sNodeRectSize, sNodeRectSize );
if ( rect.pointInRect( event.mousePoint ) )
{
if ( ( nodeIndex == -1 ) || ( pt.z < minZ ) )
{
nodeIndex = i;
minZ = pt.z;
}
}
}
return nodeIndex;
}
//-----------------------------------------------------------------------------
// RENDERING
bool GuiShapeEdPreview::getCameraTransform(MatrixF* cameraMatrix)
{
// Adjust the camera so that we are still facing the model
if ( mDisplayType == DisplayTypePerspective )
{
Point3F vec;
MatrixF xRot, zRot;
xRot.set( EulerF( mCameraRot.x, 0.0f, 0.0f ));
zRot.set( EulerF( 0.0f, 0.0f, mCameraRot.z ));
cameraMatrix->mul( zRot, xRot );
cameraMatrix->getColumn( 1, &vec );
cameraMatrix->setColumn( 3, mOrbitPos - vec*mOrbitDist );
}
else
{
cameraMatrix->identity();
if ( mModel )
{
Point3F camPos = mModel->getShape()->mBounds.getCenter();
F32 offset = mModel->getShape()->mBounds.len();
switch (mDisplayType)
{
case DisplayTypeTop: camPos.z += offset; break;
case DisplayTypeBottom: camPos.z -= offset; break;
case DisplayTypeFront: camPos.y += offset; break;
case DisplayTypeBack: camPos.y -= offset; break;
case DisplayTypeRight: camPos.x += offset; break;
case DisplayTypeLeft: camPos.x -= offset; break;
default:
break;
}
cameraMatrix->setColumn( 3, camPos );
}
}
return true;
}
void GuiShapeEdPreview::computeSceneBounds(Box3F& bounds)
{
if ( mModel )
mModel->computeBounds( mCurrentDL, bounds );
if (bounds.getExtents().x < POINT_EPSILON || bounds.getExtents().y < POINT_EPSILON || bounds.getExtents().z < POINT_EPSILON)
{
bounds.set(Point3F::Zero);
//We probably don't have any actual meshes in this model, so compute using the bones if we have them
for (S32 i = 0; i < mModel->getShape()->nodes.size(); i++)
{
Point3F nodePos = mModel->mNodeTransforms[i].getPosition();
bounds.extend(nodePos);
}
}
}
void GuiShapeEdPreview::updateDetailLevel(const SceneRenderState* state)
{
// Make sure current detail is valid
if ( !mModel->getShape()->details.size() )
return;
if ( mModel->getCurrentDetail() >= mModel->getShape()->details.size() )
setCurrentDetail( mModel->getShape()->details.size() - 1 );
// Convert between FOV and distance so zoom is consistent between Perspective
// and Orthographic views (conversion factor found by trial and error)
const F32 fov2dist = 1.3f;
if ( mDisplayType == DisplayTypePerspective )
mOrthoFOV = mOrbitDist / fov2dist;
else
mOrbitDist = mOrthoFOV * fov2dist;
// Use fixed distance in orthographic view (value found by trial + error)
F32 dist = ( mDisplayType == DisplayTypePerspective ) ? mOrbitDist : 0.1f;
// Select the appropriate detail level, and update the detail stats
S32 currentDetail = mModel->getCurrentDetail();
mModel->setDetailFromDistance( state, dist ); // need to call this to update smLastPixelSize
if ( mFixedDetail )
setCurrentDetail( currentDetail );
if ( mModel->getCurrentDetail() < 0 )
setCurrentDetail( 0 );
currentDetail = mModel->getCurrentDetail();
const TSShape::Detail& det = mModel->getShape()->details[ currentDetail ];
mDetailPolys = det.polyCount;
mDetailSize = det.size;
mPixelSize = TSShapeInstance::smLastPixelSize;
mNumMaterials = 0;
mNumDrawCalls = 0;
mNumBones = 0;
mNumWeights = 0;
if ( det.subShapeNum < 0 )
{
mNumMaterials = 1;
mNumDrawCalls = 1;
}
else
{
Vector<U32> usedMaterials;
S32 start = mModel->getShape()->subShapeFirstObject[det.subShapeNum];
S32 end = start + mModel->getShape()->subShapeNumObjects[det.subShapeNum];
for ( S32 iObj = start; iObj < end; iObj++ )
{
const TSShape::Object& obj = mModel->getShape()->objects[iObj];
if ( obj.numMeshes <= currentDetail )
continue;
const TSMesh* mesh = mModel->getShape()->meshes[ obj.startMeshIndex + currentDetail ];
if ( !mesh )
continue;
// Count the number of draw calls and materials
mNumDrawCalls += mesh->mPrimitives.size();
for ( S32 iPrim = 0; iPrim < mesh->mPrimitives.size(); iPrim++ )
usedMaterials.push_back_unique( mesh->mPrimitives[iPrim].matIndex & TSDrawPrimitive::MaterialMask );
// For skinned meshes, count the number of bones and weights
if ( mesh->getMeshType() == TSMesh::SkinMeshType )
{
const TSSkinMesh* skin = dynamic_cast<const TSSkinMesh*>(mesh);
mNumBones += skin->batchData.initialTransforms.size();
mNumWeights += skin->weight.size();
}
}
mNumMaterials = usedMaterials.size();
}
// Detect changes in detail level
if ( mCurrentDL != currentDetail )
{
mCurrentDL = currentDetail;
Con::executef( this, "onDetailChanged");
}
}
void GuiShapeEdPreview::updateThreads(F32 delta)
{
// Advance time on all threads
for ( S32 i = 0; i < mThreads.size(); i++ )
{
Thread& thread = mThreads[i];
if ( !thread.key || !thread.direction )
continue;
// Make sure thread priority matches sequence priority (which may have changed)
mModel->setPriority( thread.key, mModel->getShape()->sequences[mModel->getSequence( thread.key )].priority );
// Handle ping-pong
if ( thread.pingpong && !mModel->isInTransition( thread.key ) )
{
// Determine next position, then adjust if needed
F32 threadPos = mModel->getPos( thread.key );
F32 nextPos = threadPos + ( mModel->getTimeScale( thread.key ) * delta / mModel->getDuration( thread.key ) );
if ( nextPos < 0 )
{
// Reflect position and swap playback direction
nextPos = -nextPos;
mModel->setTimeScale( thread.key, -mModel->getTimeScale( thread.key ) );
mModel->setPos( thread.key, nextPos );
}
else if ( nextPos > 1.0f )
{
// Reflect position and swap playback direction
nextPos = 2.0f - nextPos;
mModel->setTimeScale( thread.key, -mModel->getTimeScale( thread.key ) );
mModel->setPos( thread.key, nextPos );
}
else
{
// Advance time normally
mModel->advanceTime( delta, thread.key );
}
}
else
{
// Advance time normally
mModel->advanceTime( delta, thread.key );
}
// Invoke script callback if active thread position has changed
if ( i == mActiveThread )
{
F32 threadPos = mModel->getPos( thread.key );
bool inTransition = mModel->isInTransition( thread.key );
onThreadPosChanged_callback( threadPos, inTransition );
}
}
// Mark threads as dirty so they will be re-sorted, in case the user changed
// sequence priority or blend flags
mModel->setDirty( TSShapeInstance::ThreadDirty );
// Advance time on all mounted shape threads
for ( S32 i = 0; i < mMounts.size(); i++ )
{
MountedShape* mount = mMounts[i];
if ( mount->mThread.key )
mount->mShape->advanceTime( delta, mount->mThread.key );
}
}
void GuiShapeEdPreview::renderWorld(const RectI &updateRect)
{
if ( !mModel )
return;
mSaveFrustum = GFX->getFrustum();
mSaveFrustum.setFarDist( 100000.0f );
GFX->setFrustum( mSaveFrustum );
mSaveFrustum.setTransform( smCamMatrix );
mSaveProjection = GFX->getProjectionMatrix();
mSaveWorldToScreenScale = GFX->getWorldToScreenScale();
FogData savedFogData = gClientSceneGraph->getFogData();
gClientSceneGraph->setFogData( FogData() ); // no fog in preview window
SceneRenderState state
(
gClientSceneGraph,
SPT_Diffuse,
SceneCameraState( GFX->getViewport(), mSaveFrustum,
GFX->getWorldMatrix(), GFX->getProjectionMatrix() )
);
// Set up pass transforms
RenderPassManager *renderPass = state.getRenderPass();
renderPass->assignSharedXform( RenderPassManager::View, GFX->getWorldMatrix() );
renderPass->assignSharedXform( RenderPassManager::Projection, GFX->getProjectionMatrix() );
// Set up our TS render state here.
TSRenderState rdata;
rdata.setSceneState(&state);
LIGHTMGR->unregisterAllLights();
LIGHTMGR->setSpecialLight( LightManager::slSunLightType, mFakeSun );
// We might have some forward lit materials
// so pass down a query to gather lights.
LightQuery query;
query.init( SphereF( Point3F::Zero, 1 ) );
rdata.setLightQuery( &query );
// Update projected node points (for mouse picking)
updateProjectedNodePoints();
// Determine time elapsed since last render (for animation playback)
S32 time = Platform::getVirtualMilliseconds();
S32 dt = time - mLastRenderTime;
mLastRenderTime = time;
if ( mModel )
{
updateDetailLevel( &state );
// Render the grid
renderGrid();
// Animate the model
updateThreads( (F32)dt / 1000.f );
mModel->animate();
// Render the shape
GFX->setStateBlock( mDefaultGuiSB );
if ( mRenderGhost )
rdata.setFadeOverride( 0.5f );
GFX->pushWorldMatrix();
GFX->setWorldMatrix( MatrixF::Identity );
mModel->render( rdata );
// Render mounted objects
if ( mRenderMounts )
{
for ( S32 i = 0; i < mMounts.size(); i++ )
{
MountedShape* mount = mMounts[i];
GFX->pushWorldMatrix();
if ( mount->mNode != -1 )
{
GFX->multWorld( mModel->mNodeTransforms[ mount->mNode ] );
GFX->multWorld( mount->mTransform );
}
mount->mShape->animate();
mount->mShape->render( rdata );
GFX->popWorldMatrix();
}
}
GFX->popWorldMatrix();
renderPass->renderPass( &state );
// @todo: Model and other elements (bounds, grid etc) use different
// zBuffers, so at the moment, draw order determines what is on top
// Render collision volumes
renderCollisionMeshes();
// Render the shape bounding box
if ( mRenderBounds )
{
Point3F boxSize = mModel->getShape()->mBounds.maxExtents - mModel->getShape()->mBounds.minExtents;
GFXStateBlockDesc desc;
desc.fillMode = GFXFillWireframe;
GFX->getDrawUtil()->drawCube( desc, boxSize, mModel->getShape()->center, ColorI::WHITE );
}
// Render the selected object bounding box
if ( mRenderObjBox && ( mSelectedObject != -1 ) )
{
const TSShape::Object& obj = mModel->getShape()->objects[mSelectedObject];
const TSMesh* mesh = ( mCurrentDL < obj.numMeshes ) ? mModel->getShape()->meshes[obj.startMeshIndex + mSelectedObjDetail] : NULL;
if ( mesh )
{
GFX->pushWorldMatrix();
if ( obj.nodeIndex != -1 )
GFX->multWorld( mModel->mNodeTransforms[ obj.nodeIndex ] );
const Box3F& bounds = mesh->getBounds();
GFXStateBlockDesc desc;
desc.fillMode = GFXFillWireframe;
GFX->getDrawUtil()->drawCube( desc, bounds.getExtents(), bounds.getCenter(), ColorI::RED );
GFX->popWorldMatrix();
}
}
// Render the sun direction if currently editing it
renderSunDirection();
// render the nodes in the model
renderNodes();
// use the gizmo to render the camera axes
if ( mRenderCameraAxes )
{
GizmoMode savedMode = mGizmoProfile->mode;
mGizmoProfile->mode = MoveMode;
Point3F pos;
Point2I screenCenter( updateRect.point + updateRect.extent/2 );
unproject( Point3F( screenCenter.x, screenCenter.y, 0.5 ), &pos );
mGizmo->set( MatrixF::Identity, pos, Point3F::One);
mGizmo->renderGizmo( smCamMatrix );
mGizmoProfile->mode = savedMode;
}
}
gClientSceneGraph->setFogData( savedFogData ); // restore fog setting
}
void GuiShapeEdPreview::renderGui(Point2I offset, const RectI& updateRect)
{
// Render the 2D stuff here
// Render the names of the hovered and selected nodes
if ( mModel )
{
if ( mRenderNodes && mHoverNode != -1 )
renderNodeName( mHoverNode, LinearColorF::WHITE );
if ( mSelectedNode != -1 )
renderNodeName( mSelectedNode, LinearColorF::WHITE );
}
}
void GuiShapeEdPreview::renderGrid()
{
if ( mRenderGridPlane )
{
// Use EditTSCtrl to render the grid in non-perspective views
if ( mDisplayType != DisplayTypePerspective )
{
Parent::renderGrid();
return;
}
// Round grid dimension up to a multiple of the minor ticks
Point2I dim(mGridDimension.x + mGridPlaneMinorTicks, mGridDimension.y + mGridPlaneMinorTicks);
dim /= ( mGridPlaneMinorTicks + 1 );
dim *= ( mGridPlaneMinorTicks + 1 );
Point2F minorStep( mGridPlaneSize, mGridPlaneSize );
Point2F size( minorStep.x * dim.x, minorStep.y * dim.y );
Point2F majorStep( minorStep * ( mGridPlaneMinorTicks + 1 ) );
GFXStateBlockDesc desc;
desc.setBlend( true );
desc.setZReadWrite( true, false );
GFX->getDrawUtil()->drawPlaneGrid( desc, Point3F::Zero, size, minorStep, mGridPlaneMinorTickColor );
GFX->getDrawUtil()->drawPlaneGrid( desc, Point3F::Zero, size, majorStep, mGridPlaneColor );
}
}
void GuiShapeEdPreview::renderSunDirection() const
{
if ( mEditingSun )
{
// Render four arrows aiming in the direction of the sun's light
ColorI color = LinearColorF( mFakeSun->getColor()).toColorI();
F32 length = mModel->getShape()->mBounds.len() * 0.8f;
// Get the sun's vectors
Point3F fwd = mFakeSun->getTransform().getForwardVector();
Point3F up = mFakeSun->getTransform().getUpVector() * length / 8;
Point3F right = mFakeSun->getTransform().getRightVector() * length / 8;
// Calculate the start and end points of the first arrow (bottom left)
Point3F start = mModel->getShape()->center - fwd * length - up/2 - right/2;
Point3F end = mModel->getShape()->center - fwd * length / 3 - up/2 - right/2;
GFXStateBlockDesc desc;
desc.setZReadWrite( true, true );
GFXDrawUtil* drawUtil = GFX->getDrawUtil();
drawUtil->drawArrow( desc, start, end, color );
drawUtil->drawArrow( desc, start + up, end + up, color );
drawUtil->drawArrow( desc, start + right, end + right, color );
drawUtil->drawArrow( desc, start + up + right, end + up + right, color );
}
}
void GuiShapeEdPreview::renderNodes() const
{
if ( mRenderNodes )
{
// Render links between nodes
GFXStateBlockDesc desc;
desc.setZReadWrite( false, true );
desc.setCullMode( GFXCullNone );
GFX->setStateBlockByDesc( desc );
PrimBuild::color( ColorI::WHITE );
PrimBuild::begin( GFXLineList, mModel->getShape()->nodes.size() * 2 );
for ( S32 i = 0; i < mModel->getShape()->nodes.size(); i++)
{
const TSShape::Node& node = mModel->getShape()->nodes[i];
if (node.parentIndex >= 0)
{
Point3F start(mModel->mNodeTransforms[i].getPosition());
Point3F end(mModel->mNodeTransforms[node.parentIndex].getPosition());
PrimBuild::vertex3f( start.x, start.y, start.z );
PrimBuild::vertex3f( end.x, end.y, end.z );
}
}
PrimBuild::end();
// Render the node axes
for ( S32 i = 0; i < mModel->getShape()->nodes.size(); i++)
{
// Render the selected and hover nodes last (so they are on top)
if ( ( i == mSelectedNode ) || ( i == mHoverNode ) )
continue;
renderNodeAxes( i, LinearColorF::WHITE );
}
// Render the hovered node
if ( mHoverNode != -1 )
renderNodeAxes( mHoverNode, LinearColorF::GREEN );
}
// Render the selected node (even if mRenderNodes is false)
if ( mSelectedNode != -1 )
{
renderNodeAxes( mSelectedNode, LinearColorF::GREEN );
const MatrixF& nodeMat = mModel->mNodeTransforms[mSelectedNode];
mGizmo->set( nodeMat, nodeMat.getPosition(), Point3F::One);
mGizmo->renderGizmo( smCamMatrix );
}
}
void GuiShapeEdPreview::renderNodeAxes(S32 index, const LinearColorF& nodeColor) const
{
if(mModel->mNodeTransforms.size() <= index || index < 0)
return;
const Point3F xAxis( 1.0f, 0.15f, 0.15f );
const Point3F yAxis( 0.15f, 1.0f, 0.15f );
const Point3F zAxis( 0.15f, 0.15f, 1.0f );
GFXStateBlockDesc desc;
desc.setZReadWrite( false, true );
desc.setCullMode( GFXCullNone );
// Render nodes the same size regardless of zoom
F32 scale = mOrbitDist / 60;
GFX->pushWorldMatrix();
GFX->multWorld( mModel->mNodeTransforms[index] );
const ColorI color = LinearColorF(nodeColor).toColorI();
GFX->getDrawUtil()->drawCube( desc, xAxis * scale, Point3F::Zero, color );
GFX->getDrawUtil()->drawCube( desc, yAxis * scale, Point3F::Zero, color );
GFX->getDrawUtil()->drawCube( desc, zAxis * scale, Point3F::Zero, color );
GFX->popWorldMatrix();
}
void GuiShapeEdPreview::renderNodeName(S32 index, const LinearColorF& textColor) const
{
if(index < 0 || index >= mModel->getShape()->nodes.size() || index >= mProjectedNodes.size())
return;
const TSShape::Node& node = mModel->getShape()->nodes[index];
const String& nodeName = mModel->getShape()->getName( node.nameIndex );
Point2I pos( mProjectedNodes[index].x, mProjectedNodes[index].y + sNodeRectSize + 6 );
GFX->getDrawUtil()->setBitmapModulation( LinearColorF(textColor).toColorI());
GFX->getDrawUtil()->drawText( mProfile->mFont, pos, nodeName.c_str() );
}
void GuiShapeEdPreview::renderCollisionMeshes() const
{
if ( mRenderColMeshes )
{
ConcretePolyList polylist;
polylist.setTransform( &MatrixF::Identity, Point3F::One );
for ( S32 iDet = 0; iDet < mModel->getShape()->details.size(); iDet++ )
{
const TSShape::Detail& det = mModel->getShape()->details[iDet];
const String& detName = mModel->getShape()->getName( det.nameIndex );
// Ignore non-collision details
if ( detName.startsWith( "Collision-" ) )
mModel->buildPolyList( &polylist, iDet );
}
polylist.render();
}
}
//-----------------------------------------------------------------------------
// Console methods (GuiShapeEdPreview)
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiShapeEdPreview, setOrbitPos, void, ( Point3F pos ),,
"Set the camera orbit position\n\n"
"@param pos Position in the form \"x y z\"\n" )
{
object->setOrbitPos( pos );
}
DefineEngineMethod( GuiShapeEdPreview, setModel, bool, ( const char* shapePath ),,
"Sets the model to be displayed in this control\n\n"
"@param shapeName Name of the model to display.\n"
"@return True if the model was loaded successfully, false otherwise.\n" )
{
return object->setObjectModel( shapePath );
}
DefineEngineMethod(GuiShapeEdPreview, setShapeAsset, bool, (const char* shapeAsset), ,
"Sets the model to be displayed in this control\n\n"
"@param shapeName Name of the model to display.\n"
"@return True if the model was loaded successfully, false otherwise.\n")
{
return object->setObjectShapeAsset(shapeAsset);
}
DefineEngineMethod( GuiShapeEdPreview, fitToShape, void, (),,
"Adjust the camera position and zoom to fit the shape within the view.\n\n" )
{
object->fitToShape();
}
DefineEngineMethod( GuiShapeEdPreview, refreshShape, void, (),,
"Refresh the shape (used when the shape meshes or nodes have been added or removed)\n\n" )
{
object->refreshShape();
}
DefineEngineMethod( GuiShapeEdPreview, updateNodeTransforms, void, (),,
"Refresh the shape node transforms (used when a node transform has been modified externally)\n\n" )
{
object->updateNodeTransforms();
}
DefineEngineMethod( GuiShapeEdPreview, computeShapeBounds, Box3F, (),,
"Compute the bounding box of the shape using the current detail and node transforms\n\n"
"@return the bounding box \"min.x min.y min.z max.x max.y max.z\"" )
{
Box3F bounds;
object->computeSceneBounds(bounds);
return bounds;
}
DefineEngineMethod( GuiShapeEdPreview, getMeshHidden, bool, ( const char* name ),,
"Return whether the named object is currently hidden\n\n" )
{
return object->getMeshHidden( name );
}
DefineEngineMethod( GuiShapeEdPreview, setMeshHidden, void, ( const char* name, bool hidden ),,
"Show or hide the named object in the shape\n\n" )
{
object->setMeshHidden( name, hidden );
}
DefineEngineMethod( GuiShapeEdPreview, setAllMeshesHidden, void, ( bool hidden ),,
"Show or hide all objects in the shape\n\n" )
{
object->setAllMeshesHidden( hidden );
}
DefineEngineMethod( GuiShapeEdPreview, exportToCollada, void, ( const char* path ),,
"Export the current shape and all mounted objects to COLLADA (.dae).\n"
"Note that animation is not exported, and all geometry is combined into a "
"single mesh.\n\n"
"@param path Destination filename\n" )
{
object->exportToCollada( path );
}
//-----------------------------------------------------------------------------
// THREADS
DefineEngineMethod( GuiShapeEdPreview, addThread, void, (),,
"Add a new thread (initially without any sequence set)\n\n" )
{
object->addThread();
}
DefineEngineMethod( GuiShapeEdPreview, removeThread, void, ( S32 slot ),,
"Removes the specifed thread\n\n"
"@param slot index of the thread to remove\n" )
{
object->removeThread( slot );
}
DefineEngineMethod( GuiShapeEdPreview, getThreadCount, S32, (),,
"Get the number of threads\n\n"
"@return the number of threads\n" )
{
return object->getThreadCount();
}
DefineEngineMethod( GuiShapeEdPreview, setTimeScale, void, ( F32 scale ),,
"Set the time scale of all threads\n\n"
"@param scale new time scale value\n" )
{
object->setTimeScale( scale );
}
DefineEngineMethod( GuiShapeEdPreview, setThreadSequence, void, ( const char* name, F32 duration, F32 pos, bool play ), ( 0, 0, false ),
"Sets the sequence to play for the active thread.\n\n"
"@param name name of the sequence to play\n"
"@param duration transition duration (0 for no transition)\n"
"@param pos position in the new sequence to transition to\n"
"@param play if true, the new sequence will play during the transition\n" )
{
object->setActiveThreadSequence( name, duration, pos, play );
}
DefineEngineMethod( GuiShapeEdPreview, getThreadSequence, const char*, (),,
"Get the name of the sequence assigned to the active thread" )
{
return object->getThreadSequence();
}
DefineEngineMethod( GuiShapeEdPreview, refreshThreadSequences, void, (),,
"Refreshes thread sequences (in case of removed/renamed sequences" )
{
object->refreshThreadSequences();
}
//-----------------------------------------------------------------------------
// Mounting
DefineEngineMethod( GuiShapeEdPreview, mountShape, bool, ( const char* shapeAssetId, const char* nodeName, const char* type, S32 slot ),,
"Mount a shape onto the main shape at the specified node\n\n"
"@param shapeAssetId AssetId of the shape to mount\n"
"@param nodeName name of the node on the main shape to mount to\n"
"@param type type of mounting to use (Object, Image or Wheel)\n"
"@param slot mount slot\n" )
{
return object->mountShape(shapeAssetId, nodeName, type, slot );
}
DefineEngineMethod( GuiShapeEdPreview, setMountNode, void, ( S32 slot, const char* nodeName ),,
"Set the node a shape is mounted to.\n\n"
"@param slot mounted shape slot\n"
"@param nodename name of the node to mount to\n" )
{
object->setMountNode( slot, nodeName );
}
DefineEngineMethod( GuiShapeEdPreview, getMountThreadSequence, const char*, ( S32 slot ),,
"Get the name of the sequence playing on this mounted shape\n"
"@param slot mounted shape slot\n"
"@return name of the sequence (if any)\n" )
{
return object->getMountThreadSequence( slot );
}
DefineEngineMethod( GuiShapeEdPreview, setMountThreadSequence, void, ( S32 slot, const char* name ),,
"Set the sequence to play for the shape mounted in the specified slot\n"
"@param slot mounted shape slot\n"
"@param name name of the sequence to play\n" )
{
object->setMountThreadSequence( slot, name );
}
DefineEngineMethod( GuiShapeEdPreview, getMountThreadPos, F32, ( S32 slot ),,
"Get the playback position of the sequence playing on this mounted shape\n"
"@param slot mounted shape slot\n"
"@return playback position of the sequence (0-1)\n" )
{
return object->getMountThreadPos( slot );
}
DefineEngineMethod( GuiShapeEdPreview, setMountThreadPos, void, ( S32 slot, F32 pos ),,
"Set the sequence position of the shape mounted in the specified slot\n"
"@param slot mounted shape slot\n"
"@param pos sequence position (0-1)\n" )
{
object->setMountThreadPos( slot, pos );
}
DefineEngineMethod( GuiShapeEdPreview, getMountThreadDir, F32, ( S32 slot ),,
"Get the playback direction of the sequence playing on this mounted shape\n"
"@param slot mounted shape slot\n"
"@return direction of the sequence (-1=reverse, 0=paused, 1=forward)\n" )
{
return object->getMountThreadDir( slot );
}
DefineEngineMethod( GuiShapeEdPreview, setMountThreadDir, void, ( S32 slot, F32 dir ),,
"Set the playback direction of the shape mounted in the specified slot\n"
"@param slot mounted shape slot\n"
"@param dir playback direction (-1=backwards, 0=paused, 1=forwards)\n" )
{
object->setMountThreadDir( slot, dir );
}
DefineEngineMethod( GuiShapeEdPreview, unmountShape, void, ( S32 slot ),,
"Unmount the shape in the specified slot\n"
"@param slot mounted shape slot\n" )
{
return object->unmountShape( slot );
}
DefineEngineMethod( GuiShapeEdPreview, unmountAll, void, (),,
"Unmount all shapes\n" )
{
return object->unmountAll();
}