792 lines
28 KiB
Plaintext
Executable File
792 lines
28 KiB
Plaintext
Executable File
-- 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() |