-- TerrainSeamTool.ms -- 2014 Andy Davis -- Rockstar London -- Description: tool snaps terrain seam vertices to adjacent terrain sections filein (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms") --wildwest header filein (RsConfigGetWildWestDir() + "script/3dsMax/_common_functions/RSL_dotNetUIOps.ms") --RS_dotNetPreset structure RsCollectToolUsageData (getThisScriptFilename()) global EdgeMatch struct TerrainSeamTool_SeamVert ( obj, vertID, position ) struct TerrainSeamTool_SeamEdge ( obj1, vertids1, obj2, vertids2, axis ) struct TerrainSeamTool_ObjectSeamData ( obj, handle, minXY, maxXY, edgeVertIDs = #{}, northVertIDs = #{}, southVertIDs = #{}, westVertIDs = #{}, eastVertIDs = #{}, nwVert, neVert, swVert, seVert, northEdgeIDs = #{}, southEdgeIDs = #{}, eastEdgeIDs = #{}, westEdgeIDs = #{}, --finds the end vertex on a row of vertices and removes the vertexID from the incoming array --used to find corner verts on meshes with non-square edges fn GetEndVert obj &vertIDs side = ( vertIDs = vertIDs as array local endVert local axis local numVerts = vertIDs.count --east and west means looking on the x-axis if ((side == #west) or (side == #east)) then axis = 1 else --north and south are y-axis axis = 2 --south and west means looking for the minimum values if ((side == #west) or (side == #south)) then ( local minValue = obj.verts[vertIDs[1]].position[axis] local minID = 1 for i = 2 to numVerts do ( local newValue = obj.verts[vertIDs[i]].position[axis] if (newValue < minValue) then ( minValue = newValue minID = i ) ) --transfer the found corner vert to the endVert variable, removing it from the vertIDs array endVert = vertIDs[minID] deleteItem vertIDs minID ) else --values must be #north or #east ( local maxValue = obj.verts[vertIDs[1]].position[axis] local maxID = 1 for i = 2 to numVerts do ( local newValue = obj.verts[vertIDs[i]].position[axis] if (newValue > maxValue) then ( maxValue = newValue maxID = i ) ) --transfer the found corner vert to the endVert variable, removing it from the vertIDs array endVert = vertIDs[maxID] deleteItem vertIDs maxID ) return endVert ), --run InitData when the obj is set up with a valid geometry object to the obj variable fn InitData = ( if obj != undefined then ( local edgeIDs = polyop.getOpenEdges this.obj this.edgeVertIDs = polyop.getVertsUsingEdge this.obj edgeIDs local tolerance = 0.001 -- find the corner and edge verts based on proximity to bounds for id in edgeVertIDs do ( if (abs (obj.verts[id].position.x - obj.min.x) < tolerance) then ( if (abs (obj.verts[id].position.y - obj.min.y) < tolerance) then swVert = id else if (abs (obj.verts[id].position.y - obj.max.y) < tolerance) then nwVert = id else append westVertIDs id ) else if (abs (obj.verts[id].position.x - obj.max.x) < tolerance) then ( if (abs (obj.verts[id].position.y - obj.min.y) < tolerance) then seVert = id else if (abs (obj.verts[id].position.y - obj.max.y) < tolerance) then neVert = id else append eastVertIDs id ) else if (abs (obj.verts[id].position.y - obj.min.y) < tolerance) then append southVertIDs id else if (abs (obj.verts[id].position.y - obj.max.y) < tolerance) then append northVertIDs id ) --look for corner verts on meshes with non-square edges --if found remove them from the vertID arrays: this is done by GetEndVert() --even after these checks, the corner verts may not be found: two adjacent non-square edges will not yield a corner vert if (nwVert == undefined) then ( --check north and west vertices for the corner vert if ((northVertIDs as array).count > 2) then nwVert = GetEndVert obj &northVertIDs #west --looking for the most west vertex in the north vertices else if ((westVertIDs as array).count > 2) then nwVert = GetEndVert obj &westVertIDs #north ) if (neVert == undefined) then ( --check north and east vertices for the corner vert if ((northVertIDs as array).count > 2) then neVert = GetEndVert obj &northVertIDs #east else if ((eastVertIDs as array).count > 2) then neVert = GetEndVert obj &eastVertIDs #north ) if (swVert == undefined) then ( --check south and west vertices for the corner vert if ((southVertIDs as array).count > 2) then swVert = GetEndVert obj &southVertIDs #west else if ((westVertIDs as array).count > 2) then swVert = GetEndVert obj &westVertIDs #south ) if (seVert == undefined) then ( --check south and east vertices for the corner vert if ((southVertIDs as array).count > 2) then seVert = GetEndVert obj &southVertIDs #east else if ((eastVertIDs as array).count > 2) then seVert = GetEndVert obj &eastVertIDs #south ) --sort the edges for id in edgeIDs do ( local vertIDs = (polyop.GetVertsUsingEdge this.obj id) as array local edgePosition = (this.obj.verts[vertIDs[1]].position + this.obj.verts[vertIDs[2]].position) / 2 if (abs (edgePosition.x - obj.min.x) < tolerance) then append westEdgeIDs id else if (abs (edgePosition.x - obj.max.x) < tolerance) then append eastEdgeIDs id else if (abs (edgePosition.y - obj.min.y) < tolerance) then append southEdgeIDs id else if (abs (edgePosition.y - obj.max.y) < tolerance) then append northEdgeIDs id -- else -- format "Object % Edge % is not on the periphery\n" this.obj.name id ) handle = obj.inode.handle minXY = [obj.min.x, obj.min.y] maxXY = [obj.max.x, obj.max.y] -- this.FormatData() --dev ) else format "warning: obj is undefined\n" ), fn FormatData = ( format "Name: %\t" (MaxOps.GetNodeByHandle this.handle).name format "Boundaries: [%,%] [%,%]\n" minXY.x minXY.y maxXY.x maxXY.y format "NE vert: %\nNW vert: %\nSE vert: %\nSW vert: %\n" neVert nwVert seVert swVert format "--------VERTS----------\n" format "North Verts: %\n" northVertIDs format "South Verts: %\n" southVertIDs format "East Verts: %\n" eastVertIDs format "West Verts: %\n" westVertIDs format "--------EDGES----------\n" format "North Edges: %\n" northEdgeIDs format "South Edges: %\n" southEdgeIDs format "East Edges: %\n" eastEdgeIDs format "West Edges: %\n" westEdgeIDs format "\n" ) ) struct EdgeMatchStruct ( Form, ToolTip, InfoPanel, ApplyButton, ProgBar, IniFilePath = (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/AD_Tools.ini"), ObjectList, --master array containing all data about object edges searchTolerance = 0.5, --maximum gaps between edge vertices edgeTolerance = 0.01, --maximum gap between edges SeamCheckList = #(), --list of all the object seams to check against each other CornerCheckList = #(), --list of all corners to check against each other Progress = 0, ------------------------------------------------------------------------------------------------------------------------------------------ --GENERAL FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------ fn Apply s e = ( EdgeMatch.Process() ), fn UndoClicked s e = ( print "max undo" max undo ), ------------------------------------------------------------------------------------------------------------------------------------------ --LOAD/SAVE FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------ fn SaveIniFile = ( setINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinLocX" (EdgeMatch.Form.Location.x as string) setINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinLocY" (EdgeMatch.Form.Location.y as string) -- setINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinWidth" (EdgeMatch.Form.Width as string) -- setINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinHeight" (EdgeMatch.Form.Height as string) ), fn LoadIniFile = ( --default values local WinLocX = 100 local WinLocY = 100 local WinWidth = 200 local WinHeight = 200 try ( WinLocX = getINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinLocX" as integer WinLocY = getINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinLocY" as integer -- WinWidth = getINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinWidth" as integers -- WinHeight = getINISetting EdgeMatch.IniFilePath "EdgeMatch" "WinHeight" as integer ) catch() EdgeMatch.Form.Location = dotNetObject "system.drawing.point" WinLocX WinLocY -- EdgeMatch.Form.Size = dotNetObject "System.Drawing.Size" WinWidth WinHeight ), ------------------------------------------------------------------------------------------------------------------------------------------ --TOOL CODE ------------------------------------------------------------------------------------------------------------------------------------------ fn GetEdges = ( for item in selection where (this.IsTerrainMesh item) do ( convertToPoly item item.WeldThreshold = 0.01 newTerrainSeamTool_ObjectSeamData = TerrainSeamTool_ObjectSeamData obj:item newTerrainSeamTool_ObjectSeamData.InitData() append ObjectList newTerrainSeamTool_ObjectSeamData ) ), fn IsTerrainMesh obj = ( IsTerrain = false if classOf obj == Editable_Poly then IsTerrain = true if classOf obj == Editable_Mesh then IsTerrain = true if classOf obj == Edit_Poly then IsTerrain = true IsTerrain ), --returns true if the edges of the two supplied edges line up --edge: #north, #south, #east, #west fn CheckEdgeOld obj1 obj2 edge = ( --CheckEdge this.ObjectList[i].obj this.ObjectList[j].obj #east FUBAR ARG local match = false local tolerance = 0.001 --seems to be needed due to innaccuracy with logic testing of Max floats: yes, MAXScript is occasionally shit and annoying case edge of ( #north: --south edge of obj1 to north edge of obj2 ( if (abs (obj1.max.Y - obj2.min.Y) <= this.edgeTolerance) and (obj1.min.X + tolerance < obj2.max.X) and (obj1.max.X > obj2.min.X + tolerance) then match = true break ) #south: --north edge of obj1 to south edge of obj2 ( if (abs (obj1.min.Y - obj2.max.Y) <= this.edgeTolerance) and (obj1.min.X + tolerance < obj2.max.X) and (obj1.max.X > obj2.min.X + tolerance) then match = true break ) #east: --west edge of obj1 to east edge of obj2 ( if (abs (obj1.max.X - obj2.min.X) <= this.edgeTolerance) and (obj1.min.Y + tolerance < obj2.max.Y) and (obj1.max.Y > obj2.min.Y + tolerance) then match = true break ) #west: --east edge of obj1 to west edge of obj2 ( if (abs (obj1.min.X - obj2.max.X) <= this.edgeTolerance) and (obj1.min.Y + tolerance < obj2.max.Y) and (obj1.max.Y > obj2.min.Y + tolerance) then match = true break ) ) match ), --returns the minimum and maximum values for a set of given vertices along a specific axis fn GetMinMax obj vertIDs axis = ( local id local numVerts = vertIDs.count if (axis == #x) then id = 1 if (axis == #y) then id = 2 local minValue = obj.verts[vertIDs[1]].position[id] local maxValue = obj.verts[vertIDs[1]].position[id] for i = 2 to numVerts do ( local newValue = obj.verts[vertIDs[i]].position[id] if (newValue < minValue) then minValue = newValue else if (newValue > maxValue) then maxValue = newValue ) return (dataPair min:minValue max:maxValue) ), --returns true if the edges of the two supplied edges line up --edge: #north, #south, #east, #west fn CheckEdge obj1 obj2 edge = ( local match = false local tolerance = 0.1 --seems to be needed due to innaccuracy with logic testing of Max floats: yes, MAXScript is occasionally shit and annoying case edge of ( #north: --south edge of obj1 to north edge of obj2 ( if (abs (obj1.obj.max.Y - obj2.obj.min.Y) <= this.edgeTolerance) then ( local obj1_southVertsMinMax = this.GetMinMax obj1.obj (obj1.southVertIDs as array) #x local obj2_northVertsMinMax = this.GetMinMax obj2.obj (obj2.northVertIDs as array) #x if (obj1_southVertsMinMax.min + tolerance < obj2_northVertsMinMax.max) and (obj1_southVertsMinMax.max > obj2_northVertsMinMax.min + tolerance) then match = true ) break ) #south: --north edge of obj1 to south edge of obj2 ( if (abs (obj1.obj.min.Y - obj2.obj.max.Y) <= this.edgeTolerance) then ( local obj1_southVertsMinMax = this.GetMinMax obj1.obj (obj1.southVertIDs as array) #x local obj2_northVertsMinMax = this.GetMinMax obj2.obj (obj2.northVertIDs as array) #x if (obj1_southVertsMinMax.min + tolerance < obj2_northVertsMinMax.max) and (obj1_southVertsMinMax.max > obj2_northVertsMinMax.min + tolerance) then match = true ) break ) #east: --west edge of obj1 to east edge of obj2 ( if (abs (obj1.obj.max.X - obj2.obj.min.X) <= this.edgeTolerance) then ( local obj1_eastVertsMinMax = this.GetMinMax obj1.obj (obj1.eastVertIDs as array) #y local obj2_westVertsMinMax = this.GetMinMax obj2.obj (obj2.westVertIDs as array) #y if (obj1_eastVertsMinMax.min + tolerance < obj2_westVertsMinMax.max) and (obj1_eastVertsMinMax.max > obj2_westVertsMinMax.min + tolerance) then match = true ) break ) #west: --east edge of obj1 to west edge of obj2 ( if (abs (obj1.obj.min.X - obj2.obj.max.X) <= this.edgeTolerance) then ( local obj1_westVertsMinMax = this.GetMinMax obj1.obj (obj1.westVertIDs as array) #y local obj2_eastVertsMinMax = this.GetMinMax obj2.obj (obj2.eastVertIDs as array) #y if (obj1_westVertsMinMax.min + tolerance < obj2_eastVertsMinMax.max) and (obj1_westVertsMinMax.max > obj2_eastVertsMinMax.min + tolerance) then match = true ) break ) ) match ), --qsort function fn SortSeamVertsByX a b = ( if a.position[1] < b.position[1] do return -1 if a.position[1] > b.position[1] do return 1 return 0 ), --qsort function fn SortSeamVertsByY a b = ( if a.position[2] < b.position[2] do return -1 if a.position[2] > b.position[2] do return 1 return 0 ), --matches the vertices along a pair of matching seam edges --target can be used to force which edge stays in place --target set to undefined or #obj1 or #obj2 --targetLocks determines whether unmatched verts move to the closest source vert, or whether new verts are created on the source edge fn WeldSeam Seam target: undefined targetLocked: false = ( local minValue, maxValue, axisID local obj1 = Seam.obj1 local obj2 = Seam.obj2 local vertIDs1 = Seam.vertids1 local vertIDs2 = Seam.vertids2 local axis = Seam.axis local matchedTargetVerts = #{} local unmatchedVerts local SeamVertList1 = for item in vertIDs1 collect (TerrainSeamTool_SeamVert obj:obj1 vertid:item position:obj1.verts[item].position) local SeamVertList2 = for item in vertIDs2 collect (TerrainSeamTool_SeamVert obj:obj2 vertid:item position:obj2.verts[item].position) local numVerts = SeamVertList1.count + SeamVertList2.count --the axisID corresponds to whether we are checking in x or y if axis == #x then ( axisID = 1 QSort SeamVertList1 SortSeamVertsByX QSort SeamVertList2 SortSeamVertsByX ) else ( axisID = 2 QSort SeamVertList1 SortSeamVertsByY QSort SeamVertList2 SortSeamVertsByY ) --find the minimum and maximum values if (SeamVertList1[1].position[1] < SeamVertList2[1].position[axisID]) then minValue = SeamVertList1[1].position[axisID] else minValue = SeamVertList2[1].position[axisID] if (SeamVertList1[SeamVertList1.count].position[axisID] > SeamVertList2[SeamVertList2.count].position[1]) then maxValue = SeamVertList1[SeamVertList1.count].position[axisID] else maxValue = SeamVertList2[SeamVertList2.count].position[axisID] --the list with more verts is matched to the list with fewer local SeamListSource, SeamListTarget, SourceVertIDs, SourceObject --find the target case target of ( #obj1: ( SeamListTarget = SeamVertList1 SeamListSource = SeamVertList2 SourceVertIDs = vertiDs2 SourceObject = obj2 break ) #obj2: ( SeamListTarget = SeamVertList2 SeamListSource = SeamVertList1 SourceVertIDs = vertiDs1 SourceObject = obj1 break ) default: ( --weld verts on edge with more verts if SeamVertList1.count > SeamVertList2.count then ( SeamListSource = SeamVertList1 SeamListTarget = SeamVertList2 SourceVertIDs = vertIDs1 SourceObject = obj1 ) else ( SeamListSource = SeamVertList2 SeamListTarget = SeamVertList1 SourceVertIDs = vertIDs2 SourceObject = obj2 ) break ) ) for sourceVert in SeamListSource do ( local matchingVert = this.GetMatch sourceVert SeamListTarget axisID sourceVert.obj.verts[sourceVert.vertID].position = SeamListTarget[matchingVert].position sourceVert.position = SeamListTarget[matchingVert].position append matchedTargetVerts matchingVert ) --work out which of the target vertices have not had a corresponding source vert matched unmatchedTargetVerts = #{1..SeamListTarget.count} - matchedTargetVerts --now, what to do about the unmatched target vertices? --if targetLocked true: create new verts on the source edge --if targetLocked false: move the target verts to match source verts if (not targetLocked) then ( for id in unmatchedTargetVerts do ( local targetVert = SeamListTarget[id] local matchingVert = this.GetMatch targetVert SeamListSource axisID targetVert.obj.verts[targetVert.vertID].position = SeamListSource[matchingVert].position ) ) ), --finds the id of the closest edge vert that corresponds to a given vertex position fn GetMatch vert SeamVertList axisID = ( local position = vert.position[axisID] --the x or y postion of the test vertex local numVerts = SeamVertList.count local foundMatch = false local match =SeamVertList[numVerts] local gap = (SeamVertList[numVerts].position[axisID] - SeamVertList[1].position[axisID]) * 2 for i = 1 to numVerts where foundMatch == false do ( local newGap = abs (SeamVertList[i].position[axisID] - position) if newGap <= gap then ( gap = newGap match = i ) else foundMatch = true ) match ), --generates SeamShcekList and CornerCheckList arrays fn ProcessEdges = ( local numObjects = this.ObjectList.count local edgeMatchList = #() -- for item in this.ObjectList do item.FormatData() --dev --find all the edge matches for i = 1 to (numObjects - 1) do ( -- fix edges seams and generate CornerCheckList for j = (i + 1) to numObjects do ( --check each edge for proximity if (CheckEdge this.ObjectList[i] this.ObjectList[j] #north) then ( append edgeMatchList (TerrainSeamTool_SeamEdge obj1:this.ObjectList[i].obj vertids1:this.ObjectList[i].northVertIDs obj2:this.ObjectList[j].obj vertids2:this.ObjectList[j].southVertIDs axis:#x) -- format "Case 1 (south to north): % matched to %\n" edgeMatchList[edgeMatchList.count].obj1.name edgeMatchList[edgeMatchList.count].obj2.name --dev ) if (CheckEdge this.ObjectList[i] this.ObjectList[j] #south) then ( append edgeMatchList (TerrainSeamTool_SeamEdge this.ObjectList[i].obj this.ObjectList[i].southVertIDs this.ObjectList[j].obj this.ObjectList[j].northVertIDs #x) -- format "Case 2 (north to south): % matched to %\n" edgeMatchList[edgeMatchList.count].obj1.name edgeMatchList[edgeMatchList.count].obj2.name --dev ) if (CheckEdge this.ObjectList[i] this.ObjectList[j] #east) then ( append edgeMatchList (TerrainSeamTool_SeamEdge this.ObjectList[i].obj this.ObjectList[i].eastVertIDs this.ObjectList[j].obj this.ObjectList[j].westVertIDs #y) -- format "Case 3 (west to east): % matched to %\n" edgeMatchList[edgeMatchList.count].obj1.name edgeMatchList[edgeMatchList.count].obj2.name --dev ) if (CheckEdge this.ObjectList[i] this.ObjectList[j] #west) then ( append edgeMatchList (TerrainSeamTool_SeamEdge this.ObjectList[i].obj this.ObjectList[i].westVertIDs this.ObjectList[j].obj this.ObjectList[j].eastVertIDs #y) -- format "Case 4 (east to west): % matched to %\n" edgeMatchList[edgeMatchList.count].obj1.name edgeMatchList[edgeMatchList.count].obj2.name --dev ) ) ) local numEdgeMatches = edgeMatchList.count --execute the edge matches for item in edgeMatchList where (item.vertids1.count > 0) and (item.vertids2.count > 0) do ( this.Progress += (50 / numEdgeMatches) this.ProgBar.Value = this.Progress WeldSeam item ) ), --move all matching corner points to average positions fn ProcessCorners = ( local CornerList = #() --array of SeamVert objects which describe a vertID with it's corresponding object and position for item in this.ObjectList do ( -- item.FormatData() --dev --add the corners to the corner list but only if they exist if item.nwVert != undefined then append CornerList (TerrainSeamTool_SeamVert obj:item.obj vertid:item.nwVert position:item.obj.verts[item.nwVert].position) if item.neVert != undefined then append CornerList (TerrainSeamTool_SeamVert obj:item.obj vertid:item.neVert position:item.obj.verts[item.neVert].position) if item.swVert != undefined then append CornerList (TerrainSeamTool_SeamVert obj:item.obj vertid:item.swVert position:item.obj.verts[item.swVert].position) if item.seVert != undefined then append CornerList (TerrainSeamTool_SeamVert obj:item.obj vertid:item.seVert position:item.obj.verts[item.seVert].position) ) local initialNumCorners = CornerList.count while CornerList.count > 1 do ( --iterate through the CornerList looking for matched corner verts --note the first item in the list local numCorners = CornerList.count local refCorner = CornerList[1] local matchList = #(refCorner) local matchListIDs = #(1) local tolerance = 0.001 local averagePosition = refCorner.position for i = 2 to numCorners do ( --check the distance between other corners and the refCorner to find matches local xgap = abs (CornerList[i].position[1] - refCorner.position[1]) local ygap = abs (CornerList[i].position[2] - refCorner.position[2]) if (xgap < tolerance) and (ygap < tolerance) then (--match found as vertices are within tolerance distance on x and y axes append matchList CornerList[i] append matchListIDs i averagePosition += CornerList[i].position ) ) local matchListCount = matchList.count --when a group of matched corners is found, get the average position if (matchListCount > 1) then ( averagePosition /= matchList.count --move the vertices for item in matchList do item.obj.verts[item.vertid].position.z = averagePosition.z --we only need the vertical, x and y don't change --remove these matched corners from the CornerList for i = 1 to matchListCount do deleteItem CornerList (matchListIDs[matchListCount - i + 1]) ) else --if no match on current item, remove from the CornerList deleteItem CornerList 1 this.Progress += (50 * (numCorners - CornerList.count) / initialNumCorners) this.ProgBar.Value = this.Progress ) ), fn Process = ( ClearListener() --1) Clear data this.ObjectList = #() this.Progress = 0 this.ProgBar.Value = 0 --2) Get a list of objects and their edge vertices this.GetEdges() --3) Check that they are in proximity: generate SeamCheckList and CornerCheckList undo on ( this.ProcessEdges() --4) Match all the corners this.ProcessCorners() --5) Process isolated edge vertices -- this.ProcessIsolatedVerts() --6) Weld the edge vertices for item in this.ObjectList do polyop.WeldVertsByThreshold item.obj item.edgeVertIDs ) this.Progress = 0 this.ProgBar.Value = 0 ), ------------------------------------------------------------------------------------------------------------------------------------------ --UI ------------------------------------------------------------------------------------------------------------------------------------------ fn CreateUI = ( -- form setup Form = dotNetObject "maxCustomControls.maxForm" Form.Text = "Terrain Seam Tool" Form.StartPosition = (dotNetClass "System.Windows.Forms.FormStartPosition").manual Form.Location = dotNetObject "system.drawing.point" 0 80 Form.MaximumSize = dotNetObject "System.Drawing.Size" 1600 1200 Form.MinimumSize = dotNetObject "System.Drawing.Size" 100 100 Form.Size = dotNetObject "System.Drawing.Size" 240 230 Form.FormBorderStyle = RS_dotNetPreset.FB_FixedToolWindow -- Form.FormBorderStyle = RS_dotNetPreset.FB_Sizable dotNet.AddEventHandler Form "Load" LoadIniFile dotNet.AddEventHandler Form "Closing" SaveIniFile --content ToolTip = dotnetobject "ToolTip" Table = dotNetObject "TableLayoutPanel" Table.Dock = RS_dotNetPreset.DS_Fill Table.RowCount = 4 Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 40) --banner Table.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 100) --info panel Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 40) --button Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 16) --progress bar Table.ColumnCount = 1 Table.ColumnStyles.add (RS_dotNetObject.columnStyleObject "percent" 100) Table.Margin = RS_dotNetPreset.Padding_None Form.Controls.Add Table RSBannerPanel = dotNetObject "System.Windows.Forms.Panel" RSBannerPanel.borderstyle = RS_dotNetClass.borderStyleClass.FixedSingle RSBannerPanel.dock = RS_dotNetPreset.DS.Fill local banner = makeRsBanner dn_Panel:RSBannerPanel width:395 studio:"london" mail:"andy.davis@rockstarlondon.com" wiki:"Terrain Seam Tool" banner.setup() Table.Controls.Add RSBannerPanel 0 0 InfoPanel = RS_dotNetUI.InitLabel "" RS_dotNetPreset.TA.MiddleLeft InfoPanel.Margin = dotNetObject "System.Windows.Forms.Padding" 8 InfoPanel.Font = dotNetObject "System.Drawing.Font" "Calibri" 10 InfoPanel.Text = "Select the terrain objects whose seams you want to fix, and hit the button." InfoPanel.Text += "This tool moves edge vertices to match adjoining terrain sections and welds vertices where they coincide." Table.Controls.Add InfoPanel 0 1 ButtonTable = dotNetObject "TableLayoutPanel" ButtonTable.Dock = RS_dotNetPreset.DS_Fill ButtonTable.RowCount = 1 ButtonTable.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 100) ButtonTable.ColumnCount = 2 ButtonTable.ColumnStyles.add (RS_dotNetObject.columnStyleObject "percent" 50) ButtonTable.ColumnStyles.add (RS_dotNetObject.columnStyleObject "percent" 50) Table.Controls.Add ButtonTable 0 2 ApplyButton = RS_dotNetUI.InitButton "Fix Terrain Seam" dotNet.AddEventHandler ApplyButton "Click" Apply ApplyButton.Font = dotNetObject "System.Drawing.Font" "Arial Black" 11 ButtonTable.Controls.Add ApplyButton 0 0 ButtonTable.SetColumnSpan ApplyButton 2 UndoButton = RS_dotNetUI.InitButton "Undo" dotNet.AddEventHandler UndoButton "Click" UndoClicked -- ButtonTable.Controls.Add UndoButton 1 0 ProgBar = dotNetObject "ProgressBar" ProgBar.Dock = RS_dotNetPreset.DS_Fill Table.Controls.Add ProgBar 0 3 --draw form Form.ShowModeless() Form ) ) -- ClearListener() if EdgeMatch != undefined then EdgeMatch.Form.Close() EdgeMatch = EdgeMatchStruct() EdgeMatch.CreateUI() -- EdgeMatch.Process()