//----------------------------------------------------------------------------- // 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" #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 ), mModel( NULL ), mGridDimension( 30, 30 ), mRenderGhost( false ), mRenderNodes( false ), mRenderBounds( false ), mRenderObjBox( false ), mRenderMounts( true ), mSunDiffuseColor( 255, 255, 255, 255 ), mSunAmbientColor( 140, 140, 140, 255 ), mSelectedNode( -1 ), mHoverNode( -1 ), mSelectedObject( -1 ), mSelectedObjDetail( 0 ), mUsingAxisGizmo( false ), mGizmoDragID( 0 ), mEditingSun( false ), mTimeScale( 1.0f ), mActiveThread( -1 ), mLastRenderTime( 0 ), mFakeSun( NULL ), mSunRot( 45.0f, 0, 135.0f ), mCameraRot( 0, 0, 3.9f ), 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, NULL, &setFieldThreadPos, &getFieldThreadPos, "Current position of the active thread (0-1)" ); addProtectedField( "threadDirection", TypeS32, NULL, &setFieldThreadDir, &getFieldThreadDir, "Playback direction of the active thread" ); addProtectedField( "threadPingPong", TypeBool, NULL, &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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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; if (modelName && modelName[0]) { Resource 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->radius / sMoveScaler; mZoomSpeed = shape->radius / 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(); } return true; } 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* modelName, const char* nodeName, const char* mountType, S32 slot) { if ( !modelName || !modelName[0] ) return false; Resource model = ResourceManager::get().load( modelName ); if ( !bool( model ) ) return false; TSShapeInstance* tsi = new TSShapeInstance( model, 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()->bounds.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()->bounds.getCenter(); F32 offset = mModel->getShape()->bounds.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 ); } 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 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->primitives.size(); for ( S32 iPrim = 0; iPrim < mesh->primitives.size(); iPrim++ ) usedMaterials.push_back_unique( mesh->primitives[iPrim].matIndex & TSDrawPrimitive::MaterialMask ); // For skinned meshes, count the number of bones and weights if ( mesh->getMeshType() == TSMesh::SkinMeshType ) { const TSSkinMesh* skin = dynamic_cast(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()->bounds.maxExtents - mModel->getShape()->bounds.minExtents; GFXStateBlockDesc desc; desc.fillMode = GFXFillWireframe; GFX->getDrawUtil()->drawCube( desc, boxSize, mModel->getShape()->center, ColorF::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(), ColorF::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, ColorF::WHITE ); if ( mSelectedNode != -1 ) renderNodeName( mSelectedNode, ColorF::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( mFakeSun->getColor() ); F32 length = mModel->getShape()->bounds.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, ColorF::WHITE ); } // Render the hovered node if ( mHoverNode != -1 ) renderNodeAxes( mHoverNode, ColorF::GREEN ); } // Render the selected node (even if mRenderNodes is false) if ( mSelectedNode != -1 ) { renderNodeAxes( mSelectedNode, ColorF::GREEN ); const MatrixF& nodeMat = mModel->mNodeTransforms[mSelectedNode]; mGizmo->set( nodeMat, nodeMat.getPosition(), Point3F::One); mGizmo->renderGizmo( smCamMatrix ); } } void GuiShapeEdPreview::renderNodeAxes(S32 index, const ColorF& 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] ); GFX->getDrawUtil()->drawCube( desc, xAxis * scale, Point3F::Zero, nodeColor ); GFX->getDrawUtil()->drawCube( desc, yAxis * scale, Point3F::Zero, nodeColor ); GFX->getDrawUtil()->drawCube( desc, zAxis * scale, Point3F::Zero, nodeColor ); GFX->popWorldMatrix(); } void GuiShapeEdPreview::renderNodeName(S32 index, const ColorF& 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( textColor ); 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, 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* shapePath, const char* nodeName, const char* type, S32 slot ),, "Mount a shape onto the main shape at the specified node\n\n" "@param shapePath path to 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( shapePath, 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(); }