diff --git a/Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp b/Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp index c9bd76d33..d1a75f40d 100644 --- a/Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp +++ b/Engine/source/environment/editors/guiMeshRoadEditorCtrl.cpp @@ -55,6 +55,14 @@ ConsoleDocClass( GuiMeshRoadEditorCtrl, "@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() : // Each of the mode names directly correlates with the Mesh Road Editor's @@ -69,6 +77,10 @@ GuiMeshRoadEditorCtrl::GuiMeshRoadEditorCtrl() mRotatePointMode("MeshRoadEditorRotateMode"), mSavedDrag(false), mIsDirty( false ), + mSavedProfileDrag( false ), + mDeselectProfileNode( false ), + mProfileNode( -1 ), + mProfileColor( 255,255,0 ), mRoadSet( NULL ), mSelNode( -1 ), mHoverNode( -1 ), @@ -116,6 +128,48 @@ void GuiMeshRoadEditorUndoAction::undo() object->_addNode( mNodes[i].point, mNodes[i].width, mNodes[i].depth, mNodes[i].normal ); } + // Temporarily save the Roads current profile data. + Vector profNodes; + Vector 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 object->regenerate(); @@ -228,6 +282,158 @@ void GuiMeshRoadEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) if ( !isFirstResponder() ) 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 Point3F tPos; if ( !getStaticPos( event, tPos ) ) @@ -588,6 +794,33 @@ void GuiMeshRoadEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) 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(); } @@ -661,6 +894,43 @@ void GuiMeshRoadEditorCtrl::on3DMouseMove(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 if ( !mSelRoad || mSelNode == -1 || ( mMode != mMovePointMode && mMode != mScalePointMode && mMode != mRotatePointMode ) ) @@ -756,6 +1026,18 @@ void GuiMeshRoadEditorCtrl::renderScene(const RectI & updateRect) Point3F 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 ) { _drawSpline( mHoverRoad, mHoverSplineColor ); @@ -806,6 +1088,37 @@ void GuiMeshRoadEditorCtrl::renderScene(const RectI & updateRect) _drawControlNodes( mHoverRoad, mHoverSplineColor ); if ( mSelRoad ) _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 ) @@ -834,6 +1147,33 @@ S32 GuiMeshRoadEditorCtrl::_getNodeAtScreenPos( const MeshRoad *pRoad, const Poi 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 ) { if ( river->mSlices.size() <= 1 ) @@ -842,8 +1182,12 @@ void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color ) if ( MeshRoad::smShowSpline ) { // 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++ ) { PrimBuild::vertex3fv( river->mSlices[i].p1 ); @@ -880,6 +1224,100 @@ void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color ) 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 } @@ -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 ); } + + // 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 ) @@ -1174,6 +1650,15 @@ void GuiMeshRoadEditorCtrl::submitUndo( const UTF8 *name ) 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 ); } diff --git a/Engine/source/environment/editors/guiMeshRoadEditorCtrl.h b/Engine/source/environment/editors/guiMeshRoadEditorCtrl.h index f4b34004f..31c0ddd33 100644 --- a/Engine/source/environment/editors/guiMeshRoadEditorCtrl.h +++ b/Engine/source/environment/editors/guiMeshRoadEditorCtrl.h @@ -117,6 +117,7 @@ class GuiMeshRoadEditorCtrl : public EditTSCtrl }; S32 _getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi ); + S32 _getProfileNodeAtScreenPos( MeshRoadProfile *pProfile, const Point2I &posi); void _drawSpline( MeshRoad *road, const ColorI &color ); void _drawControlNodes( MeshRoad *road, const ColorI &color ); @@ -128,9 +129,14 @@ class GuiMeshRoadEditorCtrl : public EditTSCtrl bool mSavedDrag; bool mIsDirty; + bool mSavedProfileDrag; + bool mDeselectProfileNode; + SimSet *mRoadSet; S32 mSelNode; S32 mHoverNode; + S32 mProfileNode; + Vector mSelProfNodeList; U32 mAddNodeIdx; SimObjectPtr mSelRoad; SimObjectPtr mHoverRoad; @@ -146,6 +152,7 @@ class GuiMeshRoadEditorCtrl : public EditTSCtrl ColorI mHoverSplineColor; ColorI mSelectedSplineColor; ColorI mHoverNodeColor; + ColorI mProfileColor; bool mHasCopied; public: @@ -167,6 +174,8 @@ class GuiMeshRoadEditorUndoAction : public UndoAction GuiMeshRoadEditorCtrl *mEditor; Vector mNodes; + Vector mProfileNodes; + Vector mProfileMtrls; SimObjectId mObjId; F32 mMetersPerSegment; diff --git a/Engine/source/environment/meshRoad.cpp b/Engine/source/environment/meshRoad.cpp index f9f853ada..cf15ef9ab 100644 --- a/Engine/source/environment/meshRoad.cpp +++ b/Engine/source/environment/meshRoad.cpp @@ -81,7 +81,9 @@ static S32 QSORT_CALLBACK compareHitSegments(const void* a,const void* b) const MeshRoadHitSegment *fa = (MeshRoadHitSegment*)a; const MeshRoadHitSegment *fb = (MeshRoadHitSegment*)b; - return mSign(fb->t - fa->t); + F32 diff = fb->t - fa->t; + + return (diff > 0) ? 1 : (diff < 0) ? -1 : 0; } @@ -265,6 +267,289 @@ void MeshRoadNodeListNotify::sendNotification( NodeListManager::NodeList* list ) } } +//------------------------------------------------------------------------- +// MeshRoadProfile Class +//------------------------------------------------------------------------- + +MeshRoadProfile::MeshRoadProfile() +{ + mRoad = NULL; + + // Set transformation matrix to identity + mObjToSlice.identity(); + mSliceToObj.identity(); +} + +S32 MeshRoadProfile::clickOnLine(Point3F &p) +{ + Point3F newProfilePt; + Point3F ptOnSegment; + F32 dist = 0.0f; + F32 minDist = 99999.0f; + U32 idx = 0; + + for(U32 i=0; i < mNodes.size()-1; i++) + { + ptOnSegment = MathUtils::mClosestPointOnSegment(mNodes[i].getPosition(), mNodes[i+1].getPosition(), p); + + dist = (p - ptOnSegment).len(); + + if(dist < minDist) + { + minDist = dist; + newProfilePt = ptOnSegment; + idx = i+1; + } + } + + if(minDist <= 0.1f) + { + p.set(newProfilePt.x, newProfilePt.y, newProfilePt.z); + + return idx; + } + + return -1; +} + +void MeshRoadProfile::addPoint(U32 nodeId, Point3F &p) +{ + if(nodeId < mNodes.size() && nodeId != 0) + { + p.z = 0.0f; + mNodes.insert(nodeId, p); + mSegMtrls.insert(nodeId-1, mSegMtrls[nodeId-1]); + mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask); + generateNormals(); + } +} + +void MeshRoadProfile::removePoint(U32 nodeId) +{ + if(nodeId > 0 && nodeId < mNodes.size()-1) + { + mNodes.erase(nodeId); + mSegMtrls.remove(nodeId-1); + mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask); + generateNormals(); + } +} + +void MeshRoadProfile::setNodePosition(U32 nodeId, Point3F pos) +{ + if(nodeId < mNodes.size()) + { + mNodes[nodeId].setPosition(pos.x, pos.y); + mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask); + generateNormals(); + } +} + +void MeshRoadProfile::toggleSmoothing(U32 nodeId) +{ + if(nodeId > 0 && nodeId < mNodes.size()-1) + { + mNodes[nodeId].setSmoothing(!mNodes[nodeId].isSmooth()); + mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask); + generateNormals(); + } +} + +void MeshRoadProfile::toggleSegMtrl(U32 seg) +{ + if(seg < mSegMtrls.size()) + { + switch(mSegMtrls[seg]) + { + case MeshRoad::Side: mSegMtrls[seg] = MeshRoad::Top; break; + case MeshRoad::Top: mSegMtrls[seg] = MeshRoad::Bottom; break; + case MeshRoad::Bottom: mSegMtrls[seg] = MeshRoad::Side; break; + } + + mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask); + } +} + +void MeshRoadProfile::generateNormals() +{ + VectorF t, b, n, t2, n2; + Point3F averagePt; + + mNodeNormals.clear(); + + // Loop through all profile line segments + for(U32 i=0; i < mNodes.size()-1; i++) + { + // Calculate normal for each node in line segment + for(U32 j=0; j<2; j++) + { + // Smoothed Node: Average the node with nodes before and after. + // Direction between the node and the average is the smoothed normal. + if( mNodes[i+j].isSmooth() ) + { + b = Point3F(0.0f, 0.0f, 1.0f); + t = mNodes[i+j-1].getPosition() - mNodes[i+j].getPosition(); + n = mCross(t, b); + n.normalizeSafe(); + + t2 = mNodes[i+j].getPosition() - mNodes[i+j+1].getPosition(); + n2 = mCross(t2, b); + n2.normalizeSafe(); + + n += n2; + } + // Non-smoothed Node: Normal is perpendicular to segment. + else + { + b = Point3F(0.0f, 0.0f, 1.0f); + t = mNodes[i].getPosition() - mNodes[i+1].getPosition(); + n = mCross(t, b); + } + + n.normalizeSafe(); + mNodeNormals.push_back(n); + } + } +} + +void MeshRoadProfile::generateEndCap(F32 width) +{ + Point3F pt; + + mCap.newPoly(); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + pt = mNodes[i].getPosition(); + mCap.addVert(pt); + } + + for ( S32 i = mNodes.size()-1; i >= 0; i-- ) + { + pt = mNodes[i].getPosition(); + pt.x = -pt.x - width; + mCap.addVert(pt); + } + + mCap.decompose(); +} + +void MeshRoadProfile::setProfileDepth(F32 depth) +{ + Point3F curPos = mNodes[mNodes.size()-1].getPosition(); + + mNodes[mNodes.size()-1].setPosition(curPos.x, -depth); +} + +void MeshRoadProfile::setTransform(const MatrixF &mat, const Point3F &p) +{ + mObjToSlice.identity(); + mSliceToObj.identity(); + + mObjToSlice *= mat; + mSliceToObj *= mObjToSlice.inverse(); + mSliceToObj.transpose(); + + mStartPos = p; +} + +void MeshRoadProfile::getNodeWorldPos(U32 nodeId, Point3F &p) +{ + if(nodeId < mNodes.size()) + { + p = mNodes[nodeId].getPosition(); + mObjToSlice.mulP(p); + p += mStartPos; + } +} + +void MeshRoadProfile::getNormToSlice(U32 normId, VectorF &n) +{ + if(normId < mNodeNormals.size()) + { + n = mNodeNormals[normId]; + mObjToSlice.mulP(n); + } +} + +void MeshRoadProfile::getNormWorldPos(U32 normId, Point3F &p) +{ + if(normId < mNodeNormals.size()) + { + U32 nodeId = normId/2 + (U32)(mFmod(normId,2.0f)); + p = mNodes[nodeId].getPosition(); + p += 0.5f * mNodeNormals[normId]; // Length = 0.5 units + mObjToSlice.mulP(p); + p += mStartPos; + } +} + +void MeshRoadProfile::worldToObj(Point3F &p) +{ + p -= mStartPos; + mSliceToObj.mulP(p); + p.z = 0.0f; +} + +void MeshRoadProfile::objToWorld(Point3F &p) +{ + mObjToSlice.mulP(p); + p += mStartPos; +} + +F32 MeshRoadProfile::getProfileLen() +{ + F32 sum = 0.0f; + Point3F segmentVec; + + for(U32 i=0; i < mNodes.size()-1; i++) + { + segmentVec = mNodes[i+1].getPosition() - mNodes[i].getPosition(); + sum += segmentVec.len(); + } + + return sum; +} + +F32 MeshRoadProfile::getNodePosPercent(U32 nodeId) +{ + nodeId = mFmod(nodeId, mNodes.size()); + + if(nodeId == 0) + return 0.0f; + else if(nodeId == mNodes.size()-1) + return 1.0f; + + F32 totLen = getProfileLen(); + F32 sum = 0.0f; + Point3F segmentVec; + + for(U32 i=0; i < nodeId; i++) + { + segmentVec = mNodes[i+1].getPosition() - mNodes[i].getPosition(); + sum += segmentVec.len(); + } + + return sum/totLen; +} + +void MeshRoadProfile::resetProfile(F32 defaultDepth) +{ + Point3F pos(0.0f, 0.0f, 0.0f); + + mNodes.clear(); + mNodes.push_back(pos); + + pos.y = -defaultDepth; + mNodes.push_back(pos); + + mSegMtrls.clear(); + mSegMtrls.push_back(MeshRoad::Side); + + mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask); + generateNormals(); +} + //------------------------------------------------------------------------------ // MeshRoadConvex Class //------------------------------------------------------------------------------ @@ -460,13 +745,12 @@ MeshRoadSegment::MeshRoadSegment( MeshRoadSlice *rs0, MeshRoadSlice *rs1, const // Calculate the bounding box(s) worldbounds.minExtents = worldbounds.maxExtents = rs0->p0; - worldbounds.extend( rs0->p2 ); - worldbounds.extend( rs0->pb0 ); - worldbounds.extend( rs0->pb2 ); - worldbounds.extend( rs1->p0 ); - worldbounds.extend( rs1->p2 ); - worldbounds.extend( rs1->pb0 ); - worldbounds.extend( rs1->pb2 ); + + for(U32 i=0; i < rs0->verts.size(); i++) + worldbounds.extend( rs0->verts[i] ); + + for(U32 i=0; i < rs1->verts.size(); i++) + worldbounds.extend( rs1->verts[i] ); objectbounds = worldbounds; roadMat.mul( objectbounds ); @@ -606,6 +890,7 @@ bool MeshRoad::smEditorOpen = false; bool MeshRoad::smShowBatches = false; bool MeshRoad::smShowSpline = true; bool MeshRoad::smShowRoad = true; +bool MeshRoad::smShowRoadProfile = false; bool MeshRoad::smWireframe = true; SimObjectPtr MeshRoad::smServerMeshRoadSet = NULL; @@ -625,7 +910,7 @@ MeshRoad::MeshRoad() mTypeMask |= StaticObjectType | StaticShapeObjectType; mNetFlags.set(Ghostable); - mMatInst[Top] = NULL; + mMatInst[Top] = NULL; mMatInst[Bottom] = NULL; mMatInst[Side] = NULL; mTypeMask |= TerrainLikeObjectType; @@ -634,6 +919,8 @@ MeshRoad::MeshRoad() mVertCount[i] = 0; mTriangleCount[i] = 0; } + + mSideProfile.mRoad = this; } MeshRoad::~MeshRoad() @@ -671,6 +958,9 @@ void MeshRoad::initPersistFields() addProtectedField( "Node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn, "Do not modify, for internal use." ); + addProtectedField( "ProfileNode", TypeString, NULL, &addProfileNodeFromField, &emptyStringProtectedGetFn, + "Do not modify, for internal use." ); + endGroup( "Internal" ); Parent::initPersistFields(); @@ -681,15 +971,17 @@ void MeshRoad::consoleInit() Parent::consoleInit(); Con::addVariable( "$MeshRoad::EditorOpen", TypeBool, &MeshRoad::smEditorOpen, "True if the MeshRoad editor is open, otherwise false.\n" - "@ingroup Editors\n"); + "@ingroup Editors\n"); Con::addVariable( "$MeshRoad::wireframe", TypeBool, &MeshRoad::smWireframe, "If true, will render the wireframe of the road.\n" - "@ingroup Editors\n"); + "@ingroup Editors\n"); Con::addVariable( "$MeshRoad::showBatches", TypeBool, &MeshRoad::smShowBatches, "Determines if the debug rendering of the batches cubes is displayed or not.\n" - "@ingroup Editors\n"); + "@ingroup Editors\n"); Con::addVariable( "$MeshRoad::showSpline", TypeBool, &MeshRoad::smShowSpline, "If true, the spline on which the curvature of this road is based will be rendered.\n" - "@ingroup Editors\n"); + "@ingroup Editors\n"); Con::addVariable( "$MeshRoad::showRoad", TypeBool, &MeshRoad::smShowRoad, "If true, the road will be rendered. When in the editor, roads are always rendered regardless of this flag.\n" - "@ingroup Editors\n"); + "@ingroup Editors\n"); + Con::addVariable( "$MeshRoad::showRoadProfile", TypeBool, &MeshRoad::smShowRoadProfile, "If true, the road profile will be shown in the editor.\n" + "@ingroup Editors\n"); } bool MeshRoad::addNodeFromField( void *object, const char *index, const char *data ) @@ -708,6 +1000,28 @@ bool MeshRoad::addNodeFromField( void *object, const char *index, const char *da return false; } +bool MeshRoad::addProfileNodeFromField( void* obj, const char *index, const char* data ) +{ + MeshRoad *pObj = static_cast(obj); + + F32 x, y; + U32 smooth, mtrl; + + U32 result = dSscanf( data, "%g %g %d %d", &x, &y, &smooth, &mtrl ); + if ( result == 4 ) + { + if(!pObj->mSideProfile.mNodes.empty()) + pObj->mSideProfile.mSegMtrls.push_back(mtrl); + + MeshRoadProfileNode node; + node.setPosition(x, y); + node.setSmoothing(smooth != 0); + pObj->mSideProfile.mNodes.push_back(node); + } + + return false; +} + bool MeshRoad::onAdd() { if ( !Parent::onAdd() ) @@ -729,6 +1043,27 @@ bool MeshRoad::onAdd() if ( isClientObject() ) _initMaterial(); + // If this road was not created from a file, give profile two default nodes + if(mSideProfile.mNodes.empty()) + { + // Initialize with two nodes in vertical line with unit length + MeshRoadProfileNode node1(Point3F(0.0f, 0.0f, 0.0f)); + MeshRoadProfileNode node2(Point3F(0.0f, -5.0f, 0.0f)); + + mSideProfile.mNodes.push_back(node1); + mSideProfile.mNodes.push_back(node2); + + // Both node normals are straight to the right, perpendicular to the profile line + VectorF norm(1.0f, 0.0f, 0.0f); + + mSideProfile.mNodeNormals.push_back(norm); + mSideProfile.mNodeNormals.push_back(norm); + + mSideProfile.mSegMtrls.push_back(MeshRoad::Side); + } + else + mSideProfile.generateNormals(); + // Generate the Vert/Index buffers and everything else. _regenerate(); @@ -793,6 +1128,33 @@ void MeshRoad::writeFields( Stream &stream, U32 tabStop ) dSprintf( buffer, 1024, "Node = \"%g %g %g %g %g %g %g %g\";", node.point.x, node.point.y, node.point.z, node.width, node.depth, node.normal.x, node.normal.y, node.normal.z ); stream.writeLine( (const U8*)buffer ); } + + stream.write(2, "\r\n"); + + Point3F nodePos; + U8 smooth, mtrl; + + for ( U32 i = 0; i < mSideProfile.mNodes.size(); i++ ) + { + nodePos = mSideProfile.mNodes[i].getPosition(); + + if(i) + mtrl = mSideProfile.mSegMtrls[i-1]; + else + mtrl = 0; + + if(mSideProfile.mNodes[i].isSmooth()) + smooth = 1; + else + smooth = 0; + + stream.writeTabs(tabStop); + + char buffer[1024]; + dMemset( buffer, 0, 1024 ); + dSprintf( buffer, 1024, "ProfileNode = \"%.6f %.6f %d %d\";", nodePos.x, nodePos.y, smooth, mtrl); + stream.writeLine( (const U8*)buffer ); + } } bool MeshRoad::writeField( StringTableEntry fieldname, const char *value ) @@ -800,6 +1162,9 @@ bool MeshRoad::writeField( StringTableEntry fieldname, const char *value ) if ( fieldname == StringTable->insert("Node") ) return false; + if ( fieldname == StringTable->insert("ProfileNode") ) + return false; + return Parent::writeField( fieldname, value ); } @@ -817,7 +1182,7 @@ SimSet* MeshRoad::getServerSet() { smServerMeshRoadSet = new SimSet(); smServerMeshRoadSet->registerObject( "ServerMeshRoadSet" ); - Sim::getRootGroup()->addObject( smServerMeshRoadSet ); + Sim::getRootGroup()->addObject( smServerMeshRoadSet ); } return smServerMeshRoadSet; @@ -829,7 +1194,7 @@ void MeshRoad::prepRenderImage( SceneRenderState* state ) return; RenderPassManager *renderPass = state->getRenderPass(); - + // Normal Road RenderInstance // Always rendered when the editor is not open // otherwise obey the smShowRoad flag @@ -844,7 +1209,7 @@ void MeshRoad::prepRenderImage( SceneRenderState* state ) coreRI.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View); coreRI.projection = renderPass->allocSharedXform(RenderPassManager::Projection); coreRI.type = RenderPassManager::RIT_Mesh; - + BaseMatInstance *matInst; for ( U32 i = 0; i < SurfaceCount; i++ ) { @@ -857,7 +1222,7 @@ void MeshRoad::prepRenderImage( SceneRenderState* state ) { LightQuery query; query.init( getWorldSphere() ); - query.getLights( coreRI.lights, 8 ); + query.getLights( coreRI.lights, 8 ); } MeshRenderInst *ri = renderPass->allocInst(); @@ -893,7 +1258,7 @@ void MeshRoad::prepRenderImage( SceneRenderState* state ) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind( this, &MeshRoad::_debugRender ); - ri->type = RenderPassManager::RIT_Editor; + ri->type = RenderPassManager::RIT_Editor; state->getRenderPass()->addInst( ri ); } } @@ -923,7 +1288,7 @@ void MeshRoad::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, Base //GFX->setStateBlock( smStateBlock ); return; - /* + /* U32 convexCount = mDebugConvex.size(); PrimBuild::begin( GFXTriangleList, convexCount * 12 ); @@ -1010,6 +1375,22 @@ U32 MeshRoad::packUpdate(NetConnection * con, U32 mask, BitStream * stream) stream->write( mWidthSubdivisions ); } + if ( stream->writeFlag( mask & ProfileMask ) ) + { + stream->writeInt( mSideProfile.mNodes.size(), 16 ); + + for( U32 i = 0; i < mSideProfile.mNodes.size(); i++ ) + { + mathWrite( *stream, mSideProfile.mNodes[i].getPosition() ); + stream->writeFlag( mSideProfile.mNodes[i].isSmooth() ); + + if(i) + stream->writeInt(mSideProfile.mSegMtrls[i-1], 3); + else + stream->writeInt(0, 3); + } + } + if ( stream->writeFlag( mask & NodeMask ) ) { const U32 nodeByteSize = 32; // Based on sending all of a node's parameters @@ -1107,6 +1488,32 @@ void MeshRoad::unpackUpdate(NetConnection * con, BitStream * stream) stream->read( &mWidthSubdivisions ); } + // ProfileMask + if(stream->readFlag()) + { + Point3F pos; + + mSideProfile.mNodes.clear(); + mSideProfile.mSegMtrls.clear(); + + U32 count = stream->readInt( 16 ); + + for( U32 i = 0; i < count; i++) + { + mathRead( *stream, &pos ); + MeshRoadProfileNode node(pos); + node.setSmoothing( stream->readFlag() ); + mSideProfile.mNodes.push_back(node); + + if(i) + mSideProfile.mSegMtrls.push_back(stream->readInt(3)); + else + stream->readInt(3); + } + + mSideProfile.generateNormals(); + } + // NodeMask if ( stream->readFlag() ) { @@ -1202,6 +1609,12 @@ void MeshRoad::buildConvex(const Box3F& box, Convex* convex) U32 segmentCount = mSegments.size(); + U32 numConvexes ; + U32 halfConvexes; + U32 nextSegOffset = 2*mSideProfile.mNodes.size(); + U32 leftSideOffset = nextSegOffset/2; + U32 k2, capIdx1, capIdx2, capIdx3; + // Create convex(s) for each segment for ( U32 i = 0; i < segmentCount; i++ ) { @@ -1221,8 +1634,22 @@ void MeshRoad::buildConvex(const Box3F& box, Convex* convex) if ( j == 5 && i != segmentCount-1 ) continue; - // Each face has 2 convex(s) - for ( U32 k = 0; k < 2; k++ ) + // The top and bottom sides have 2 convex(s) + // The left, right, front, and back sides depend on the user-defined profile + switch(j) + { + case 0: numConvexes = 2; break; // Top + case 1: // Left + case 2: numConvexes = 2* (mSideProfile.mNodes.size()-1); break; // Right + case 3: numConvexes = 2; break; // Bottom + case 4: // Front + case 5: numConvexes = mSideProfile.mCap.getNumTris(); break; // Back + default: numConvexes = 0; + } + + halfConvexes = numConvexes/2; + + for ( U32 k = 0; k < numConvexes; k++ ) { // See if this convex exists in the working set already... Convex* cc = 0; @@ -1246,14 +1673,103 @@ void MeshRoad::buildConvex(const Box3F& box, Convex* convex) if (cc) continue; - // Get the triangle... - U32 idx0 = gIdxArray[j][k][0]; - U32 idx1 = gIdxArray[j][k][1]; - U32 idx2 = gIdxArray[j][k][2]; + Point3F a, b, c; - Point3F a = segment[idx0]; - Point3F b = segment[idx1]; - Point3F c = segment[idx2]; + // Top or Bottom + if(j == 0 || j == 3) + { + // Get the triangle... + U32 idx0 = gIdxArray[j][k][0]; + U32 idx1 = gIdxArray[j][k][1]; + U32 idx2 = gIdxArray[j][k][2]; + + a = segment[idx0]; + b = segment[idx1]; + c = segment[idx2]; + } + // Left Side + else if(j == 1) + { + if(k >= halfConvexes) + { + k2 = k + leftSideOffset - halfConvexes; + a = segment.slice1->verts[k2]; + b = segment.slice0->verts[k2]; + c = segment.slice1->verts[k2 + 1]; + } + else + { + k2 = k + leftSideOffset; + a = segment.slice0->verts[k2]; + b = segment.slice0->verts[k2 + 1]; + c = segment.slice1->verts[k2 + 1]; + } + } + // Right Side + else if(j == 2) + { +// a.set(2*k, 2*k, 0.0f); +// b.set(2*k, 2*k, 2.0f); +// c.set(2*(k+1), 2*(k+1), 0.0f); + + if(k >= halfConvexes) + { + k2 = k - halfConvexes; + a = segment.slice1->verts[k2]; + b = segment.slice1->verts[k2 + 1]; + c = segment.slice0->verts[k2]; + } + else + { + a = segment.slice0->verts[k]; + b = segment.slice1->verts[k + 1]; + c = segment.slice0->verts[k + 1]; + } + } + // Front + else if(j == 4) + { + k2 = nextSegOffset + leftSideOffset - 1; + + capIdx1 = mSideProfile.mCap.getTriIdx(k, 0); + capIdx2 = mSideProfile.mCap.getTriIdx(k, 1); + capIdx3 = mSideProfile.mCap.getTriIdx(k, 2); + + if(capIdx1 >= leftSideOffset) + capIdx1 = k2 - capIdx1; + + if(capIdx2 >= leftSideOffset) + capIdx2 = k2 - capIdx2; + + if(capIdx3 >= leftSideOffset) + capIdx3 = k2 - capIdx3; + + a = segment.slice0->verts[capIdx1]; + b = segment.slice0->verts[capIdx2]; + c = segment.slice0->verts[capIdx3]; + } + // Back + else + { + k2 = nextSegOffset + leftSideOffset - 1; + + capIdx1 = mSideProfile.mCap.getTriIdx(k, 0); + capIdx2 = mSideProfile.mCap.getTriIdx(k, 1); + capIdx3 = mSideProfile.mCap.getTriIdx(k, 2); + + if(capIdx1 >= leftSideOffset) + capIdx1 = k2 - capIdx1; + + if(capIdx2 >= leftSideOffset) + capIdx2 = k2 - capIdx2; + + if(capIdx3 >= leftSideOffset) + capIdx3 = k2 - capIdx3; + + a = segment.slice1->verts[capIdx3]; + b = segment.slice1->verts[capIdx2]; + c = segment.slice1->verts[capIdx1]; + } // Transform the result into object space! //mWorldToObj.mulP( a ); @@ -1327,22 +1843,20 @@ bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx if ( i == startSegIdx ) { - polyList->addPoint( seg.slice0->p0 ); - polyList->addPoint( seg.slice0->p2 ); - polyList->addPoint( seg.slice0->pb0 ); - polyList->addPoint( seg.slice0->pb2 ); + for(U32 j = 0; j < seg.slice0->verts.size(); j++) + polyList->addPoint( seg.slice0->verts[j] ); } - polyList->addPoint( seg.slice1->p0 ); - polyList->addPoint( seg.slice1->p2 ); - polyList->addPoint( seg.slice1->pb0 ); - polyList->addPoint( seg.slice1->pb2 ); + for(U32 j = 0; j < seg.slice1->verts.size(); j++) + polyList->addPoint( seg.slice1->verts[j] ); } // Temporaries to hold indices for the corner points of a quad. S32 p00, p01, p11, p10; S32 pb00, pb01, pb11, pb10; U32 offset = 0; + S32 a, b, c; + U32 mirror; DebugDrawer *ddraw = NULL;//DebugDrawer::get(); ClippedPolyList *cpolyList = dynamic_cast(polyList); @@ -1351,17 +1865,19 @@ bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx if ( cpolyList ) cpolyList->getTransform( &mat, &scale ); + U32 nextSegOffset = 2*mSideProfile.mNodes.size(); + U32 leftSideOffset = nextSegOffset/2; for ( U32 i = startSegIdx; i <= endSegIdx; i++ ) - { - p00 = offset; - p10 = offset + 1; - pb00 = offset + 2; - pb10 = offset + 3; - p01 = offset + 4; - p11 = offset + 5; - pb01 = offset + 6; - pb11 = offset + 7; + { + p00 = offset + leftSideOffset; + p10 = offset; + pb00 = offset + nextSegOffset - 1; + pb10 = offset + leftSideOffset - 1; + p01 = offset + nextSegOffset + leftSideOffset; + p11 = offset + nextSegOffset; + pb01 = offset + 2*nextSegOffset - 1; + pb11 = offset + nextSegOffset + leftSideOffset - 1; // Top Face @@ -1371,6 +1887,7 @@ bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx polyList->vertex( p11 ); polyList->plane( p00, p01, p11 ); polyList->end(); + if ( ddraw && cpolyList ) { Point3F v0 = cpolyList->mVertexList[p00].point; @@ -1402,36 +1919,56 @@ bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx continue; } // Left Face + for(U32 j = leftSideOffset; j < nextSegOffset-1; j++) + { + a = offset + j; + b = a + nextSegOffset + 1; + c = b - 1; - polyList->begin( 0,0 ); - polyList->vertex( pb00 ); - polyList->vertex( pb01 ); - polyList->vertex( p01 ); - polyList->plane( pb00, pb01, p01 ); - polyList->end(); + polyList->begin( 0,0 ); + polyList->vertex( a ); + polyList->vertex( b ); + polyList->vertex( c); + polyList->plane( a, b, c ); + polyList->end(); - polyList->begin( 0,0 ); - polyList->vertex( pb00 ); - polyList->vertex( p01 ); - polyList->vertex( p00 ); - polyList->plane( pb00, p01, p00 ); - polyList->end(); + a = offset + j; + b = a + 1; + c = a + nextSegOffset + 1; + + polyList->begin( 0,0 ); + polyList->vertex( a ); + polyList->vertex( b ); + polyList->vertex( c ); + polyList->plane( a, b, c ); + polyList->end(); + } // Right Face + for(U32 j = 0; j < leftSideOffset-1; j++) + { + a = offset + j; + b = a + nextSegOffset; + c = b + 1; - polyList->begin( 0,0 ); - polyList->vertex( p10 ); - polyList->vertex( p11 ); - polyList->vertex( pb11 ); - polyList->plane( p10, p11, pb11 ); - polyList->end(); + polyList->begin( 0,0 ); + polyList->vertex( a ); + polyList->vertex( b ); + polyList->vertex( c); + polyList->plane( a, b, c ); + polyList->end(); - polyList->begin( 0,0 ); - polyList->vertex( p10 ); - polyList->vertex( pb11 ); - polyList->vertex( pb10 ); - polyList->plane( p10, pb11, pb10 ); - polyList->end(); + a = offset + j; + b = a + nextSegOffset + 1; + c = a + 1; + + polyList->begin( 0,0 ); + polyList->vertex( a ); + polyList->vertex( b ); + polyList->vertex( c ); + polyList->plane( a, b, c ); + polyList->end(); + } // Bottom Face @@ -1453,40 +1990,62 @@ bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx if ( i == startSegIdx && capFront ) { - polyList->begin( 0,0 ); - polyList->vertex( p00 ); - polyList->vertex( p10 ); - polyList->vertex( pb10 ); - polyList->plane( p00, p10, pb10 ); - polyList->end(); + mirror = nextSegOffset + leftSideOffset - 1; - polyList->begin( 0,0 ); - polyList->vertex( p00 ); - polyList->vertex( pb10 ); - polyList->vertex( pb00 ); - polyList->plane( p00, pb10, pb00 ); - polyList->end(); + for(U32 j = 0; j < mSideProfile.mCap.getNumTris(); j++) + { + a = mSideProfile.mCap.getTriIdx(j, 0); + b = mSideProfile.mCap.getTriIdx(j, 1); + c = mSideProfile.mCap.getTriIdx(j, 2); + + if(a >= leftSideOffset) + a = mirror - a; + + if(b >= leftSideOffset) + b = mirror - b; + + if(c >= leftSideOffset) + c = mirror - c; + + polyList->begin( 0,0 ); + polyList->vertex( a ); + polyList->vertex( b ); + polyList->vertex( c ); + polyList->plane( a, b, c ); + polyList->end(); + } } // Back Face if ( i == endSegIdx && capEnd ) { - polyList->begin( 0,0 ); - polyList->vertex( p01 ); - polyList->vertex( pb01 ); - polyList->vertex( pb11 ); - polyList->plane( p01, pb01, pb11 ); - polyList->end(); + mirror = nextSegOffset + leftSideOffset - 1; - polyList->begin( 0,0 ); - polyList->vertex( p01 ); - polyList->vertex( pb11 ); - polyList->vertex( p11 ); - polyList->plane( p01, pb11, p11 ); - polyList->end(); + for(U32 j = 0; j < mSideProfile.mCap.getNumTris(); j++) + { + a = mSideProfile.mCap.getTriIdx(j, 0); + b = mSideProfile.mCap.getTriIdx(j, 1); + c = mSideProfile.mCap.getTriIdx(j, 2); + + if(a >= leftSideOffset) + a = offset + nextSegOffset + mirror - a; + + if(b >= leftSideOffset) + b = offset + nextSegOffset + mirror - b; + + if(c >= leftSideOffset) + c = offset + nextSegOffset + mirror - c; + + polyList->begin( 0,0 ); + polyList->vertex( c ); + polyList->vertex( b ); + polyList->vertex( a ); + polyList->plane( c, b, a ); + polyList->end(); + } } - offset += 4; + offset += nextSegOffset; } return true; @@ -1529,6 +2088,12 @@ bool MeshRoad::castRay( const Point3F &s, const Point3F &e, RayInfo *info ) U32 segIdx = hitSegments[i].idx; const MeshRoadSegment &segment = mSegments[segIdx]; + U32 numConvexes ; + U32 halfConvexes; + U32 nextSegOffset = 2*mSideProfile.mNodes.size(); + U32 leftSideOffset = nextSegOffset/2; + U32 k2, capIdx1, capIdx2, capIdx3; + // Each segment has 6 faces for ( U32 j = 0; j < 6; j++ ) { @@ -1538,19 +2103,121 @@ bool MeshRoad::castRay( const Point3F &s, const Point3F &e, RayInfo *info ) if ( j == 5 && segIdx != mSegments.size() - 1 ) continue; - // Each face has 2 triangles - for ( U32 k = 0; k < 2; k++ ) + // The top and bottom sides have 2 convex(s) + // The left, right, front, and back sides depend on the user-defined profile + switch(j) { - idx0 = gIdxArray[j][k][0]; - idx1 = gIdxArray[j][k][1]; - idx2 = gIdxArray[j][k][2]; + case 0: numConvexes = 2; break; // Top + case 1: // Left + case 2: numConvexes = 2* (mSideProfile.mNodes.size()-1); break; // Right + case 3: numConvexes = 2; break; // Bottom + case 4: // Front + case 5: numConvexes = mSideProfile.mCap.getNumTris(); break; // Back + default: numConvexes = 0; + } - const Point3F &v0 = segment[idx0]; - const Point3F &v1 = segment[idx1]; - const Point3F &v2 = segment[idx2]; + halfConvexes = numConvexes/2; + + // Each face has 2 triangles + for ( U32 k = 0; k < numConvexes; k++ ) + { + const Point3F *a = NULL; + const Point3F *b = NULL; + const Point3F *c = NULL; + + // Top or Bottom + if(j == 0 || j == 3) + { + idx0 = gIdxArray[j][k][0]; + idx1 = gIdxArray[j][k][1]; + idx2 = gIdxArray[j][k][2]; + + a = &segment[idx0]; + b = &segment[idx1]; + c = &segment[idx2]; + } + // Left Side + else if(j == 1) + { + if(k >= halfConvexes) + { + k2 = k + leftSideOffset - halfConvexes; + a = &segment.slice1->verts[k2]; + b = &segment.slice0->verts[k2]; + c = &segment.slice1->verts[k2 + 1]; + } + else + { + k2 = k + leftSideOffset; + a = &segment.slice0->verts[k2]; + b = &segment.slice0->verts[k2 + 1]; + c = &segment.slice1->verts[k2 + 1]; + } + } + // Right Side + else if(j == 2) + { + if(k >= halfConvexes) + { + k2 = k - halfConvexes; + a = &segment.slice1->verts[k2]; + b = &segment.slice1->verts[k2 + 1]; + c = &segment.slice0->verts[k2]; + } + else + { + a = &segment.slice0->verts[k]; + b = &segment.slice1->verts[k + 1]; + c = &segment.slice0->verts[k + 1]; + } + } + // Front + else if(j == 4) + { + k2 = nextSegOffset + leftSideOffset - 1; + + capIdx1 = mSideProfile.mCap.getTriIdx(k, 0); + capIdx2 = mSideProfile.mCap.getTriIdx(k, 1); + capIdx3 = mSideProfile.mCap.getTriIdx(k, 2); + + if(capIdx1 >= leftSideOffset) + capIdx1 = k2 - capIdx1; + + if(capIdx2 >= leftSideOffset) + capIdx2 = k2 - capIdx2; + + if(capIdx3 >= leftSideOffset) + capIdx3 = k2 - capIdx3; + + a = &segment.slice0->verts[capIdx1]; + b = &segment.slice0->verts[capIdx2]; + c = &segment.slice0->verts[capIdx3]; + } + // Back + else + { + k2 = nextSegOffset + leftSideOffset - 1; + + capIdx1 = mSideProfile.mCap.getTriIdx(k, 0); + capIdx2 = mSideProfile.mCap.getTriIdx(k, 1); + capIdx3 = mSideProfile.mCap.getTriIdx(k, 2); + + if(capIdx1 >= leftSideOffset) + capIdx1 = k2 - capIdx1; + + if(capIdx2 >= leftSideOffset) + capIdx2 = k2 - capIdx2; + + if(capIdx3 >= leftSideOffset) + capIdx3 = k2 - capIdx3; + + a = &segment.slice1->verts[capIdx3]; + b = &segment.slice1->verts[capIdx2]; + c = &segment.slice1->verts[capIdx1]; + } if ( !MathUtils::mLineTriangleCollide( start, end, - v2, v1, v0, + *c, *b, *a, NULL, &t ) ) continue; @@ -1558,7 +2225,7 @@ bool MeshRoad::castRay( const Point3F &s, const Point3F &e, RayInfo *info ) if ( t >= 0.0f && t < 1.0f && t < out ) { out = t; - norm = PlaneF( v0, v1, v2 ); + norm = PlaneF( *a, *b, *c ); } } } @@ -1592,6 +2259,9 @@ void MeshRoad::_regenerate() if ( mNodes.size() == 0 ) return; + if ( mSideProfile.mNodes.size() == 2 && mSideProfile.mNodes[1].getPosition().x == 0.0f) + mSideProfile.setProfileDepth(mNodes[0].depth); + const Point3F &nodePt = mNodes.first().point; MatrixF mat( true ); @@ -1689,22 +2359,27 @@ void MeshRoad::_generateSlices() MatrixF mat(true); Box3F box; + + U32 lastProfileNode = mSideProfile.mNodes.size() - 1; + F32 depth = mSideProfile.mNodes[lastProfileNode].getPosition().y; + F32 bttmOffset = mSideProfile.mNodes[lastProfileNode].getPosition().x; + for ( U32 i = 0; i < mSlices.size(); i++ ) { // Calculate uvec, fvec, and rvec for all slices calcSliceTransform( i, mat ); - MeshRoadSlice *slicePtr = &mSlices[i]; + MeshRoadSlice *slicePtr = &mSlices[i]; mat.getColumn( 0, &slicePtr->rvec ); mat.getColumn( 1, &slicePtr->fvec ); mat.getColumn( 2, &slicePtr->uvec ); // Calculate p0/p2/pb0/pb2 for all slices - slicePtr->p0 = slicePtr->p1 - slicePtr->rvec * slicePtr->width * 0.5f; - slicePtr->p2 = slicePtr->p1 + slicePtr->rvec * slicePtr->width * 0.5f; - slicePtr->pb0 = slicePtr->p0 - slicePtr->uvec * slicePtr->depth; - slicePtr->pb2 = slicePtr->p2 - slicePtr->uvec * slicePtr->depth; + slicePtr->p0 = slicePtr->p1 - slicePtr->rvec * slicePtr->width * 0.5f; + slicePtr->p2 = slicePtr->p1 + slicePtr->rvec * slicePtr->width * 0.5f; + slicePtr->pb0 = slicePtr->p0 + slicePtr->uvec * depth - slicePtr->rvec * bttmOffset; + slicePtr->pb2 = slicePtr->p2 + slicePtr->uvec * depth + slicePtr->rvec * bttmOffset; - // Generate or extend the object/world bounds + // Generate or extend the object/world bounds if ( i == 0 ) { box.minExtents = slicePtr->p0; @@ -1719,6 +2394,68 @@ void MeshRoad::_generateSlices() box.extend(slicePtr->pb0 ); box.extend(slicePtr->pb2 ); } + + // Right side + Point3F pos; + VectorF norm; + + MatrixF profileMat1(true); + profileMat1.setRow(0, slicePtr->rvec); + profileMat1.setRow(1, slicePtr->uvec); + profileMat1.setRow(2, -slicePtr->fvec); + + // Left side + MatrixF profileMat2(true); + profileMat2.setRow(0, -slicePtr->rvec); + profileMat2.setRow(1, slicePtr->uvec); + profileMat2.setRow(2, slicePtr->fvec); + + for(U32 i = 0; i < 2; i++) + { + if(i) + mSideProfile.setTransform(profileMat2, slicePtr->p0); + else + mSideProfile.setTransform(profileMat1, slicePtr->p2); + + // Retain original per-node depth functionality + if(mSideProfile.mNodes.size() == 2 && mSideProfile.mNodes[1].getPosition().y == -mSlices[0].depth) + { + mSideProfile.getNodeWorldPos(0, pos); + slicePtr->verts.push_back(pos); + box.extend( pos ); + + pos.z -= slicePtr->depth; + slicePtr->verts.push_back(pos); + box.extend( pos ); + + if(i) + slicePtr->pb0 = pos; + else + slicePtr->pb2 = pos; + + mSideProfile.getNormToSlice(0, norm); + slicePtr->norms.push_back(norm); + + mSideProfile.getNormToSlice(1, norm); + slicePtr->norms.push_back(norm); + } + // New profile functionality + else + { + for(U32 j = 0; j < mSideProfile.mNodes.size(); j++) + { + mSideProfile.getNodeWorldPos(j, pos); + slicePtr->verts.push_back(pos); + box.extend( pos ); + } + + for(U32 j = 0; j < mSideProfile.mNodeNormals.size(); j++) + { + mSideProfile.getNormToSlice(j, norm); + slicePtr->norms.push_back(norm); + } + } + } } mWorldBox = box; @@ -1740,6 +2477,8 @@ void MeshRoad::_generateSegments() mSegments.push_back( seg ); } + //mSideProfile.generateEndCap(mSlices[0].width); + if ( isClientObject() ) _generateVerts(); @@ -1771,14 +2510,37 @@ void MeshRoad::_generateVerts() const U32 sliceCount = mSlices.size(); const U32 segmentCount = mSegments.size(); + U32 numProfSide, numProfTop, numProfBottom; + + numProfSide = numProfTop = numProfBottom = 0; + + // Find how many profile segments are set to side, top, and bottom materials + for ( U32 i = 0; i < mSideProfile.mSegMtrls.size(); i++) + { + switch(mSideProfile.mSegMtrls[i]) + { + case Side: numProfSide++; break; + case Top: numProfTop++; break; + case Bottom: numProfBottom++; break; + } + } + + F32 profLen = mSideProfile.getProfileLen(); + mVertCount[Top] = ( 2 + widthDivisions ) * sliceCount; + mVertCount[Top] += sliceCount * numProfTop * 4; mTriangleCount[Top] = segmentCount * 2 * ( widthDivisions + 1 ); + mTriangleCount[Top] += segmentCount * numProfTop * 4; mVertCount[Bottom] = sliceCount * 2; + mVertCount[Bottom] += sliceCount * numProfBottom * 4; mTriangleCount[Bottom] = segmentCount * 2; + mTriangleCount[Bottom] += segmentCount * numProfBottom * 4; - mVertCount[Side] = sliceCount * 4; - mTriangleCount[Side] = segmentCount * 4 + 4; + mVertCount[Side] = sliceCount * numProfSide * 4; // side verts + mVertCount[Side] += mSideProfile.mNodes.size() * 4; // end cap verts + mTriangleCount[Side] = segmentCount * numProfSide * 4; // side tris + mTriangleCount[Side] += mSideProfile.mCap.getNumTris() * 2; // end cap tris // Calculate TexCoords for Slices @@ -1838,6 +2600,60 @@ void MeshRoad::_generateVerts() vertCounter++; } + if(numProfTop) + { + for ( U32 i = 0; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; + + // Right Side + for ( U32 j = 0; j < mSideProfile.mNodes.size()-1; j++) + { + if(mSideProfile.mSegMtrls[j] == Top) + { + // Vertex 1 + pVert->point = slice.verts[j]; + pVert->normal = slice.norms[2*j]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + + // Vertex 2 + pVert->point = slice.verts[j+1]; + pVert->normal = slice.norms[2*j+1]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + } + } + + // Left Side + for( U32 j = mSideProfile.mNodes.size(); j < 2*mSideProfile.mNodes.size()-1; j++) + { + if(mSideProfile.mSegMtrls[j-mSideProfile.mNodes.size()] == Top) + { + // Vertex 1 + pVert->point = slice.verts[j]; + pVert->normal = slice.norms[2*j-2]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + + // Vertex 2 + pVert->point = slice.verts[j+1]; + pVert->normal = slice.norms[2*j-1]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + } + } + } + } + AssertFatal( vertCounter == mVertCount[Top], "MeshRoad, wrote incorrect number of verts in mVB[Top]!" ); mVB[Top].unlock(); @@ -1867,6 +2683,60 @@ void MeshRoad::_generateVerts() vertCounter++; } + if(numProfBottom) + { + for ( U32 i = 0; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; + + // Right Side + for ( U32 j = 0; j < mSideProfile.mNodes.size()-1; j++) + { + if(mSideProfile.mSegMtrls[j] == Bottom) + { + // Vertex 1 + pVert->point = slice.verts[j]; + pVert->normal = slice.norms[2*j]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + + // Vertex 2 + pVert->point = slice.verts[j+1]; + pVert->normal = slice.norms[2*j+1]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + } + } + + // Left Side + for( U32 j = mSideProfile.mNodes.size(); j < 2*mSideProfile.mNodes.size()-1; j++) + { + if(mSideProfile.mSegMtrls[j-mSideProfile.mNodes.size()] == Bottom) + { + // Vertex 1 + pVert->point = slice.verts[j]; + pVert->normal = slice.norms[2*j-2]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + + // Vertex 2 + pVert->point = slice.verts[j+1]; + pVert->normal = slice.norms[2*j-1]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + } + } + } + } + AssertFatal( vertCounter == mVertCount[Bottom], "MeshRoad, wrote incorrect number of verts in mVB[Bottom]!" ); mVB[Bottom].unlock(); @@ -1877,37 +2747,106 @@ void MeshRoad::_generateVerts() pVert = mVB[Side].lock(); vertCounter = 0; - for ( U32 i = 0; i < sliceCount; i++ ) + if(numProfSide) { - MeshRoadSlice &slice = mSlices[i]; + for ( U32 i = 0; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; - pVert->point = slice.p0; - pVert->normal = -slice.rvec; - pVert->tangent = slice.fvec; - pVert->texCoord.set(1,slice.texCoordV); - pVert++; - vertCounter++; + // Right Side + for( U32 j = 0; j < mSideProfile.mNodes.size()-1; j++) + { + if(mSideProfile.mSegMtrls[j] == Side) + { + // Segment Vertex 1 + pVert->point = slice.verts[j]; + pVert->normal = slice.norms[2*j]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; - pVert->point = slice.p2; - pVert->normal = slice.rvec; - pVert->tangent = slice.fvec; - pVert->texCoord.set(1,slice.texCoordV); - pVert++; - vertCounter++; + // Segment Vertex 2 + pVert->point = slice.verts[j+1]; + pVert->normal = slice.norms[2*j+1]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + } + } - pVert->point = slice.pb0; - pVert->normal = -slice.rvec; - pVert->tangent = slice.fvec; - pVert->texCoord.set(0,slice.texCoordV); - pVert++; - vertCounter++; + // Left Side + for( U32 j = mSideProfile.mNodes.size(); j < 2*mSideProfile.mNodes.size()-1; j++) + { + if(mSideProfile.mSegMtrls[j-mSideProfile.mNodes.size()] == Side) + { + // Segment Vertex 1 + pVert->point = slice.verts[j]; + pVert->normal = slice.norms[2*j-2]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; - pVert->point = slice.pb2; - pVert->normal = slice.rvec; - pVert->tangent = slice.fvec; - pVert->texCoord.set(0,slice.texCoordV); - pVert++; - vertCounter++; + // Segment Vertex 2 + pVert->point = slice.verts[j+1]; + pVert->normal = slice.norms[2*j-1]; + pVert->tangent = slice.fvec; + pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV); + pVert++; + vertCounter++; + } + } + } + } + + // Cap verts + Point3F pos; + VectorF norm; + VectorF tang; + + for( U32 i = 0; i < mSlices.size(); i += mSlices.size()-1) + { + MeshRoadSlice &slice = mSlices[i]; + + // Back cap + if(i) + { + norm = slice.fvec; + tang = -slice.rvec; + } + // Front cap + else + { + norm = -slice.fvec; + tang = slice.rvec; + } + + // Right side + for( U32 j = 0; j < mSideProfile.mNodes.size(); j++) + { + pVert->point = slice.verts[j]; + pVert->normal = norm; + pVert->tangent = tang; + pos = mSideProfile.mNodes[j].getPosition(); + pVert->texCoord.set(pos.x/mTextureLength, pos.y/mTextureLength); + pVert++; + vertCounter++; + } + + // Left side + for( U32 j = 2*mSideProfile.mNodes.size()-1; j >= mSideProfile.mNodes.size(); j--) + { + pVert->point = slice.verts[j]; + pVert->normal = norm; + pVert->tangent = tang; + pos = mSideProfile.mNodes[j-mSideProfile.mNodes.size()].getPosition(); + pos.x = -pos.x - slice.width; + pVert->texCoord.set(pos.x/mTextureLength, pos.y/mTextureLength); + pVert++; + vertCounter++; + } } AssertFatal( vertCounter == mVertCount[Side], "MeshRoad, wrote incorrect number of verts in mVB[Side]!" ); @@ -1960,6 +2899,55 @@ void MeshRoad::_generateVerts() offset += 1; } + offset += 2; + + if(numProfTop) + { + U32 nextSegOffset = 4 * numProfTop; + + for ( U32 i = 0; i < segmentCount; i++ ) + { + // Loop through profile segments on right side + for( U32 j = 0; j < numProfTop; j++) + { + // Profile Segment Face 1 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset; + curIdx++; + + // Profile Segment Face 2 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + } + + // Loop through profile segments on left side + for( U32 j = numProfTop; j < 2*numProfTop; j++) + { + // Profile Segment Face 1 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + + // Profile Segment Face 2 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset; + curIdx++; + } + } + } AssertFatal( curIdx == mTriangleCount[Top] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Top]!" ); @@ -1997,6 +2985,56 @@ void MeshRoad::_generateVerts() offset += 2; } + offset += 2; + + if(numProfBottom) + { + U32 nextSegOffset = 4 * numProfBottom; + + for ( U32 i = 0; i < segmentCount; i++ ) + { + // Loop through profile segments on right side + for( U32 j = 0; j < numProfBottom; j++) + { + // Profile Segment Face 1 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset; + curIdx++; + + // Profile Segment Face 2 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + } + + // Loop through profile segments on left side + for( U32 j = numProfBottom; j < 2*numProfBottom; j++) + { + // Profile Segment Face 1 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + + // Profile Segment Face 2 + pIdx[curIdx] = nextSegOffset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset; + curIdx++; + pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset; + curIdx++; + } + } + } + AssertFatal( curIdx == mTriangleCount[Bottom] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Bottom]!" ); mPB[Bottom].unlock(); @@ -2007,69 +3045,79 @@ void MeshRoad::_generateVerts() mPB[Side].lock(&pIdx); curIdx = 0; - offset = 0; + offset = 4 * numProfSide; - for ( U32 i = 0; i < mSegments.size(); i++ ) - { - p00 = offset; - p10 = offset + 1; - pb00 = offset + 2; - pb10 = offset + 3; - p01 = offset + 4; - p11 = offset + 5; - pb01 = offset + 6; - pb11 = offset + 7; + if(numProfSide) + { + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + // Loop through profile segments on right side + for( U32 j = 0; j < numProfSide; j++) + { + // Profile Segment Face 1 + pIdx[curIdx] = offset*i + 2*j; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + offset + 1; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + 1; + curIdx++; - // Left Side + // Profile Segment Face 2 + pIdx[curIdx] = offset*i + 2*j; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + offset; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + offset + 1; + curIdx++; + } - pIdx[curIdx] = pb00; - curIdx++; - pIdx[curIdx] = pb01; - curIdx++; - pIdx[curIdx] = p01; - curIdx++; + // Loop through profile segments on left side + for( U32 j = numProfSide; j < 2*numProfSide; j++) + { + // Profile Segment Face 1 + pIdx[curIdx] = offset*i + 2*j; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + 1; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + offset + 1; + curIdx++; - pIdx[curIdx] = pb00; - curIdx++; - pIdx[curIdx] = p01; - curIdx++; - pIdx[curIdx] = p00; - curIdx++; - - // Right Side - - pIdx[curIdx] = p10; - curIdx++; - pIdx[curIdx] = p11; - curIdx++; - pIdx[curIdx] = pb11; - curIdx++; - - pIdx[curIdx] = p10; - curIdx++; - pIdx[curIdx] = pb11; - curIdx++; - pIdx[curIdx] = pb10; - curIdx++; - - offset += 4; + // Profile Segment Face 2 + pIdx[curIdx] = offset*i + 2*j; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + offset + 1; + curIdx++; + pIdx[curIdx] = offset*i + 2*j + offset; + curIdx++; + } + } } - // Cap the front and back ends - pIdx[curIdx++] = 0; - pIdx[curIdx++] = 1; - pIdx[curIdx++] = 3; - pIdx[curIdx++] = 0; - pIdx[curIdx++] = 3; - pIdx[curIdx++] = 2; - - pIdx[curIdx++] = offset + 0; - pIdx[curIdx++] = offset + 3; - pIdx[curIdx++] = offset + 1; - pIdx[curIdx++] = offset + 0; - pIdx[curIdx++] = offset + 2; - pIdx[curIdx++] = offset + 3; + // Cap the front + offset = sliceCount * numProfSide * 4; + for ( U32 i = 0; i < mSideProfile.mCap.getNumTris(); i++ ) + { + pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 0) + offset; + curIdx++; + pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 1) + offset; + curIdx++; + pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 2) + offset; + curIdx++; + } + + // Cap the back + offset += mSideProfile.mNodes.size() * 2; + + for ( U32 i = 0; i < mSideProfile.mCap.getNumTris(); i++ ) + { + pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 2) + offset; + curIdx++; + pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 1) + offset; + curIdx++; + pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 0) + offset; + curIdx++; + } AssertFatal( curIdx == mTriangleCount[Side] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Side]!" ); diff --git a/Engine/source/environment/meshRoad.h b/Engine/source/environment/meshRoad.h index 6279e20fa..b817d6b8d 100644 --- a/Engine/source/environment/meshRoad.h +++ b/Engine/source/environment/meshRoad.h @@ -50,6 +50,7 @@ #include "collision/convex.h" #endif +#include "math/util/decomposePoly.h" //extern U32 gIdxArray[6][2][3]; @@ -61,6 +62,67 @@ struct MeshRoadHitSegment 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 mNodes; // The list of nodes in the profile + Vector mNodeNormals; // The list of normals for each node + Vector 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 getNodes() { return mNodes; } + void resetProfile(F32 defaultDepth); // Reset profile to 2 default nodes +}; + //------------------------------------------------------------------------- // MeshRoadConvex Class //------------------------------------------------------------------------- @@ -282,6 +344,9 @@ struct MeshRoadSlice F32 texCoordV; U32 parentNodeIdx; + + Vector verts; + Vector norms; }; typedef Vector MeshRoadSliceVector; @@ -420,6 +485,7 @@ private: friend class GuiMeshRoadEditorCtrl; friend class GuiMeshRoadEditorUndoAction; friend class MeshRoadConvex; + friend class MeshRoadProfile; typedef SceneObject Parent; @@ -431,7 +497,8 @@ private: InitialUpdateMask = Parent::NextFreeMask << 3, SelectedMask = Parent::NextFreeMask << 4, MaterialMask = Parent::NextFreeMask << 5, - NextFreeMask = Parent::NextFreeMask << 6, + ProfileMask = Parent::NextFreeMask << 6, + NextFreeMask = Parent::NextFreeMask << 7, }; public: @@ -509,12 +576,14 @@ public: /// 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 addProfileNodeFromField(void *obj, const char *index, const char* data); static bool smEditorOpen; static bool smWireframe; static bool smShowBatches; static bool smShowSpline; static bool smShowRoad; + static bool smShowRoadProfile; static SimObjectPtr smServerMeshRoadSet; protected: @@ -556,6 +625,8 @@ protected: U32 mVertCount[SurfaceCount]; U32 mTriangleCount[SurfaceCount]; + + MeshRoadProfile mSideProfile; // Fields. F32 mTextureLength; diff --git a/Engine/source/math/util/decomposePoly.cpp b/Engine/source/math/util/decomposePoly.cpp new file mode 100644 index 000000000..f17b2eed4 --- /dev/null +++ b/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 ¬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; +} diff --git a/Engine/source/math/util/decomposePoly.h b/Engine/source/math/util/decomposePoly.h new file mode 100644 index 000000000..884ac6123 --- /dev/null +++ b/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 mVertList; + Vector mEdgeList; + Vector mInsideVerts; + Vector 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 diff --git a/Templates/BaseGame/game/tools/meshRoadEditor/main.cs b/Templates/BaseGame/game/tools/meshRoadEditor/main.cs index 74f0cd349..d1dc613f7 100644 --- a/Templates/BaseGame/game/tools/meshRoadEditor/main.cs +++ b/Templates/BaseGame/game/tools/meshRoadEditor/main.cs @@ -59,6 +59,7 @@ function initializeMeshRoadEditor() %map.bindCmd( keyboard, "z", "MeshRoadEditorShowSplineBtn.performClick();", "" ); %map.bindCmd( keyboard, "x", "MeshRoadEditorWireframeBtn.performClick();", "" ); %map.bindCmd( keyboard, "v", "MeshRoadEditorShowRoadBtn.performClick();", "" ); + %map.bindCmd( keyboard, "p", "MeshRoadEditorShowProfileBtn.performClick();", ""); MeshRoadEditorPlugin.map = %map; MeshRoadEditorPlugin.initSettings(); diff --git a/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorGui.cs b/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorGui.cs index 9d2985e25..3fcfd528f 100644 --- a/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorGui.cs +++ b/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorGui.cs @@ -24,6 +24,8 @@ $MeshRoad::wireframe = true; $MeshRoad::showSpline = true; $MeshRoad::showReflectPlane = false; $MeshRoad::showRoad = true; +$MeshRoad::showRoadProfile = false; + $MeshRoad::breakAngle = 3.0; function MeshRoadEditorGui::onWake( %this ) diff --git a/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorToolbar.gui b/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorToolbar.gui index be5877706..14444b62d 100644 --- a/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorToolbar.gui +++ b/Templates/BaseGame/game/tools/meshRoadEditor/meshRoadEditorToolbar.gui @@ -27,7 +27,7 @@ }; new GuiDynamicCtrlArrayControl(){ Position = "116 3"; - extent = "111 32"; + extent = "146 32"; colCount = "31"; colSize = "29"; rowCount = "1"; @@ -103,6 +103,29 @@ buttonType = "ToggleButton"; useMouseEvents = "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) { @@ -111,7 +134,7 @@ Profile = "ToolsGuiTransparentProfile"; HorizSizing = "right"; VertSizing = "bottom"; - position = "230 5"; + position = "265 5"; Extent = "120 50"; MinExtent = "8 2"; canSave = "1"; @@ -189,7 +212,7 @@ Profile = "ToolsGuiTransparentProfile"; HorizSizing = "right"; VertSizing = "bottom"; - position = "360 5"; + position = "395 5"; Extent = "120 50"; MinExtent = "8 2"; canSave = "1"; diff --git a/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_d.png b/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_d.png new file mode 100644 index 000000000..81609c3c1 Binary files /dev/null and b/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_d.png differ diff --git a/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_h.png b/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_h.png new file mode 100644 index 000000000..23585ab3f Binary files /dev/null and b/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_h.png differ diff --git a/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_n.png b/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_n.png new file mode 100644 index 000000000..5415b5809 Binary files /dev/null and b/Templates/BaseGame/game/tools/worldEditor/images/road-river/menubar/show-profile_n.png differ