mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 00:24:40 +00:00
Profile editor for the meshRoad object
credit to Ryan Mounts (RDM) found originally at http://www.garagegames.com/community/forums/viewthread/105391
This commit is contained in:
parent
76c5e30869
commit
973fd44c6a
12 changed files with 2501 additions and 201 deletions
|
|
@ -55,6 +55,14 @@ ConsoleDocClass( GuiMeshRoadEditorCtrl,
|
||||||
"@internal"
|
"@internal"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
S32 _NodeIndexCmp( U32 const *a, U32 const *b )
|
||||||
|
{
|
||||||
|
S32 a2 = (*a);
|
||||||
|
S32 b2 = (*b);
|
||||||
|
S32 diff = a2 - b2;
|
||||||
|
return diff < 0 ? 1 : diff > 0 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
GuiMeshRoadEditorCtrl::GuiMeshRoadEditorCtrl()
|
GuiMeshRoadEditorCtrl::GuiMeshRoadEditorCtrl()
|
||||||
:
|
:
|
||||||
// Each of the mode names directly correlates with the Mesh Road Editor's
|
// Each of the mode names directly correlates with the Mesh Road Editor's
|
||||||
|
|
@ -69,6 +77,10 @@ GuiMeshRoadEditorCtrl::GuiMeshRoadEditorCtrl()
|
||||||
mRotatePointMode("MeshRoadEditorRotateMode"),
|
mRotatePointMode("MeshRoadEditorRotateMode"),
|
||||||
mSavedDrag(false),
|
mSavedDrag(false),
|
||||||
mIsDirty( false ),
|
mIsDirty( false ),
|
||||||
|
mSavedProfileDrag( false ),
|
||||||
|
mDeselectProfileNode( false ),
|
||||||
|
mProfileNode( -1 ),
|
||||||
|
mProfileColor( 255,255,0 ),
|
||||||
mRoadSet( NULL ),
|
mRoadSet( NULL ),
|
||||||
mSelNode( -1 ),
|
mSelNode( -1 ),
|
||||||
mHoverNode( -1 ),
|
mHoverNode( -1 ),
|
||||||
|
|
@ -116,6 +128,48 @@ void GuiMeshRoadEditorUndoAction::undo()
|
||||||
object->_addNode( mNodes[i].point, mNodes[i].width, mNodes[i].depth, mNodes[i].normal );
|
object->_addNode( mNodes[i].point, mNodes[i].width, mNodes[i].depth, mNodes[i].normal );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temporarily save the Roads current profile data.
|
||||||
|
Vector<MeshRoadProfileNode> profNodes;
|
||||||
|
Vector<U8> profMtrls;
|
||||||
|
profNodes.merge( object->mSideProfile.mNodes );
|
||||||
|
profMtrls.merge( object->mSideProfile.mSegMtrls );
|
||||||
|
|
||||||
|
// Restore the Profile Nodes saved in the UndoAction
|
||||||
|
Point3F pos;
|
||||||
|
object->mSideProfile.mNodes.clear();
|
||||||
|
object->mSideProfile.mSegMtrls.clear();
|
||||||
|
for ( U32 i = 0; i < mProfileNodes.size(); i++ )
|
||||||
|
{
|
||||||
|
MeshRoadProfileNode newNode;
|
||||||
|
|
||||||
|
pos = mProfileNodes[i].getPosition();
|
||||||
|
newNode.setSmoothing( mProfileNodes[i].isSmooth() );
|
||||||
|
|
||||||
|
object->mSideProfile.mNodes.push_back( newNode );
|
||||||
|
object->mSideProfile.mNodes.last().setPosition( pos.x, pos.y );
|
||||||
|
|
||||||
|
if(i)
|
||||||
|
object->mSideProfile.mSegMtrls.push_back(mProfileMtrls[i-1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the first node position to trigger packet update to client
|
||||||
|
pos.set(0.0f, 0.0f, 0.0f);
|
||||||
|
object->mSideProfile.setNodePosition(0,pos);
|
||||||
|
|
||||||
|
// Regenerate the Road
|
||||||
|
object->mSideProfile.generateNormals();
|
||||||
|
|
||||||
|
// If applicable set the selected Road and node
|
||||||
|
mEditor->mProfileNode = -1;
|
||||||
|
|
||||||
|
// Now save the previous Road data in this UndoAction
|
||||||
|
// since an undo action must become a redo action and vice-versa
|
||||||
|
//mMetersPerSegment = metersPerSeg;
|
||||||
|
mProfileNodes.clear();
|
||||||
|
mProfileNodes.merge( profNodes );
|
||||||
|
mProfileMtrls.clear();
|
||||||
|
mProfileMtrls.merge( profMtrls );
|
||||||
|
|
||||||
// Regenerate the Road
|
// Regenerate the Road
|
||||||
object->regenerate();
|
object->regenerate();
|
||||||
|
|
||||||
|
|
@ -228,6 +282,158 @@ void GuiMeshRoadEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event)
|
||||||
if ( !isFirstResponder() )
|
if ( !isFirstResponder() )
|
||||||
setFirstResponder();
|
setFirstResponder();
|
||||||
|
|
||||||
|
if( MeshRoad::smShowRoadProfile && mSelRoad )
|
||||||
|
{
|
||||||
|
// Ctrl-Click = Add Node
|
||||||
|
if(event.modifier & SI_CTRL)
|
||||||
|
{
|
||||||
|
S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
|
||||||
|
|
||||||
|
if(clickedNode != -1)
|
||||||
|
{
|
||||||
|
// If clicked node is already in list, remove it, else add it to list
|
||||||
|
if(!mSelProfNodeList.remove(clickedNode) && clickedNode > 0)
|
||||||
|
mSelProfNodeList.push_back(clickedNode);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point3F pos;
|
||||||
|
|
||||||
|
PlaneF xy( mSelRoad->mSlices[0].p2, -mSelRoad->mSlices[0].fvec );
|
||||||
|
|
||||||
|
xy.intersect(event.pos, event.vec, &pos);
|
||||||
|
|
||||||
|
mSelRoad->mSideProfile.worldToObj(pos);
|
||||||
|
|
||||||
|
U32 node = mSelRoad->mSideProfile.clickOnLine(pos);
|
||||||
|
|
||||||
|
if(node != -1)
|
||||||
|
{
|
||||||
|
submitUndo( "Add Profile Node" );
|
||||||
|
mSelRoad->mSideProfile.addPoint(node, pos);
|
||||||
|
mProfileNode = node;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
mSelProfNodeList.push_back(node);
|
||||||
|
mIsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alt-Click = Delete Node
|
||||||
|
if(event.modifier & SI_ALT)
|
||||||
|
{
|
||||||
|
S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
|
||||||
|
|
||||||
|
|
||||||
|
if(mSelProfNodeList.find_next(clickedNode) != -1)
|
||||||
|
{
|
||||||
|
submitUndo( "Delete Profile Node" );
|
||||||
|
|
||||||
|
mSelProfNodeList.sort( _NodeIndexCmp );
|
||||||
|
for(U32 i=0; i < mSelProfNodeList.size(); i++)
|
||||||
|
mSelRoad->mSideProfile.removePoint( mSelProfNodeList[i] );
|
||||||
|
|
||||||
|
mProfileNode = -1;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
mIsDirty = true;
|
||||||
|
}
|
||||||
|
else if(clickedNode > 0 && clickedNode < mSelRoad->mSideProfile.mNodes.size()-1)
|
||||||
|
{
|
||||||
|
submitUndo( "Delete Profile Node" );
|
||||||
|
mSelRoad->mSideProfile.removePoint( clickedNode );
|
||||||
|
mProfileNode = -1;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
mIsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift-Click = Toggle Node Smoothing
|
||||||
|
if(event.modifier & SI_SHIFT)
|
||||||
|
{
|
||||||
|
S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
|
||||||
|
|
||||||
|
if(clickedNode != -1)
|
||||||
|
{
|
||||||
|
submitUndo( "Smooth Profile Node" );
|
||||||
|
|
||||||
|
if(mSelProfNodeList.find_next(clickedNode) != -1)
|
||||||
|
{
|
||||||
|
for(U32 i=0; i < mSelProfNodeList.size(); i++)
|
||||||
|
mSelRoad->mSideProfile.toggleSmoothing(mSelProfNodeList[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSelRoad->mSideProfile.toggleSmoothing(clickedNode);
|
||||||
|
|
||||||
|
if(clickedNode != 0)
|
||||||
|
{
|
||||||
|
mProfileNode = clickedNode;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
mSelProfNodeList.push_back(clickedNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mIsDirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point3F pos;
|
||||||
|
PlaneF xy( mSelRoad->mSlices[0].p2, -mSelRoad->mSlices[0].fvec );
|
||||||
|
xy.intersect(event.pos, event.vec, &pos);
|
||||||
|
mSelRoad->mSideProfile.worldToObj(pos);
|
||||||
|
U32 node = mSelRoad->mSideProfile.clickOnLine(pos);
|
||||||
|
|
||||||
|
if(node > 0)
|
||||||
|
{
|
||||||
|
submitUndo( "Profile Material" );
|
||||||
|
mSelRoad->mSideProfile.toggleSegMtrl(node-1);
|
||||||
|
mIsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click to select/deselect nodes
|
||||||
|
S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
|
||||||
|
|
||||||
|
if(clickedNode != -1)
|
||||||
|
{
|
||||||
|
if(mSelProfNodeList.find_next(clickedNode) != -1)
|
||||||
|
{
|
||||||
|
mProfileNode = clickedNode;
|
||||||
|
mDeselectProfileNode = true;
|
||||||
|
}
|
||||||
|
else if(clickedNode != 0)
|
||||||
|
{
|
||||||
|
mProfileNode = clickedNode;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
mSelProfNodeList.push_back(clickedNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mProfileNode = -1;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
|
||||||
|
// Reset profile if Node 0 is double-clicked
|
||||||
|
if( event.mouseClickCount > 1 )
|
||||||
|
{
|
||||||
|
submitUndo( "Reset Profile" );
|
||||||
|
mSelRoad->mSideProfile.resetProfile(mSelRoad->mSlices[0].depth);
|
||||||
|
mSelRoad->regenerate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mProfileNode = -1;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Get the raycast collision position
|
// Get the raycast collision position
|
||||||
Point3F tPos;
|
Point3F tPos;
|
||||||
if ( !getStaticPos( event, tPos ) )
|
if ( !getStaticPos( event, tPos ) )
|
||||||
|
|
@ -588,6 +794,33 @@ void GuiMeshRoadEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event)
|
||||||
|
|
||||||
mSavedDrag = false;
|
mSavedDrag = false;
|
||||||
|
|
||||||
|
mSavedProfileDrag = false;
|
||||||
|
|
||||||
|
if( MeshRoad::smShowRoadProfile && mSelRoad )
|
||||||
|
{
|
||||||
|
// If we need to deselect node... this means we clicked on a selected node without dragging
|
||||||
|
if( mDeselectProfileNode )
|
||||||
|
{
|
||||||
|
S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
|
||||||
|
|
||||||
|
if(clickedNode == mProfileNode)
|
||||||
|
{
|
||||||
|
mProfileNode = -1;
|
||||||
|
mSelProfNodeList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
mDeselectProfileNode = false;
|
||||||
|
}
|
||||||
|
// Else if we dragged a node, update the road
|
||||||
|
else
|
||||||
|
{
|
||||||
|
S32 clickedNode = _getProfileNodeAtScreenPos( &mSelRoad->mSideProfile, event.mousePoint );
|
||||||
|
|
||||||
|
if(clickedNode == mProfileNode)
|
||||||
|
mSelRoad->regenerate(); // This regens the road for collision purposes on the server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mouseUnlock();
|
mouseUnlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -661,6 +894,43 @@ void GuiMeshRoadEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event)
|
||||||
|
|
||||||
void GuiMeshRoadEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
|
void GuiMeshRoadEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event)
|
||||||
{
|
{
|
||||||
|
if( MeshRoad::smShowRoadProfile && mProfileNode > 0 && mSelRoad)
|
||||||
|
{
|
||||||
|
// If we haven't already saved,
|
||||||
|
// save an undo action to get back to this state,
|
||||||
|
// before we make any modifications to the selected node.
|
||||||
|
if ( !mSavedProfileDrag )
|
||||||
|
{
|
||||||
|
submitUndo( "Modify Profile Node" );
|
||||||
|
mSavedProfileDrag = true;
|
||||||
|
mIsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 idx;
|
||||||
|
Point3F pos, diff;
|
||||||
|
|
||||||
|
PlaneF xy( mSelRoad->mSlices[0].p2, -mSelRoad->mSlices[0].fvec );
|
||||||
|
xy.intersect(event.pos, event.vec, &pos);
|
||||||
|
|
||||||
|
mSelRoad->mSideProfile.worldToObj(pos);
|
||||||
|
diff = pos - mSelRoad->mSideProfile.mNodes[mProfileNode].getPosition();
|
||||||
|
|
||||||
|
for(U32 i=0; i < mSelProfNodeList.size(); i++)
|
||||||
|
{
|
||||||
|
idx = mSelProfNodeList[i];
|
||||||
|
pos = mSelRoad->mSideProfile.mNodes[idx].getPosition();
|
||||||
|
pos += diff;
|
||||||
|
|
||||||
|
if(pos.x < -mSelRoad->mSlices[0].width/2.0f)
|
||||||
|
pos.x = -mSelRoad->mSlices[0].width/2.0f + 1e-6;
|
||||||
|
|
||||||
|
mSelRoad->mSideProfile.setNodePosition( idx, pos );
|
||||||
|
}
|
||||||
|
|
||||||
|
mDeselectProfileNode = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Drags are only used to transform nodes
|
// Drags are only used to transform nodes
|
||||||
if ( !mSelRoad || mSelNode == -1 ||
|
if ( !mSelRoad || mSelNode == -1 ||
|
||||||
( mMode != mMovePointMode && mMode != mScalePointMode && mMode != mRotatePointMode ) )
|
( mMode != mMovePointMode && mMode != mScalePointMode && mMode != mRotatePointMode ) )
|
||||||
|
|
@ -756,6 +1026,18 @@ void GuiMeshRoadEditorCtrl::renderScene(const RectI & updateRect)
|
||||||
Point3F camPos;
|
Point3F camPos;
|
||||||
mat.getColumn(3,&camPos);
|
mat.getColumn(3,&camPos);
|
||||||
|
|
||||||
|
// Set up transform
|
||||||
|
if( mSelRoad )
|
||||||
|
{
|
||||||
|
MatrixF profileMat(true);
|
||||||
|
|
||||||
|
profileMat.setRow(0, mSelRoad->mSlices[0].rvec);
|
||||||
|
profileMat.setRow(1, mSelRoad->mSlices[0].uvec);
|
||||||
|
profileMat.setRow(2, -mSelRoad->mSlices[0].fvec);
|
||||||
|
|
||||||
|
mSelRoad->mSideProfile.setTransform(profileMat, mSelRoad->mSlices[0].p2);
|
||||||
|
}
|
||||||
|
|
||||||
if ( mHoverRoad && mHoverRoad != mSelRoad )
|
if ( mHoverRoad && mHoverRoad != mSelRoad )
|
||||||
{
|
{
|
||||||
_drawSpline( mHoverRoad, mHoverSplineColor );
|
_drawSpline( mHoverRoad, mHoverSplineColor );
|
||||||
|
|
@ -806,6 +1088,37 @@ void GuiMeshRoadEditorCtrl::renderScene(const RectI & updateRect)
|
||||||
_drawControlNodes( mHoverRoad, mHoverSplineColor );
|
_drawControlNodes( mHoverRoad, mHoverSplineColor );
|
||||||
if ( mSelRoad )
|
if ( mSelRoad )
|
||||||
_drawControlNodes( mSelRoad, mSelectedSplineColor );
|
_drawControlNodes( mSelRoad, mSelectedSplineColor );
|
||||||
|
|
||||||
|
if(MeshRoad::smShowRoadProfile)
|
||||||
|
{
|
||||||
|
char buf[64];
|
||||||
|
Point2I posi;
|
||||||
|
|
||||||
|
posi.x = 10;
|
||||||
|
posi.y = updateRect.len_y() - 80;
|
||||||
|
|
||||||
|
GFX->getDrawUtil()->setBitmapModulation(ColorI(128, 128, 128));
|
||||||
|
dStrcpy(buf, "Reset Profile: Double-click Start Node", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
|
||||||
|
dStrcpy(buf, "Move Node: Click and Drag Node", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
|
||||||
|
dStrcpy(buf, "Select Multiple Nodes: Ctrl-click Nodes", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
|
||||||
|
dStrcpy(buf, "Toggle Material: Shift-click Spline Segment", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
|
||||||
|
dStrcpy(buf, "Toggle Smoothing: Shift-click Node", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
|
||||||
|
dStrcpy(buf, "Delete Node: Alt-click Node", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
posi.y -= mProfile->mFont->getCharHeight((U8)buf[0]) + 4;
|
||||||
|
dStrcpy(buf, "Add Node: Ctrl-click Spline", 64);
|
||||||
|
GFX->getDrawUtil()->drawTextN(mProfile->mFont, posi, buf, dStrlen(buf));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
S32 GuiMeshRoadEditorCtrl::_getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi )
|
S32 GuiMeshRoadEditorCtrl::_getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi )
|
||||||
|
|
@ -834,6 +1147,33 @@ S32 GuiMeshRoadEditorCtrl::_getNodeAtScreenPos( const MeshRoad *pRoad, const Poi
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
S32 GuiMeshRoadEditorCtrl::_getProfileNodeAtScreenPos( MeshRoadProfile *pProfile, const Point2I &posi)
|
||||||
|
{
|
||||||
|
for ( U32 i = 0; i < pProfile->mNodes.size(); i++ )
|
||||||
|
{
|
||||||
|
Point3F nodePos;
|
||||||
|
pProfile->getNodeWorldPos(i, nodePos);
|
||||||
|
|
||||||
|
Point3F screenPos;
|
||||||
|
project( nodePos, &screenPos );
|
||||||
|
|
||||||
|
if ( screenPos.z < 0.0f )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Point2I screenPosI( (S32)screenPos.x, (S32)screenPos.y );
|
||||||
|
|
||||||
|
RectI nodeScreenRect( screenPosI - mNodeHalfSize, mNodeHalfSize * 2 );
|
||||||
|
|
||||||
|
if ( nodeScreenRect.pointInRect(posi) )
|
||||||
|
{
|
||||||
|
// we found a hit!
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color )
|
void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color )
|
||||||
{
|
{
|
||||||
if ( river->mSlices.size() <= 1 )
|
if ( river->mSlices.size() <= 1 )
|
||||||
|
|
@ -842,7 +1182,11 @@ void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color )
|
||||||
if ( MeshRoad::smShowSpline )
|
if ( MeshRoad::smShowSpline )
|
||||||
{
|
{
|
||||||
// Render the River center-line
|
// Render the River center-line
|
||||||
PrimBuild::color( color );
|
if( MeshRoad::smShowRoadProfile )
|
||||||
|
PrimBuild::color( ColorI(100,100,100) );
|
||||||
|
else
|
||||||
|
PrimBuild::color( color );
|
||||||
|
|
||||||
PrimBuild::begin( GFXLineStrip, river->mSlices.size() );
|
PrimBuild::begin( GFXLineStrip, river->mSlices.size() );
|
||||||
for ( U32 i = 0; i < river->mSlices.size(); i++ )
|
for ( U32 i = 0; i < river->mSlices.size(); i++ )
|
||||||
{
|
{
|
||||||
|
|
@ -880,6 +1224,100 @@ void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color )
|
||||||
PrimBuild::end();
|
PrimBuild::end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are in Profile Edit Mode, draw the profile spline and node normals
|
||||||
|
if ( MeshRoad::smShowRoadProfile )
|
||||||
|
{
|
||||||
|
Point3F nodePos;
|
||||||
|
Point3F normEndPos;
|
||||||
|
U32 numSide, numTop, numBottom;
|
||||||
|
|
||||||
|
numSide = numTop = numBottom = 0;
|
||||||
|
|
||||||
|
for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
|
||||||
|
{
|
||||||
|
switch(river->mSideProfile.mSegMtrls[i])
|
||||||
|
{
|
||||||
|
case MeshRoad::Side: numSide++; break;
|
||||||
|
case MeshRoad::Top: numTop++; break;
|
||||||
|
case MeshRoad::Bottom: numBottom++; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the profile spline
|
||||||
|
// Side
|
||||||
|
if(numSide)
|
||||||
|
{
|
||||||
|
PrimBuild::color( mProfileColor );
|
||||||
|
PrimBuild::begin( GFXLineList, 2*numSide );
|
||||||
|
for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
|
||||||
|
{
|
||||||
|
if(river->mSideProfile.mSegMtrls[i] == MeshRoad::Side)
|
||||||
|
{
|
||||||
|
river->mSideProfile.getNodeWorldPos(i, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
|
||||||
|
river->mSideProfile.getNodeWorldPos(i+1, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrimBuild::end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top
|
||||||
|
if(numTop)
|
||||||
|
{
|
||||||
|
PrimBuild::color( ColorI(0,255,0) );
|
||||||
|
PrimBuild::begin( GFXLineList, 2*numTop );
|
||||||
|
for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
|
||||||
|
{
|
||||||
|
if(river->mSideProfile.mSegMtrls[i] == MeshRoad::Top)
|
||||||
|
{
|
||||||
|
river->mSideProfile.getNodeWorldPos(i, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
|
||||||
|
river->mSideProfile.getNodeWorldPos(i+1, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrimBuild::end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
if(numBottom)
|
||||||
|
{
|
||||||
|
PrimBuild::color( ColorI(255,0,255) );
|
||||||
|
PrimBuild::begin( GFXLineList, 2*numBottom );
|
||||||
|
for ( U32 i = 0; i < river->mSideProfile.mSegMtrls.size(); i++ )
|
||||||
|
{
|
||||||
|
if(river->mSideProfile.mSegMtrls[i] == MeshRoad::Bottom)
|
||||||
|
{
|
||||||
|
river->mSideProfile.getNodeWorldPos(i, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
|
||||||
|
river->mSideProfile.getNodeWorldPos(i+1, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrimBuild::end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render node normals
|
||||||
|
PrimBuild::color( ColorI(255,0,0) );
|
||||||
|
PrimBuild::begin( GFXLineList, 4*river->mSideProfile.mNodes.size() - 4 );
|
||||||
|
for ( U32 i = 0; i < river->mSideProfile.mNodes.size()-1; i++ )
|
||||||
|
{
|
||||||
|
for( U32 j = 0; j < 2; j++)
|
||||||
|
{
|
||||||
|
river->mSideProfile.getNodeWorldPos(i+j, nodePos);
|
||||||
|
PrimBuild::vertex3fv( nodePos );
|
||||||
|
|
||||||
|
river->mSideProfile.getNormWorldPos(2*i+j, normEndPos);
|
||||||
|
PrimBuild::vertex3fv( normEndPos );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrimBuild::end();
|
||||||
|
}
|
||||||
|
|
||||||
// Segment
|
// Segment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -940,8 +1378,46 @@ void GuiMeshRoadEditorCtrl::_drawControlNodes( MeshRoad *river, const ColorI &co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( MeshRoad::smShowRoadProfile && isSelected )
|
||||||
|
theColor.set(100,100,100);
|
||||||
|
|
||||||
drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor );
|
drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw profile control nodes
|
||||||
|
if( MeshRoad::smShowRoadProfile && isSelected )
|
||||||
|
{
|
||||||
|
Point3F wpos;
|
||||||
|
Point3F spos;
|
||||||
|
Point2I posi;
|
||||||
|
ColorI theColor;
|
||||||
|
|
||||||
|
for( U32 i = 0; i < river->mSideProfile.mNodes.size(); i++)
|
||||||
|
{
|
||||||
|
river->mSideProfile.getNodeWorldPos(i, wpos);
|
||||||
|
|
||||||
|
project( wpos, &spos );
|
||||||
|
|
||||||
|
if ( spos.z > 1.0f )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
posi.x = spos.x;
|
||||||
|
posi.y = spos.y;
|
||||||
|
|
||||||
|
if ( !bounds.pointInRect( posi ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(i == 0)
|
||||||
|
theColor.set(mProfileColor.red/3, mProfileColor.green/3, mProfileColor.blue/3,255);
|
||||||
|
else
|
||||||
|
theColor.set(mProfileColor,255);
|
||||||
|
|
||||||
|
if( mSelProfNodeList.find_next(i) != -1 )
|
||||||
|
theColor.set(0,0,255);
|
||||||
|
|
||||||
|
drawer->drawRectFill( posi - mNodeHalfSize, posi + mNodeHalfSize, theColor );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GuiMeshRoadEditorCtrl::getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos )
|
bool GuiMeshRoadEditorCtrl::getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos )
|
||||||
|
|
@ -1174,6 +1650,15 @@ void GuiMeshRoadEditorCtrl::submitUndo( const UTF8 *name )
|
||||||
action->mNodes.push_back( mSelRoad->mNodes[i] );
|
action->mNodes.push_back( mSelRoad->mNodes[i] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save profile nodes and materials
|
||||||
|
for( U32 i = 0; i < mSelRoad->mSideProfile.mNodes.size(); i++)
|
||||||
|
{
|
||||||
|
action->mProfileNodes.push_back( mSelRoad->mSideProfile.mNodes[i] );
|
||||||
|
|
||||||
|
if(i)
|
||||||
|
action->mProfileMtrls.push_back( mSelRoad->mSideProfile.mSegMtrls[i-1] );
|
||||||
|
}
|
||||||
|
|
||||||
undoMan->addAction( action );
|
undoMan->addAction( action );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ class GuiMeshRoadEditorCtrl : public EditTSCtrl
|
||||||
};
|
};
|
||||||
|
|
||||||
S32 _getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi );
|
S32 _getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi );
|
||||||
|
S32 _getProfileNodeAtScreenPos( MeshRoadProfile *pProfile, const Point2I &posi);
|
||||||
void _drawSpline( MeshRoad *road, const ColorI &color );
|
void _drawSpline( MeshRoad *road, const ColorI &color );
|
||||||
void _drawControlNodes( MeshRoad *road, const ColorI &color );
|
void _drawControlNodes( MeshRoad *road, const ColorI &color );
|
||||||
|
|
||||||
|
|
@ -128,9 +129,14 @@ class GuiMeshRoadEditorCtrl : public EditTSCtrl
|
||||||
bool mSavedDrag;
|
bool mSavedDrag;
|
||||||
bool mIsDirty;
|
bool mIsDirty;
|
||||||
|
|
||||||
|
bool mSavedProfileDrag;
|
||||||
|
bool mDeselectProfileNode;
|
||||||
|
|
||||||
SimSet *mRoadSet;
|
SimSet *mRoadSet;
|
||||||
S32 mSelNode;
|
S32 mSelNode;
|
||||||
S32 mHoverNode;
|
S32 mHoverNode;
|
||||||
|
S32 mProfileNode;
|
||||||
|
Vector<U32> mSelProfNodeList;
|
||||||
U32 mAddNodeIdx;
|
U32 mAddNodeIdx;
|
||||||
SimObjectPtr<MeshRoad> mSelRoad;
|
SimObjectPtr<MeshRoad> mSelRoad;
|
||||||
SimObjectPtr<MeshRoad> mHoverRoad;
|
SimObjectPtr<MeshRoad> mHoverRoad;
|
||||||
|
|
@ -146,6 +152,7 @@ class GuiMeshRoadEditorCtrl : public EditTSCtrl
|
||||||
ColorI mHoverSplineColor;
|
ColorI mHoverSplineColor;
|
||||||
ColorI mSelectedSplineColor;
|
ColorI mSelectedSplineColor;
|
||||||
ColorI mHoverNodeColor;
|
ColorI mHoverNodeColor;
|
||||||
|
ColorI mProfileColor;
|
||||||
|
|
||||||
bool mHasCopied;
|
bool mHasCopied;
|
||||||
public:
|
public:
|
||||||
|
|
@ -167,6 +174,8 @@ class GuiMeshRoadEditorUndoAction : public UndoAction
|
||||||
GuiMeshRoadEditorCtrl *mEditor;
|
GuiMeshRoadEditorCtrl *mEditor;
|
||||||
|
|
||||||
Vector<MeshRoadNode> mNodes;
|
Vector<MeshRoadNode> mNodes;
|
||||||
|
Vector<MeshRoadProfileNode> mProfileNodes;
|
||||||
|
Vector<U8> mProfileMtrls;
|
||||||
|
|
||||||
SimObjectId mObjId;
|
SimObjectId mObjId;
|
||||||
F32 mMetersPerSegment;
|
F32 mMetersPerSegment;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -50,6 +50,7 @@
|
||||||
#include "collision/convex.h"
|
#include "collision/convex.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "math/util/decomposePoly.h"
|
||||||
|
|
||||||
//extern U32 gIdxArray[6][2][3];
|
//extern U32 gIdxArray[6][2][3];
|
||||||
|
|
||||||
|
|
@ -61,6 +62,67 @@ struct MeshRoadHitSegment
|
||||||
|
|
||||||
class MeshRoad;
|
class MeshRoad;
|
||||||
|
|
||||||
|
class MeshRoadProfileNode
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Point3F mPos; // The position of the node. Only x and y are used.
|
||||||
|
bool mSmooth; // Is the node smoothed? Determines the normal at the node.
|
||||||
|
|
||||||
|
public:
|
||||||
|
MeshRoadProfileNode() { mSmooth = false; }
|
||||||
|
MeshRoadProfileNode(Point3F p) { mPos = p; mSmooth = false; }
|
||||||
|
|
||||||
|
void setPosition(F32 x, F32 y) { mPos.x = x; mPos.y = y; mPos.z = 0.0f; }
|
||||||
|
Point3F getPosition() { return mPos; }
|
||||||
|
void setSmoothing(bool isSmooth) { mSmooth = isSmooth; }
|
||||||
|
bool isSmooth() { return mSmooth; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// MeshRoadProfile Class
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class MeshRoadProfile
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend class GuiMeshRoadEditorCtrl;
|
||||||
|
friend class MeshRoad;
|
||||||
|
friend class GuiMeshRoadEditorUndoAction;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MeshRoad* mRoad; // A pointer to the Road this profile belongs to
|
||||||
|
Vector<MeshRoadProfileNode> mNodes; // The list of nodes in the profile
|
||||||
|
Vector<VectorF> mNodeNormals; // The list of normals for each node
|
||||||
|
Vector<U8> mSegMtrls; // The list of segment materials
|
||||||
|
MatrixF mObjToSlice; // Transform profile from obj to slice space
|
||||||
|
MatrixF mSliceToObj; // Transform profile from slice to obj space
|
||||||
|
Point3F mStartPos; // Start position of profile in world space
|
||||||
|
decompPoly mCap; // The polygon that caps the ends
|
||||||
|
|
||||||
|
public:
|
||||||
|
MeshRoadProfile();
|
||||||
|
|
||||||
|
S32 clickOnLine(Point3F &p); // In profile space
|
||||||
|
void addPoint(U32 nodeId, Point3F &p); // In profile space
|
||||||
|
void removePoint(U32 nodeId);
|
||||||
|
void setNodePosition(U32 nodeId, Point3F pos); // In profile space
|
||||||
|
void toggleSmoothing(U32 nodeId);
|
||||||
|
void toggleSegMtrl(U32 seg); // Toggle between top, bottom, side
|
||||||
|
void generateNormals();
|
||||||
|
void generateEndCap(F32 width);
|
||||||
|
void setProfileDepth(F32 depth);
|
||||||
|
void setTransform(const MatrixF &mat, const Point3F &p); // Object to slice space transform
|
||||||
|
void getNodeWorldPos(U32 nodeId, Point3F &p); // Get node position in world space
|
||||||
|
void getNormToSlice(U32 normId, VectorF &n); // Get normal vector in slice space
|
||||||
|
void getNormWorldPos(U32 normId, Point3F &p); // Get normal end pos in world space
|
||||||
|
void worldToObj(Point3F &p); // Transform from world to obj space
|
||||||
|
void objToWorld(Point3F &p); // Transform from obj to world space
|
||||||
|
F32 getProfileLen();
|
||||||
|
F32 getNodePosPercent(U32 nodeId);
|
||||||
|
Vector<MeshRoadProfileNode> getNodes() { return mNodes; }
|
||||||
|
void resetProfile(F32 defaultDepth); // Reset profile to 2 default nodes
|
||||||
|
};
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// MeshRoadConvex Class
|
// MeshRoadConvex Class
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
|
|
@ -282,6 +344,9 @@ struct MeshRoadSlice
|
||||||
F32 texCoordV;
|
F32 texCoordV;
|
||||||
|
|
||||||
U32 parentNodeIdx;
|
U32 parentNodeIdx;
|
||||||
|
|
||||||
|
Vector<Point3F> verts;
|
||||||
|
Vector<VectorF> norms;
|
||||||
};
|
};
|
||||||
typedef Vector<MeshRoadSlice> MeshRoadSliceVector;
|
typedef Vector<MeshRoadSlice> MeshRoadSliceVector;
|
||||||
|
|
||||||
|
|
@ -420,6 +485,7 @@ private:
|
||||||
friend class GuiMeshRoadEditorCtrl;
|
friend class GuiMeshRoadEditorCtrl;
|
||||||
friend class GuiMeshRoadEditorUndoAction;
|
friend class GuiMeshRoadEditorUndoAction;
|
||||||
friend class MeshRoadConvex;
|
friend class MeshRoadConvex;
|
||||||
|
friend class MeshRoadProfile;
|
||||||
|
|
||||||
typedef SceneObject Parent;
|
typedef SceneObject Parent;
|
||||||
|
|
||||||
|
|
@ -431,7 +497,8 @@ private:
|
||||||
InitialUpdateMask = Parent::NextFreeMask << 3,
|
InitialUpdateMask = Parent::NextFreeMask << 3,
|
||||||
SelectedMask = Parent::NextFreeMask << 4,
|
SelectedMask = Parent::NextFreeMask << 4,
|
||||||
MaterialMask = Parent::NextFreeMask << 5,
|
MaterialMask = Parent::NextFreeMask << 5,
|
||||||
NextFreeMask = Parent::NextFreeMask << 6,
|
ProfileMask = Parent::NextFreeMask << 6,
|
||||||
|
NextFreeMask = Parent::NextFreeMask << 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -509,12 +576,14 @@ public:
|
||||||
|
|
||||||
/// Protected 'Component' Field setter that will add a component to the list.
|
/// Protected 'Component' Field setter that will add a component to the list.
|
||||||
static bool addNodeFromField( void *object, const char *index, const char *data );
|
static bool addNodeFromField( void *object, const char *index, const char *data );
|
||||||
|
static bool addProfileNodeFromField(void *obj, const char *index, const char* data);
|
||||||
|
|
||||||
static bool smEditorOpen;
|
static bool smEditorOpen;
|
||||||
static bool smWireframe;
|
static bool smWireframe;
|
||||||
static bool smShowBatches;
|
static bool smShowBatches;
|
||||||
static bool smShowSpline;
|
static bool smShowSpline;
|
||||||
static bool smShowRoad;
|
static bool smShowRoad;
|
||||||
|
static bool smShowRoadProfile;
|
||||||
static SimObjectPtr<SimSet> smServerMeshRoadSet;
|
static SimObjectPtr<SimSet> smServerMeshRoadSet;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -557,6 +626,8 @@ protected:
|
||||||
U32 mVertCount[SurfaceCount];
|
U32 mVertCount[SurfaceCount];
|
||||||
U32 mTriangleCount[SurfaceCount];
|
U32 mTriangleCount[SurfaceCount];
|
||||||
|
|
||||||
|
MeshRoadProfile mSideProfile;
|
||||||
|
|
||||||
// Fields.
|
// Fields.
|
||||||
F32 mTextureLength;
|
F32 mTextureLength;
|
||||||
F32 mBreakAngle;
|
F32 mBreakAngle;
|
||||||
|
|
|
||||||
580
Engine/source/math/util/decomposePoly.cpp
Normal file
580
Engine/source/math/util/decomposePoly.cpp
Normal file
|
|
@ -0,0 +1,580 @@
|
||||||
|
#include "math/util/decomposePoly.h"
|
||||||
|
|
||||||
|
// twoIndices Methods
|
||||||
|
|
||||||
|
twoIndices::twoIndices()
|
||||||
|
{
|
||||||
|
i1 = i2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices::twoIndices(U8 a, U8 b)
|
||||||
|
{
|
||||||
|
i1 = a;
|
||||||
|
i2 = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decompTri Methods
|
||||||
|
|
||||||
|
decompTri::decompTri()
|
||||||
|
{
|
||||||
|
mVertIdx[0] = 0;
|
||||||
|
mVertIdx[1] = 0;
|
||||||
|
mVertIdx[2] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompTri::orderVerts()
|
||||||
|
{
|
||||||
|
// Bubble sort, smallest to largest
|
||||||
|
U8 temp;
|
||||||
|
|
||||||
|
for (U8 i = 2; i > 0; i--)
|
||||||
|
{
|
||||||
|
for (U8 j = 0; j < i; j++)
|
||||||
|
{
|
||||||
|
if (mVertIdx[j] > mVertIdx[j + 1])
|
||||||
|
{
|
||||||
|
temp = mVertIdx[j];
|
||||||
|
mVertIdx[j] = mVertIdx[j + 1];
|
||||||
|
mVertIdx[j + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompTri::setVert(U8 val, U8 idx)
|
||||||
|
{
|
||||||
|
if(idx < 3)
|
||||||
|
mVertIdx[idx] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 decompTri::getVert(U8 idx)
|
||||||
|
{
|
||||||
|
if(idx < 3)
|
||||||
|
return mVertIdx[idx];
|
||||||
|
|
||||||
|
return mVertIdx[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices decompTri::getOtherVerts(U8 idx)
|
||||||
|
{
|
||||||
|
if(idx == mVertIdx[0])
|
||||||
|
return twoIndices(mVertIdx[1], mVertIdx[2]);
|
||||||
|
|
||||||
|
if(idx == mVertIdx[1])
|
||||||
|
return twoIndices(mVertIdx[0], mVertIdx[2]);
|
||||||
|
|
||||||
|
if(idx == mVertIdx[2])
|
||||||
|
return twoIndices(mVertIdx[0], mVertIdx[1]);
|
||||||
|
|
||||||
|
return twoIndices(0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decompPoly Methods
|
||||||
|
|
||||||
|
void decompPoly::addVert(Point3F &newVert)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(newVert == mVertList[i])
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found)
|
||||||
|
mVertList.push_back(newVert);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompPoly::initEdgeList()
|
||||||
|
{
|
||||||
|
mEdgeList.clear();
|
||||||
|
|
||||||
|
S32 next = 0;
|
||||||
|
|
||||||
|
for(S32 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(i == mVertList.size()-1)
|
||||||
|
next = 0;
|
||||||
|
else
|
||||||
|
next = i+1;
|
||||||
|
|
||||||
|
mEdgeList.push_back(twoIndices(i, next));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decompPoly::sameSide(Point3F &p1, Point3F &p2, Point3F &l1, Point3F &l2)
|
||||||
|
{
|
||||||
|
return ((p1.x-l1.x)*(l2.y-l1.y)-(l2.x-l1.x)*(p1.y-l1.y))*((p2.x-l1.x)*(l2.y-l1.y)-(l2.x-l1.x)*(p2.y-l1.y)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decompPoly::isInside(decompTri &tri, U8 vertIdx)
|
||||||
|
{
|
||||||
|
Point3F a(mVertList[tri.getVert(0)]);
|
||||||
|
Point3F b(mVertList[tri.getVert(1)]);
|
||||||
|
Point3F c(mVertList[tri.getVert(2)]);
|
||||||
|
Point3F p(mVertList[vertIdx]);
|
||||||
|
|
||||||
|
return (sameSide(p,a,b,c) && sameSide(p,b,a,c) && sameSide(p,c,a,b));
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 decompPoly::leftmost()
|
||||||
|
{
|
||||||
|
F32 xMin = 9999999.f;
|
||||||
|
U8 idx = 0;
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(mVertList[i].x < xMin && isVertInEdgeList(i))
|
||||||
|
{
|
||||||
|
xMin = mVertList[i].x;
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 decompPoly::rightmost()
|
||||||
|
{
|
||||||
|
F32 xMax = -9999999.f;
|
||||||
|
U8 idx = 0;
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(mVertList[i].x > xMax && isVertInEdgeList(i))
|
||||||
|
{
|
||||||
|
xMax = mVertList[i].x;
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 decompPoly::uppermost()
|
||||||
|
{
|
||||||
|
F32 yMax = -9999999.f;
|
||||||
|
U8 idx = 0;
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(mVertList[i].y > yMax && isVertInEdgeList(i))
|
||||||
|
{
|
||||||
|
yMax = mVertList[i].y;
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 decompPoly::lowermost()
|
||||||
|
{
|
||||||
|
F32 yMin = 9999999.f;
|
||||||
|
U8 idx = 0;
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(mVertList[i].y < yMin && isVertInEdgeList(i))
|
||||||
|
{
|
||||||
|
yMin = mVertList[i].y;
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices decompPoly::findEdges(U8 vertIdx, bool ¬Unique)
|
||||||
|
{
|
||||||
|
U8 found = 0;
|
||||||
|
U8 edgeIdx[2];
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mEdgeList.size(); i++)
|
||||||
|
{
|
||||||
|
if(mEdgeList[i].i1 == vertIdx || mEdgeList[i].i2 == vertIdx)
|
||||||
|
{
|
||||||
|
edgeIdx[found++] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notUnique = found > 2;
|
||||||
|
|
||||||
|
return twoIndices(edgeIdx[0], edgeIdx[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
decompTri decompPoly::formTriFromEdges(U8 idx1, U8 idx2)
|
||||||
|
{
|
||||||
|
decompTri tri;
|
||||||
|
|
||||||
|
tri.setVert(mEdgeList[idx1].i1, 0);
|
||||||
|
tri.setVert(mEdgeList[idx1].i2, 1);
|
||||||
|
|
||||||
|
if(mEdgeList[idx2].i1 == tri.getVert(0) || mEdgeList[idx2].i1 == tri.getVert(1))
|
||||||
|
{
|
||||||
|
tri.setVert(mEdgeList[idx2].i2, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tri.setVert(mEdgeList[idx2].i1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tri;
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices decompPoly::leftmostInsideVerts(U8 idx1, U8 idx2)
|
||||||
|
{
|
||||||
|
F32 xMin = 9999999.f;
|
||||||
|
S32 left[] = {-1, -1};
|
||||||
|
|
||||||
|
for(U8 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
for(U8 j = 0; j < mInsideVerts.size(); j++)
|
||||||
|
{
|
||||||
|
if(mVertList[mInsideVerts[j]].x < xMin && mInsideVerts[j] != left[0])
|
||||||
|
{
|
||||||
|
xMin = mVertList[mInsideVerts[j]].x;
|
||||||
|
left[i] = mInsideVerts[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx1].x < xMin && idx1 != left[0])
|
||||||
|
{
|
||||||
|
xMin = mVertList[idx1].x;
|
||||||
|
left[i] = idx1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx2].x < xMin && idx2 != left[0])
|
||||||
|
{
|
||||||
|
xMin = mVertList[idx2].x;
|
||||||
|
left[i] = idx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
xMin = 9999999.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return twoIndices(left[0], left[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices decompPoly::rightmostInsideVerts(U8 idx1, U8 idx2)
|
||||||
|
{
|
||||||
|
F32 xMax = -9999999.f;
|
||||||
|
S32 right[] = {-1, -1};
|
||||||
|
|
||||||
|
for(U8 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
for(U8 j = 0; j < mInsideVerts.size(); j++)
|
||||||
|
{
|
||||||
|
if(mVertList[mInsideVerts[j]].x > xMax && mInsideVerts[j] != right[0])
|
||||||
|
{
|
||||||
|
xMax = mVertList[mInsideVerts[j]].x;
|
||||||
|
right[i] = mInsideVerts[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx1].x > xMax && idx1 != right[0])
|
||||||
|
{
|
||||||
|
xMax = mVertList[idx1].x;
|
||||||
|
right[i] = idx1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx2].x > xMax && idx2 != right[0])
|
||||||
|
{
|
||||||
|
xMax = mVertList[idx2].x;
|
||||||
|
right[i] = idx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
xMax = -9999999.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return twoIndices(right[0], right[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices decompPoly::uppermostInsideVerts(U8 idx1, U8 idx2)
|
||||||
|
{
|
||||||
|
F32 yMax = -9999999.f;
|
||||||
|
S32 up[] = {-1, -1};
|
||||||
|
|
||||||
|
for(U8 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
for(U8 j = 0; j < mInsideVerts.size(); j++)
|
||||||
|
{
|
||||||
|
if(mVertList[mInsideVerts[j]].y > yMax && mInsideVerts[j] != up[0])
|
||||||
|
{
|
||||||
|
yMax = mVertList[mInsideVerts[j]].y;
|
||||||
|
up[i] = mInsideVerts[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx1].y > yMax && idx1 != up[0])
|
||||||
|
{
|
||||||
|
yMax = mVertList[idx1].y;
|
||||||
|
up[i] = idx1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx2].y > yMax && idx2 != up[0])
|
||||||
|
{
|
||||||
|
yMax = mVertList[idx2].y;
|
||||||
|
up[i] = idx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
yMax = -9999999.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return twoIndices(up[0], up[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
twoIndices decompPoly::lowermostInsideVerts(U8 idx1, U8 idx2)
|
||||||
|
{
|
||||||
|
F32 yMin = 9999999.f;
|
||||||
|
S32 down[] = {-1, -1};
|
||||||
|
|
||||||
|
for(U8 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
for(U8 j = 0; j < mInsideVerts.size(); j++)
|
||||||
|
{
|
||||||
|
if(mVertList[mInsideVerts[j]].y < yMin && mInsideVerts[j] != down[0])
|
||||||
|
{
|
||||||
|
yMin = mVertList[mInsideVerts[j]].y;
|
||||||
|
down[i] = mInsideVerts[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx1].y < yMin && idx1 != down[0])
|
||||||
|
{
|
||||||
|
yMin = mVertList[idx1].y;
|
||||||
|
down[i] = idx1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mVertList[idx2].y < yMin && idx2 != down[0])
|
||||||
|
{
|
||||||
|
yMin = mVertList[idx2].y;
|
||||||
|
down[i] = idx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
yMin = 9999999.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return twoIndices(down[0], down[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompPoly::findPointsInside()
|
||||||
|
{
|
||||||
|
mInsideVerts.clear();
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mVertList.size(); i++)
|
||||||
|
{
|
||||||
|
if(i != mTestTri.getVert(0) && i != mTestTri.getVert(1) && i != mTestTri.getVert(2))
|
||||||
|
{
|
||||||
|
if(isInside(mTestTri, i))
|
||||||
|
{
|
||||||
|
mInsideVerts.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompPoly::addRemoveEdge(U8 idx1, U8 idx2)
|
||||||
|
{
|
||||||
|
twoIndices edge1(idx1, idx2);
|
||||||
|
|
||||||
|
if(!mEdgeList.remove(edge1))
|
||||||
|
mEdgeList.push_back(edge1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decompPoly::newPoly()
|
||||||
|
{
|
||||||
|
mVertList.clear();
|
||||||
|
mEdgeList.clear();
|
||||||
|
mInsideVerts.clear();
|
||||||
|
mTris.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decompPoly::isVertInEdgeList(U8 idx)
|
||||||
|
{
|
||||||
|
for(U8 i = 0; i < mEdgeList.size(); i++)
|
||||||
|
{
|
||||||
|
if(mEdgeList[i].i1 == idx || mEdgeList[i].i2 == idx)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point3F decompPoly::getVert(U8 idx)
|
||||||
|
{
|
||||||
|
if(idx < mVertList.size())
|
||||||
|
return mVertList[idx];
|
||||||
|
|
||||||
|
return Point3F(0.0f, 0.0f, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 decompPoly::getTriIdx(U32 tri, U8 idx)
|
||||||
|
{
|
||||||
|
if(tri < mTris.size() && idx < 3)
|
||||||
|
return mTris[tri].getVert(idx);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decompPoly::checkEdgeLength(F32 len)
|
||||||
|
{
|
||||||
|
Point3F p1, p2;
|
||||||
|
|
||||||
|
for(U8 i = 0; i < mEdgeList.size(); i++)
|
||||||
|
{
|
||||||
|
p1 = mVertList[mEdgeList[i].i1];
|
||||||
|
p2 = mVertList[mEdgeList[i].i2];
|
||||||
|
|
||||||
|
p1 = p2 - p1;
|
||||||
|
|
||||||
|
if(p1.len() < len)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------
|
||||||
|
// Concave polygon decomposition into triangles
|
||||||
|
//
|
||||||
|
// Based upon this resource:
|
||||||
|
// http://www.siggraph.org/education/materials/HyperGraph/scanline/outprims/polygon1.htm
|
||||||
|
//
|
||||||
|
// 1. Find leftmost vertex in polygon (smallest value along x-axis).
|
||||||
|
// 2. Find the two edges adjacent to this vertex.
|
||||||
|
// 3. Form a test tri by connecting the open side of these two edges.
|
||||||
|
// 4. See if any of the vertices in the poly, besides the ones that form the test tri, are inside the
|
||||||
|
// test tri.
|
||||||
|
// 5. If there are verts inside, find the 3 leftmost of the inside verts and the test tri verts, form
|
||||||
|
// a new test tri from these verts, and repeat from step 4. When no verts are inside, continue.
|
||||||
|
// 6. Save test tri as a valid triangle.
|
||||||
|
// 7. Remove the edges that the test tri and poly share, and add the new edges from the test tri to
|
||||||
|
// the poly to form a new closed poly with the test tri subtracted out.
|
||||||
|
//
|
||||||
|
// Note: our polygon can contain no more than 255 verts. Complex polygons can create situations where
|
||||||
|
// a vertex has more than 2 adjacent edges, causing step 2 to fail. This method improves on the resource
|
||||||
|
// listed above by not limiting the decomposition to the leftmost side only. If step 2 fails, the
|
||||||
|
// algorithm also tries to decompose the polygon from the top, right, and bottom until one succeeds or
|
||||||
|
// they all fail. If they all fail, the polygon is too complex to be decomposed with this method.
|
||||||
|
|
||||||
|
bool decompPoly::decompose()
|
||||||
|
{
|
||||||
|
// Must have at least 3 verts to form a poly
|
||||||
|
if(mVertList.size() < 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Clear out any previously stored tris
|
||||||
|
mTris.clear();
|
||||||
|
|
||||||
|
// Initialize the edge list with the default edges
|
||||||
|
initEdgeList();
|
||||||
|
|
||||||
|
twoIndices otherVerts, outerVerts2;
|
||||||
|
U32 counter = 0;
|
||||||
|
U8 uniqueVertAttempt = 0;
|
||||||
|
U8 formTriAttempt = 0;
|
||||||
|
bool notUnique = false;
|
||||||
|
|
||||||
|
// The main decomposition loop
|
||||||
|
while(mEdgeList.size() > 3)
|
||||||
|
{
|
||||||
|
// Find outermost vert in poly, LMV
|
||||||
|
U8 outerVertIdx;
|
||||||
|
|
||||||
|
switch(uniqueVertAttempt)
|
||||||
|
{
|
||||||
|
case 0: outerVertIdx = leftmost(); break;
|
||||||
|
case 1: outerVertIdx = rightmost(); break;
|
||||||
|
case 2: outerVertIdx = uppermost(); break;
|
||||||
|
case 3: outerVertIdx = uppermost(); break;
|
||||||
|
default: outerVertIdx = leftmost();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find edges that share LMV
|
||||||
|
twoIndices edgesIdx = findEdges(outerVertIdx, notUnique);
|
||||||
|
|
||||||
|
// If vert shares more than two edges, try decomposing from different direction
|
||||||
|
if(notUnique)
|
||||||
|
{
|
||||||
|
if(uniqueVertAttempt < 4)
|
||||||
|
{
|
||||||
|
uniqueVertAttempt++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newPoly();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if(edgesIdx.i1 >= mEdgeList.size() || edgesIdx.i2 >= mEdgeList.size())
|
||||||
|
{
|
||||||
|
newPoly();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form the test tri from these 2 edges
|
||||||
|
mTestTri = formTriFromEdges(edgesIdx.i1, edgesIdx.i2);
|
||||||
|
|
||||||
|
// See if there are any other poly verts inside the test tri
|
||||||
|
findPointsInside();
|
||||||
|
|
||||||
|
// If there are verts inside, repeat procedure until there are none
|
||||||
|
while(mInsideVerts.size() > 0 && formTriAttempt++ < mVertList.size())
|
||||||
|
{
|
||||||
|
// Get the two verts of the test tri that are not the LMV
|
||||||
|
otherVerts = mTestTri.getOtherVerts(outerVertIdx);
|
||||||
|
|
||||||
|
// Get the 2 outermost verts of the inside and test tri verts (excluding LMV)
|
||||||
|
switch(uniqueVertAttempt)
|
||||||
|
{
|
||||||
|
case 0: outerVerts2 = leftmostInsideVerts(otherVerts.i1, otherVerts.i2); break;
|
||||||
|
case 1: outerVerts2 = rightmostInsideVerts(otherVerts.i1, otherVerts.i2); break;
|
||||||
|
case 2: outerVerts2 = uppermostInsideVerts(otherVerts.i1, otherVerts.i2); break;
|
||||||
|
case 3: outerVerts2 = uppermostInsideVerts(otherVerts.i1, otherVerts.i2); break;
|
||||||
|
default: outerVerts2 = leftmostInsideVerts(otherVerts.i1, otherVerts.i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form a new test tri from LMV, and the 2 new leftmost verts above
|
||||||
|
mTestTri.setVert(outerVerts2.i1, 0);
|
||||||
|
mTestTri.setVert(outerVertIdx, 1);
|
||||||
|
mTestTri.setVert(outerVerts2.i2, 2);
|
||||||
|
|
||||||
|
// See if there are verts inside this new test tri
|
||||||
|
findPointsInside();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have found a valid tri
|
||||||
|
mTris.push_back(mTestTri);
|
||||||
|
|
||||||
|
// Remove edges common to test tri and poly... add unique edges from test tri to poly
|
||||||
|
addRemoveEdge(mTestTri.getVert(0), mTestTri.getVert(1));
|
||||||
|
addRemoveEdge(mTestTri.getVert(1), mTestTri.getVert(2));
|
||||||
|
addRemoveEdge(mTestTri.getVert(0), mTestTri.getVert(2));
|
||||||
|
|
||||||
|
// This should never take 255 iterations... we must be stuck in an infinite loop
|
||||||
|
if(counter++ > 255)
|
||||||
|
{
|
||||||
|
newPoly();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset attempts
|
||||||
|
formTriAttempt = 0;
|
||||||
|
uniqueVertAttempt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last tri
|
||||||
|
mTris.push_back(formTriFromEdges(0,1));
|
||||||
|
|
||||||
|
// The verts need to be ordered to draw correctly
|
||||||
|
for(U8 i = 0; i < mTris.size(); i++)
|
||||||
|
{
|
||||||
|
mTris[i].orderVerts();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
81
Engine/source/math/util/decomposePoly.h
Normal file
81
Engine/source/math/util/decomposePoly.h
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#ifndef DECOMPOSE_POLY_H
|
||||||
|
#define DECOMPOSE_POLY_H
|
||||||
|
|
||||||
|
#include "core/util/tVector.h"
|
||||||
|
#include "math/mathTypes.h"
|
||||||
|
#include "math/mPoint3.h"
|
||||||
|
|
||||||
|
struct twoIndices{
|
||||||
|
U8 i1, i2;
|
||||||
|
|
||||||
|
public:
|
||||||
|
twoIndices();
|
||||||
|
twoIndices(U8 a, U8 b);
|
||||||
|
bool operator==(const twoIndices&) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline bool twoIndices::operator==(const twoIndices& _test) const
|
||||||
|
{
|
||||||
|
return ((i1 == _test.i1) && (i2 == _test.i2) || (i1 == _test.i2) && (i2 == _test.i1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class decompTri
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
U8 mVertIdx[3];
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
decompTri();
|
||||||
|
void setVert(U8 val, U8 idx);
|
||||||
|
U8 getVert(U8 idx);
|
||||||
|
twoIndices getOtherVerts(U8 idx);
|
||||||
|
void orderVerts();
|
||||||
|
};
|
||||||
|
|
||||||
|
class decompPoly
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
Vector<Point3F> mVertList;
|
||||||
|
Vector<twoIndices> mEdgeList;
|
||||||
|
Vector<U8> mInsideVerts;
|
||||||
|
Vector<decompTri> mTris;
|
||||||
|
|
||||||
|
decompTri mTestTri;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void initEdgeList();
|
||||||
|
bool sameSide(Point3F &p1, Point3F &p2, Point3F &l1, Point3F &l2);
|
||||||
|
bool isInside(decompTri &tri, U8 vertIdx);
|
||||||
|
U8 leftmost();
|
||||||
|
U8 rightmost();
|
||||||
|
U8 uppermost();
|
||||||
|
U8 lowermost();
|
||||||
|
twoIndices findEdges(U8 idx, bool ¬Unique);
|
||||||
|
decompTri formTriFromEdges(U8 idx1, U8 idx2);
|
||||||
|
twoIndices leftmostInsideVerts(U8 idx1, U8 idx2);
|
||||||
|
twoIndices rightmostInsideVerts(U8 idx1, U8 idx2);
|
||||||
|
twoIndices uppermostInsideVerts(U8 idx1, U8 idx2);
|
||||||
|
twoIndices lowermostInsideVerts(U8 idx1, U8 idx2);
|
||||||
|
void findPointsInside();
|
||||||
|
void addRemoveEdge(U8 idx1, U8 idx2);
|
||||||
|
bool isVertInEdgeList(U8 idx);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
void addVert(Point3F &newVert);
|
||||||
|
Point3F getVert(U8 idx);
|
||||||
|
void newPoly();
|
||||||
|
U8 getNumVerts() { return mVertList.size(); }
|
||||||
|
U32 getNumTris() { return mTris.size(); }
|
||||||
|
U8 getTriIdx(U32 tri, U8 idx);
|
||||||
|
bool checkEdgeLength(F32 len);
|
||||||
|
bool decompose();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -59,6 +59,7 @@ function initializeMeshRoadEditor()
|
||||||
%map.bindCmd( keyboard, "z", "MeshRoadEditorShowSplineBtn.performClick();", "" );
|
%map.bindCmd( keyboard, "z", "MeshRoadEditorShowSplineBtn.performClick();", "" );
|
||||||
%map.bindCmd( keyboard, "x", "MeshRoadEditorWireframeBtn.performClick();", "" );
|
%map.bindCmd( keyboard, "x", "MeshRoadEditorWireframeBtn.performClick();", "" );
|
||||||
%map.bindCmd( keyboard, "v", "MeshRoadEditorShowRoadBtn.performClick();", "" );
|
%map.bindCmd( keyboard, "v", "MeshRoadEditorShowRoadBtn.performClick();", "" );
|
||||||
|
%map.bindCmd( keyboard, "p", "MeshRoadEditorShowProfileBtn.performClick();", "");
|
||||||
MeshRoadEditorPlugin.map = %map;
|
MeshRoadEditorPlugin.map = %map;
|
||||||
|
|
||||||
MeshRoadEditorPlugin.initSettings();
|
MeshRoadEditorPlugin.initSettings();
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ $MeshRoad::wireframe = true;
|
||||||
$MeshRoad::showSpline = true;
|
$MeshRoad::showSpline = true;
|
||||||
$MeshRoad::showReflectPlane = false;
|
$MeshRoad::showReflectPlane = false;
|
||||||
$MeshRoad::showRoad = true;
|
$MeshRoad::showRoad = true;
|
||||||
|
$MeshRoad::showRoadProfile = false;
|
||||||
|
|
||||||
$MeshRoad::breakAngle = 3.0;
|
$MeshRoad::breakAngle = 3.0;
|
||||||
|
|
||||||
function MeshRoadEditorGui::onWake( %this )
|
function MeshRoadEditorGui::onWake( %this )
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
};
|
};
|
||||||
new GuiDynamicCtrlArrayControl(){
|
new GuiDynamicCtrlArrayControl(){
|
||||||
Position = "116 3";
|
Position = "116 3";
|
||||||
extent = "111 32";
|
extent = "146 32";
|
||||||
colCount = "31";
|
colCount = "31";
|
||||||
colSize = "29";
|
colSize = "29";
|
||||||
rowCount = "1";
|
rowCount = "1";
|
||||||
|
|
@ -103,6 +103,29 @@
|
||||||
buttonType = "ToggleButton";
|
buttonType = "ToggleButton";
|
||||||
useMouseEvents = "0";
|
useMouseEvents = "0";
|
||||||
useInactiveState = "0";
|
useInactiveState = "0";
|
||||||
|
};
|
||||||
|
new GuiBitmapButtonCtrl(MeshRoadEditorShowProfileBtn) {
|
||||||
|
canSaveDynamicFields = "0";
|
||||||
|
Enabled = "1";
|
||||||
|
isContainer = "0";
|
||||||
|
Profile = "GuiDefalutProfile";
|
||||||
|
HorizSizing = "right";
|
||||||
|
VertSizing = "bottom";
|
||||||
|
Position = "89 3";
|
||||||
|
Extent = "29 27";
|
||||||
|
MinExtent = "8 2";
|
||||||
|
canSave = "1";
|
||||||
|
isDecoy = "0";
|
||||||
|
Visible = "1";
|
||||||
|
Variable = "$MeshRoad::showRoadProfile";
|
||||||
|
tooltipprofile = "ToolsGuiToolTipProfile";
|
||||||
|
hovertime = "1000";
|
||||||
|
toolTip = "Show Road Profile (P)";
|
||||||
|
bitmap = "tools/worldEditor/images/road-river/menubar/show-profile";
|
||||||
|
groupNum = "-1";
|
||||||
|
buttonType = "ToggleButton";
|
||||||
|
useMouseEvents = "0";
|
||||||
|
useInactiveState = "0";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
new GuiControl(MeshRoadDefaultWidthTextEditContainer) {
|
new GuiControl(MeshRoadDefaultWidthTextEditContainer) {
|
||||||
|
|
@ -111,7 +134,7 @@
|
||||||
Profile = "ToolsGuiTransparentProfile";
|
Profile = "ToolsGuiTransparentProfile";
|
||||||
HorizSizing = "right";
|
HorizSizing = "right";
|
||||||
VertSizing = "bottom";
|
VertSizing = "bottom";
|
||||||
position = "230 5";
|
position = "265 5";
|
||||||
Extent = "120 50";
|
Extent = "120 50";
|
||||||
MinExtent = "8 2";
|
MinExtent = "8 2";
|
||||||
canSave = "1";
|
canSave = "1";
|
||||||
|
|
@ -189,7 +212,7 @@
|
||||||
Profile = "ToolsGuiTransparentProfile";
|
Profile = "ToolsGuiTransparentProfile";
|
||||||
HorizSizing = "right";
|
HorizSizing = "right";
|
||||||
VertSizing = "bottom";
|
VertSizing = "bottom";
|
||||||
position = "360 5";
|
position = "395 5";
|
||||||
Extent = "120 50";
|
Extent = "120 50";
|
||||||
MinExtent = "8 2";
|
MinExtent = "8 2";
|
||||||
canSave = "1";
|
canSave = "1";
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 708 B |
Loading…
Add table
Add a link
Reference in a new issue