Torque3D/Engine/source/gui/editor/guiShapeEdPreview.cpp
AzaezelX de87d2f6ad fix guishapeedpreview not displaying IBL
note this *does* require a baked skylight in the main scene
todo: track down why a similar protocol does not operate for guimaterialpreview and guiobjectview
2024-02-10 13:36:55 -06:00

1954 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"
#include "renderInstance/renderProbeMgr.h"
#include "T3D/lighting/skylight.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()
{
docsURL;
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.c_str() : "";
}
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
if (Skylight::smSkylightProbe.isValid())
PROBEMGR->submitProbe(Skylight::smSkylightProbe->getProbeInfo());
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();
}