-- UltimateTerrainMapper.ms -- 2012 Andy Davis -- Rockstar London -- UV-mapping tool allowing the user to map geometry that aligns to a univeral grid if (not gRsIsOutsource) do ( filein (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms") --wildwest header ) filein (RsConfigGetWildWestDir() + "script/3dsMax/_common_functions/RSL_dotNetUIOps.ms") --RS_dotNetPreset structure if (not gRsIsOutsource) do ( RsCollectToolUsageData (getThisScriptFilename()) ) global UltimateTerrainMapper ------------------------------------------------------------------------------------------------------------------------------------------ --MAIN TOOL STRUCT ------------------------------------------------------------------------------------------------------------------------------------------ struct UltimateTerrainMapperStruct ( Form, ToolTip, InfoPanel, ApplyMappingButton, MapPanel1, MapPanel2, MapPanelTable1, MapPanelTable2, ActiveCB1, ActiveCB2, NumBoxSet1, NumBoxSet2, PeelCB, EditCB, CollapseCB, ProgBar, ProgBarValue, IniFilePath = (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/RSL_Tools.ini"), FaceMode, ------------------------------------------------------------------------------------------------------------------------------------------ --GENERAL FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------ fn ApplyMapping s e = ( max modify mode local objset = for this in selection where superClassOf this == GeometryClass collect this if (subObjectLevel > 3) then UltimateTerrainMapper.FaceMode = true else UltimateTerrainMapper.FaceMode = false if objset.count > 0 then ( local active1 = UltimateTerrainMapper.ActiveCB1.Checked local active2 = UltimateTerrainMapper.ActiveCB2.Checked local peel = UltimateTerrainMapper.PeelCB.Checked local collapseState = UltimateTerrainMapper.CollapseCB.Checked local channel1 = UltimateTerrainMapper.NumBoxSet1[1].Value local channel2 = UltimateTerrainMapper.NumBoxSet2[1].Value local width1 = UltimateTerrainMapper.NumBoxSet1[2].Text as float local width2 = UltimateTerrainMapper.NumBoxSet2[2].Text as float local length1 = UltimateTerrainMapper.NumBoxSet1[3].Text as float local length2 = UltimateTerrainMapper.NumBoxSet2[3].Text as float local originalSelection = selection as array local transformedObjectSet = #() --check for objects with rotations or scaling for obj in objset do ( if (obj.rotation != (quat 0 0 0 1)) or (obj.scale != [1,1,1]) then append transformedObjectSet obj ) if (transformedObjectSet.count > 0) then ( UltimateTerrainMapper.IssueWarning transformedObjectSet ) else ( UltimateTerrainMapper.ProgBarValue = 0 UltimateTerrainMapper.ProgBar.value = 0 for i = 1 to objset.count do ( UltimateTerrainMapper.ProgBarValue += (100 / objset.count) UltimateTerrainMapper.ProgBar.Value = UltimateTerrainMapper.ProgBarValue select objset[i] if (active1) then UltimateTerrainMapper.MapObject objset[i] channel1 width1 length1 --if (peel and active1) then UltimateTerrainMapper.PeelObject objset[i] channel1 if (active2) then UltimateTerrainMapper.MapObject objset[i] channel2 width2 length2 --if (peel and active2) then UltimateTerrainMapper.PeelObject objset[i] channel2 if (peel and not active1 and not active2) then UltimateTerrainMapper.PeelObject objset[i] 1 ) if (collapseState) then convertToPoly objSet if (objset.count > 1) then select originalSelection UltimateTerrainMapper.ProgBar.value = 0 ) ) else ( messagebox "No compatible object selected" title:"Ultimate Terrain Mapper: Warning" ) ), fn IssueWarning transformedObjectSet = ( local infoText = "" if (transformedObjectSet.count == 1) then infoText += (transformedObjectSet[1].name + " has transforms.") else ( infoText +=("The following objects have transforms: " + transformedObjectSet[1].name) for obj = 2 to transformedObjectSet.count do ( infoText += ", " + transformedObjectSet[obj].name ) ) infoText += "\nPlease reset before mapping." messagebox infoText title:"Ultimate Terrain Mapper: Warning" ), fn IntegerToBitArray num = ( ba = #{} for i = 1 to num do append ba i return ba ), fn EditChecked s e = ( if (s.Checked == true) then UltimateTerrainMapper.CollapseCB.Checked = false ), fn CollapseChecked s e = ( if (s.Checked == true) then UltimateTerrainMapper.EditCB.Checked = false ), fn PeelChecked s e = ( UltimateTerrainMapper.EditCB.Visible = s.Checked ), fn ActiveChecked s e = ( UltimateTerrainMapper.ManageMapPanel s.Parent s.Checked ), fn ManageMapPanel table state = ( for i = 2 to 7 do table.Controls.Item[i].Visible = state ), fn BuildMapPanel panelName &activeCheckBox &numBoxArray &mapPanelTable = ( numBoxArray = #() numBoxArray[3] = undefined mapPanelTable = dotNetObject "TableLayoutPanel" mapPanelTable.Dock = RS_dotNetPreset.DS.Fill mapPanelTable.RowCount = 3 mapPanelTable.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 33) mapPanelTable.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 33) mapPanelTable.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 33) mapPanelTable.ColumnCount = 3 mapPanelTable.ColumnStyles.add (RS_dotNetObject.columnStyleObject "percent" 100) mapPanelTable.ColumnStyles.add (RS_dotNetObject.columnStyleObject "absolute" 80) mapPanelTable.ColumnStyles.add (RS_dotNetObject.columnStyleObject "absolute" 70) mapPanelTable.Margin = dotNetObject "System.Windows.Forms.Padding" 4 mapPanelTable.BackColor = RS_dotNetPreset.ARGB 90 90 90 mapPanelTable.Controls.Add (RS_dotNetUI.InitLabel panelName RS_dotNetPreset.TA.MiddleLeft) 0 0 activeCheckBox = RS_dotNetUI.InitCheckBox "Active" dotNet.AddEventHandler activeCheckBox "CheckedChanged" ActiveChecked mapPanelTable.Controls.Add activeCheckBox 0 1 mapPanelTable.Controls.Add (RS_dotNetUI.InitLabel "Map Channel:" RS_dotNetPreset.TA.MiddleRight) 1 0 mapPanelTable.Controls.Add (RS_dotNetUI.InitLabel "Map Width:" RS_dotNetPreset.TA.MiddleRight) 1 1 mapPanelTable.Controls.Add (RS_dotNetUI.InitLabel "Map Height:" RS_dotNetPreset.TA.MiddleRight) 1 2 numBoxArray[1] = RS_dotNetUI.InitNumericUpDown 1 99 1 numBoxArray[2] = RS_dotNetUI.InitNumericUpDown 0.1 1000 100 numBoxArray[2].DecimalPlaces = 1 numBoxArray[3] = RS_dotNetUI.InitNumericUpDown 0.1 1000 100 numBoxArray[3].DecimalPlaces = 1 mapPanelTable.Controls.Add numBoxArray[1] 2 0 mapPanelTable.Controls.Add numBoxArray[2] 2 1 mapPanelTable.Controls.Add numBoxArray[3] 2 2 ), fn UVWindowCallback = ( local hwnd = dialogMonitorOps.getWindowHandle() print (hwnd) as string if (uiAccessor.getWindowText hwnd == "Edit UVWs") then ( print "found edit window" ) true ), fn PeelObject obj mapChannel = ( local geometryType = classOf obj local openUVEdges local editChecked = UltimateTerrainMapper.EditCB.Checked max modify mode if (UltimateTerrainMapper.FaceMode) then ( modPanel.AddModToSelection (Unwrap_UVW()) ) else ( addModifier obj (Unwrap_UVW()) ) obj.Unwrap_UVW.setMapChannel mapChannel obj.Unwrap_UVW.Edit() subObjectLevel = 2 openUVEdges = UltimateTerrainMapper.GetOpenUVEdges obj obj.Unwrap_UVW.SelectEdges openUVEdges obj.Unwrap_UVW.edgeToVertSelect() obj.Unwrap_UVW.PinSelected obj obj.Unwrap_UVW.Fit() obj.Unwrap_UVW.LSCMSolve() if (not editChecked) then ( local hwnd = windows.getChildHWND 0 "Edit UVWs" if hwnd != undefined then windows.sendMessage hwnd[1] 0x0010 0 0 ) ), fn GetOpenUVEdges obj = ( local openedgelist = #{} local facecount = obj.faces.count local facebitarray = UltimateTerrainMapper.IntegerToBitarray facecount obj.Unwrap_UVW.SelectFaces facebitarray obj.Unwrap_UVW.faceToEdgeSelect() local edgebitarray = obj.Unwrap_UVW.getSelectedEdges() local edgecount = edgebitarray.count local edgecountarray = #() for i = 1 to edgecount do append edgecountarray 0 for face in facebitarray do ( obj.Unwrap_UVW.SelectFaces #{face} obj.Unwrap_UVW.faceToEdgeSelect() local faceedges = obj.Unwrap_UVW.getSelectedEdges() for thisedge in faceedges do ( edgecountarray[thisedge] += 1 ) ) for i = 1 to edgecount do if (edgecountarray[i] == 1) then append openedgelist i return openedgelist ), -- Round the supplied number to the nearest multiple. fn roundToNearestMultiple numToRound multiple = ( local result = undefined -- Negate the multiple if the number to round is a negative. if numToRound < 0 and multiple > 0 then ( multiple = -multiple ) -- Do nothing. if multiple == 0 then ( result = numToRound ) else ( local remainder = mod numToRound multiple -- Already at the multiple. if remainder == 0 then ( result = numToRound -- Round up to the multiple. ) else ( result = numToRound + multiple - remainder ) ) result ), -- Return the world=space position of the selected faces. fn getFaceSelectionPosition obj = ( local center = undefined in coordsys #world ( if ( getSelectionLevel obj ) == #face then ( local x = #() local y = #() if classof obj.baseobject == Editable_Poly then ( local faces = ( polyop.getFaceSelection obj.baseobject ) as array for faceIdx in faces do ( local verts = ( polyop.getVertsUsingFace obj.baseobject faceIdx ) as array for vertIdx in verts do ( local vert = polyop.getVert obj.baseobject vertIdx node:obj append x vert.x append y vert.y ) ) ) else if classof obj.baseobject == Editable_Mesh then ( local faces = ( getFaceSelection obj.baseobject ) as array for faceIdx in faces do ( local verts = ( meshop.getVertsUsingFace obj.baseobject faceIdx ) as array for vertIdx in verts do ( local vert = meshop.getVert obj.baseobject vertIdx node:obj append x vert.x append y vert.y ) ) ) local minX = amin x local minY = amin y local maxX = amax x local maxY = amax y if minX != undefined then ( local bboxMin = [ minX, minY, 0 ] local bboxMax = [ maxX, maxY, 0 ] center = ( bboxMin + bboxMax ) / 2.0 ) ) ) center ), -- MapObject applies a planar map modifier according to values specified in arguments fn MapObject obj mapChannel mapwidth maplength = ( local parentPos = obj.pos -- If any faces are selected, this will return their position in world space. local faceSelectionPos = getFaceSelectionPosition obj local uvMod = Uvwmap() uvMod.mapChannel = mapChannel uvMod.length = mapLength uvMod.width = mapWidth uvMod.height = mapWidth modPanel.addModToSelection uvMod ui:on local newGizmoTransform = matrix3 1 local gizmoPos = undefined -- Faces are currently selected, so use their position. if faceSelectionPos != undefined then ( gizmoPos = faceSelectionPos print gizmoPos ) else ( gizmoPos = parentPos ) if gizmoPos != undefined then ( local nearestX = roundToNearestMultiple gizmoPos.x mapWidth local nearestY = roundToNearestMultiple gizmoPos.y mapLength --newGizmoTransform.row4 = [ 0, 0, 0] format "gizmoPos: % \n" gizmoPos --newGizmoTransform.row4 = [ parentPos.x - (parentPos.x + gizmoPos.x), parentPos.y - (parentPos.y + gizmoPos.y), 0] newGizmoTransform.row4 = [ nearestX - gizmoPos.x, nearestY - gizmoPos.y, 0 ] uvMod.gizmo.transform = newGizmoTransform ) ), -- GetGeometryNode returns the id of the highest geometry node in an object's modifier stack fn GetGeometryNode obj otherModifiers:#() = ( local geometryNode local modifierCount = obj.modifiers.count local modList = #(Edit_Poly, Edit_Mesh) join modList otherModifiers if (modifierCount > 0) then ( for i = 1 to modifierCount do ( for thisModifier in modList do ( local testmod = toLower (UltimateTerrainMapper.SwapToken (thisModifier as string) "_" " ") if (toLower (obj.modifiers[modifierCount - i + 1].name as string) == testmod) then geometryNode = (modifierCount - i + 1) ) ) ) if (geometryNode == undefined) then geometryNode = 0 return geometryNode ), fn SwapToken instring oldToken newToken = ( local tokens = filterString instring oldToken local outstring = tokens[1] local tokenCount = tokens.count if tokenCount > 1 then ( for i = 2 to tokenCount do outstring += (newToken + tokens[i]) ) return outstring ), ------------------------------------------------------------------------------------------------------------------------------------------ --LOAD/SAVE FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------ fn SaveIniFile = ( setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "WinLocX" (UltimateTerrainMapper.Form.Location.x as string) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "WinLocY" (UltimateTerrainMapper.Form.Location.y as string) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Active1" (UltimateTerrainMapper.ActiveCB1.Checked as string) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Active2" (UltimateTerrainMapper.ActiveCB2.Checked as string) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Channel1" (UltimateTerrainMapper.NumBoxSet1[1].Text) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Channel2" (UltimateTerrainMapper.NumBoxSet2[1].Text) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Width1" (UltimateTerrainMapper.NumBoxSet1[2].Text) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Width2" (UltimateTerrainMapper.NumBoxSet2[2].Text) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Height1" (UltimateTerrainMapper.NumBoxSet1[3].Text) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Height2" (UltimateTerrainMapper.NumBoxSet2[3].Text) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "PeelState" (UltimateTerrainMapper.PeelCB.Checked as string) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "EditState" (UltimateTerrainMapper.EditCB.Checked as string) setINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "CollapseState" (UltimateTerrainMapper.CollapseCB.Checked as string) ), fn LoadIniFile = ( --default values local WinLocX = 100 local WinLocY = 100 local Channel1 = 1 local Channel2 = 2 local Width1 = 100 local Width2 = 100 local Height1 = 100 local Height2 = 100 local Active1 = true local Active2 = true local PeelState = true local EditState = true local CollapseState = false try ( WinLocX = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "WinLocX" as integer WinLocY = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "WinLocY" as integer Active1 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Active1" as BooleanClass Active2 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Active2" as BooleanClass Channel1 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Channel1" as integer Channel2 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Channel2" as integer Width1 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Width1" as integer Width2 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Width2" as integer Height1 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Height1" as integer Height2 = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "Height2" as integer PeelState = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "PeelState" as BooleanClass EditState = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "EditState" as BooleanClass CollapseState = getINISetting UltimateTerrainMapper.IniFilePath "UltimateTerrainMapper" "CollapseState" as BooleanClass ) catch (print "Failed to read value") UltimateTerrainMapper.Form.Location = dotNetObject "System.Drawing.Point" WinLocX WinLocY UltimateTerrainMapper.NumBoxSet1[1].Value = Channel1 UltimateTerrainMapper.NumBoxSet1[2].Value = Width1 UltimateTerrainMapper.NumBoxSet1[3].Value = Height1 UltimateTerrainMapper.NumBoxSet2[1].Value = Channel2 UltimateTerrainMapper.NumBoxSet2[2].Value = Width2 UltimateTerrainMapper.NumBoxSet2[3].Value = Height2 UltimateTerrainMapper.ActiveCB1.Checked = Active1 UltimateTerrainMapper.ActiveCB2.Checked = Active2 UltimateTerrainMapper.PeelCB.Checked = PeelState UltimateTerrainMapper.EditCB.Checked = EditState UltimateTerrainMapper.CollapseCB.Checked = CollapseState UltimateTerrainMapper.ManageMapPanel MapPanel1 Active1 UltimateTerrainMapper.ManageMapPanel MapPanel2 Active2 UltimateTerrainMapper.EditCB.Visible = UltimateTerrainMapper.PeelCB.Checked ), ------------------------------------------------------------------------------------------------------------------------------------------ --UI ------------------------------------------------------------------------------------------------------------------------------------------ fn CreateUI = ( -- RS_dotNetPreset.Font_Main = dotNetObject "System.Drawing.Font" "Calibri" 9 -- form setup Form = dotNetObject "maxCustomControls.maxForm" Form.Text = "Ultimate Terrain Mapper" Form.StartPosition = (dotNetClass "System.Windows.Forms.FormStartPosition").manual Form.Location = dotNetObject "system.drawing.point" 0 80 Form.MaximumSize = dotNetObject "System.Drawing.Size" 280 320 Form.MinimumSize = dotNetObject "System.Drawing.Size" 280 320 Form.FormBorderStyle = RS_dotNetPreset.FBS.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 = 5 Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 40) Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 80) Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 80) Table.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 100) Table.RowStyles.add (RS_dotNetObject.rowStyleObject "absolute" 20) 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 = (dotnetClass "System.Windows.Forms.BorderStyle").FixedSingle RSBannerPanel.dock = RS_dotNetPreset.DS.Fill local banner = makeRsBanner dn_Panel:RSBannerPanel wiki:"Map_Art_Tech" versionNum:1.10 versionName:"Ossified Bean" filename:(getThisScriptFilename()) banner.setup() Table.Controls.Add RSBannerPanel 0 0 BuildMapPanel "Mapping Operation 1" &ActiveCB1 &NumBoxSet1 &MapPanel1 Table.Controls.Add MapPanel1 0 1 BuildMapPanel "Mapping Operation 2" &ActiveCB2 &NumBoxSet2 &MapPanel2 Table.Controls.Add MapPanel2 0 2 ApplyPanel = dotNetObject "TableLayoutPanel" ApplyPanel.Dock = RS_dotNetPreset.DS.Fill ApplyPanel.RowCount = 3 ApplyPanel.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 30) ApplyPanel.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 30) ApplyPanel.RowStyles.add (RS_dotNetObject.rowStyleObject "percent" 30) ApplyPanel.ColumnCount = 2 ApplyPanel.ColumnStyles.add (RS_dotNetObject.columnStyleObject "absolute" 100) ApplyPanel.ColumnStyles.add (RS_dotNetObject.columnStyleObject "percent" 100) ApplyPanel.Margin = RS_dotNetPreset.Padding_None Table.Controls.Add ApplyPanel 0 2 ApplyMappingButton = RS_dotNetUI.InitButton "Apply Mapping" dotNet.AddEventHandler ApplyMappingButton "Click" ApplyMapping ApplyMappingButton.Margin = dotNetObject "System.Windows.Forms.Padding" 8 ApplyPanel.SetRowSpan ApplyMappingButton 3 ApplyPanel.Controls.Add ApplyMappingButton 1 0 PeelCB = RS_dotNetUI.InitCheckBox "Peel Edge UVs" dotNet.AddEventHandler PeelCB "CheckedChanged" PeelChecked --ApplyPanel.Controls.Add PeelCB 0 0 EditCB = RS_dotNetUI.InitCheckBox "Edit After Peel" dotNet.AddEventHandler EditCB "CheckedChanged" EditChecked --ApplyPanel.Controls.Add EditCB 0 1 CollapseCB = RS_dotNetUI.InitCheckBox "Collapse Stack" dotNet.AddEventHandler CollapseCB "CheckedChanged" CollapseChecked ApplyPanel.Controls.Add CollapseCB 0 2 Progbar = dotNetObject "ProgressBar" Progbar.Minimum = 0 Progbar.Maximum = 100 Progbar.Value = 0 Progbar.Dock = RS_dotNetPreset.DS.Fill Table.Controls.Add ProgBar 0 3 --draw form Form.ShowModeless() Form ) ) if UltimateTerrainMapper != undefined then ( UltimateTerrainMapper.Form.Close() ) UltimateTerrainMapper = UltimateTerrainMapperStruct() UltimateTerrainMapper.CreateUI()