Browse Source

Profile editor for the meshRoad object

credit to Ryan Mounts (RDM)

found originally at http://www.garagegames.com/community/forums/viewthread/105391
Lukas Aldershaab 4 years ago
parent
commit
973fd44c6a

+ 487 - 2
Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp

@@ -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,8 +1182,12 @@ 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 );
-		PrimBuild::begin( GFXLineStrip, river->mSlices.size() );            
+		if( MeshRoad::smShowRoadProfile )
+			PrimBuild::color( ColorI(100,100,100) );
+		else
+			PrimBuild::color( color );
+
+		PrimBuild::begin( GFXLineStrip, river->mSlices.size() );
 		for ( U32 i = 0; i < river->mSlices.size(); i++ )
 		for ( U32 i = 0; i < river->mSlices.size(); i++ )
 		{            		      
 		{            		      
 			PrimBuild::vertex3fv( river->mSlices[i].p1 );		      
 			PrimBuild::vertex3fv( river->mSlices[i].p1 );		      
@@ -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 );
 }
 }
 
 

+ 9 - 0
Engine/source/environment/editors/guiMeshRoadEditorCtrl.h

@@ -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
+ 770 - 103
Engine/source/environment/meshRoad.cpp


+ 72 - 1
Engine/source/environment/meshRoad.h

@@ -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:
@@ -556,6 +625,8 @@ protected:
 
 
    U32 mVertCount[SurfaceCount];
    U32 mVertCount[SurfaceCount];
    U32 mTriangleCount[SurfaceCount];   
    U32 mTriangleCount[SurfaceCount];   
+
+   MeshRoadProfile mSideProfile;
       
       
    // Fields.
    // Fields.
    F32 mTextureLength;
    F32 mTextureLength;

+ 580 - 0
Engine/source/math/util/decomposePoly.cpp

@@ -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 &notUnique)
+{
+   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 - 0
Engine/source/math/util/decomposePoly.h

@@ -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 &notUnique);
+   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

+ 1 - 0
Templates/BaseGame/game/tools/meshRoadEditor/main.cs

@@ -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();

+ 2 - 0
Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorGui.cs

@@ -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 )

+ 26 - 3
Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorToolbar.gui

@@ -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";

BIN
Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_d.png


BIN
Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_h.png


BIN
Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_n.png


Some files were not shown because too many files changed in this diff