-- -- File:: pipeline/util/placement.ms -- Description:: Placement Toolkit -- -- Author:: Marissa Warner-Wu -- Date:: 15 March 2010 -- ----------------------------------------------------------------------------- -- HISTORY -- -- by David Muir -- Authored some scripts used here -- ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Uses ----------------------------------------------------------------------------- filein "pipeline/helpers/maps/EPlanter.ms" filein "pipeline/util/MeshUtil.ms" filein "rockstar/helpers/pathnodecheck.ms" filein "rockstar/util/datimporter.ms" filein "pipeline/helpers/climbing/handhold_importer.ms" ----------------------------------------------------------------------------- -- Rollouts ----------------------------------------------------------------------------- --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --------------------------------- PLACEMENT --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// rollout PlaceHelpersRoll "Placement Helpers" ( local fSectorSize = 50.0 local fWidthInSectors = 120.0 local fDepthInSectors = 120.0 -- FILTERS fn objFilter obj = isProperty obj "mesh" --//////////////////////////////////////////////////////////// -- interface --//////////////////////////////////////////////////////////// hyperlink lnkHelp "Help?" address:"https://devstar.rockstargames.com/wiki/index.php/Placement_Toolkit#Placement_Helpers" align:#right color:(color 0 0 255) hoverColor:(color 0 0 255) visitedColor:(color 0 0 255) local btnWidth = 110 local btnHeight = 24 local grpWidth = btnWidth + 12 group "Decal Surface Snap" ( spinner spnDecalPush "Push:" tooltip:"Distance(m) for mesh to be pushed away from the surface beneath once snapped." range:[0.0, 5.0, 0.0] type:#float align:#left across:2 offset:[15,0] button btnDecalSurfaceSnap "Snap Selected" tooltip:"Snap the decal verts to the mesh selected underneath" width:120 offset:[0, -3] ) group "Conform To Ground" ( spinner spnPush "Push:" tooltip:"Distance(m) for mesh to be pushed away from the surface beneath once conformed." range:[0.0, 5.0, 0.0] type:#float align:#left across:2 offset:[15,0] button btnConformToGround "Conform Selected" tooltip:"Conform the selected meshes to whatever mesh are below them." width:120 offset:[0, -3] ) group "Move to Ground" ( --groupBox grpAlignZ "Move to Ground" pos:[(PlaceHelpersRoll.width / 2) - 4 - grpWidth,40] width:grpWidth height:73 pickbutton pbn_ground "Pick Ground" width:120 offset:[0, 0] across:2 filter:objFilter autoDisplay:true button btnAlignZ "Move Selection" tooltip:"Move selected objects up/down until they sit on top of this picked object" width:130 checkbox chkGndAlign "Align to Ground" tooltip:"Align selection to ground-object" width:130 offset:[10,2] spinner spnPercentOfGroundMin "Min %:" tooltip:"" range:[0, 100, 0] type:#integer align:#right offset:[0,2] width:130 across:2 enabled:false spinner spnPercentOfGroundMax "Max %:" tooltip:"" range:[0, 100, 100] type:#integer align:#right offset:[0,2] width:130 enabled:false ) group "Instancer" ( --groupBox grpInstancer "Instancer" pos:[(PlaceHelpersRoll.width / 2) + 4,40] width:grpWidth height:73 pickbutton btnChooseInst "Choose Master Obj" autoDisplay:true tooltip:"Choose master-object for instancing" across:2 width:120 button btnChangeModels "Replace Selected" tooltip:"Replace selected objects with instances of above object" width:120 ) group "Random rotate in Z" ( --groupBox grpRandRotZ "Random rotate in Z" pos:(grpAlignZ.pos + [0, grpAlignZ.height + 2]) width:grpWidth height:68 button btnRandRotZ "Rotate selected" tooltip:"Rotated selected objects randomly around Z axis" across:2 width:120 radioButtons radAxisChoice "" labels:#("Local", "World") align:#left tooltip:"Rotate in Local or World Z-axis" width:120 offset:[20,2] ) group "Random Scale" ( --groupBox grpRandScale "Random Scale" pos:(grpInstancer.pos + [0, grpInstancer.height + 2]) width:grpWidth height:68 button btnRandScale "Scale selected" \ tooltip:"Scales (non-dynamic) selected objects randomly within the jitter value\nDynamic objects will be de-selected" \ across:2 width:120 spinner spnScaleJitter "Jitter %:" range:[0,100,10] type:#integer align:#left width:120 offset:[10,0] ) group "World Sector Calculator" ( --groupBox grpWorld "World Sector Calculator" offset:[4,8] width:252 height:92 --label lblWorld "World Centre:" width:77 height:16 spinner spnWorldX "World Centre: X: " across:2 offset:[40, 0] spinner spnWorldY "Y: " range:[0,100,0] --label lblSector "Sector:" width:77 height:16 spinner spnSecX "Sector: X: " range:[0,100,0] across:2 offset:[40, 0] spinner spnSecY "Y: " range:[0,100,0] ) --//////////////////////////////////////////////////////////// -- methods --//////////////////////////////////////////////////////////// -------------------------------------------------------------- -- Move to Ground -------------------------------------------------------------- fn pickFilter obj = ( (isProperty obj "mesh") ) -------------------------------------------------------------- -- World-Sector Calculator -------------------------------------------------------------- fn Refresh worldUpdate = ( if ( worldUpdate ) then ( -- World coordinates updated, reflect change in sector coords spnSecX.value = floor( ( ( spnWorldX.value as float ) / fSectorSize ) + ( fWidthInSectors / 2.0 ) ) spnSecY.value = floor( ( ( spnWorldY.value as float ) / fSectorSize ) + ( fDepthInSectors / 2.0 ) ) ) else ( -- Sector coordinates updated, reflect change in world coords spnWorldX.value = ( ( ( spnSecX.value as float ) - fWidthInSectors / 2.0 ) * fSectorSize ) + fSectorSize / 2.0 spnWorldY.value = ( ( ( spnSecY.value as float ) - fDepthInSectors / 2.0 ) * fSectorSize ) + fSectorSize / 2.0 ) ) --//////////////////////////////////////////////////////////// -- events --//////////////////////////////////////////////////////////// on chkGndAlign changed arg do ( spnPercentOfGroundMin.enabled = spnPercentOfGroundMax.enabled = arg ) ------------------------------------------------------------- -- Decal Surface Snap ------------------------------------------------------------- on btnDecalSurfaceSnap pressed do ( --check selection if selection.count == 0 then ( messageBox "Nothing Selected\nPick surface then decal before running" title:"Bad Selection" return false ) if selection.count == 1 then ( messageBox "Select the surface and decal before running" title:"Bad Selection" return false ) local surfaceMesh = convertToMesh selection[1] local decalMesh = convertToMesh selection[2] --needs to be editable mesh --setup RayMeshGridIntersect local rayMesh = RayMeshGridIntersect() rayMesh.Initialize 10 rayMesh.addNode surfaceMesh raymesh.BuildGrid() --raycast from target decal normal into the the surfaceMesh for vtx=1 to (getNumVerts decalMesh) do ( --the Position local position = in coordsys #world (meshop.getVert decalMesh vtx node:decalMesh) --get the normal local normal = normalize(getnormal decalMesh vtx) --normal = [0,0,1] --cast a ray local hits = raymesh.intersectRay position -normal true --if we get a hit then.... if hits > 0 then ( --get the face hit local theIndex = rayMesh.getClosestHit() --get the index of the closest hit by the ray local theFace = rayMesh.getHitFace theIndex --get the barycentric coords of the face hit --local baryPt = rayMesh.getHitBary theIndex --get the verts for the face hit local faceVerts = (meshop.getVertsUsingFace surfaceMesh theFace) as Array --get the closestvert, the smallest value in baryPt should give the index of the closest vert local minTest = 9999999 --local baryArray = #(baryPt.x, baryPt.y, baryPt.z) --shifted order otherwise it goes further than it should local vIdx = 0 local closestVertPos = [0, 0, 0] for pt=1 to 3 do ( local thisVertPos = meshop.getvert surfaceMesh faceVerts[pt] local dist = distance thisVertPos position if dist < minTest then ( closestVertPos = thisVertPos minTest = dist ) ) --move the decal vert to closesVert Pos + push local finalPos = closestVertPos + (spnDecalPush.value * normal) in coordsys #world meshop.setVert decalMesh #{vtx} finalPos node:decalMesh ) ) ) ------------------------------------------------------------- -- Conform To Ground ------------------------------------------------------------- on btnConformToGround pressed do ( --check anythign selected if $selection.count == 0 then ( messageBox "Nothing Selected!" title:"Error" return false ) --get the user selection local meshes = for obj in $selection where classOf obj == Editable_Poly or classOf obj == Editable_mesh collect obj if meshes.count == 0 then ( messageBox "No Meshes Selected!" title:"Error" return false ) coordsys #world --iterate through those we got for item in meshes do ( undo "drop to ground" on ( local geoClass = classOf item local numVerts = getNumVerts item.mesh local theVerts = for v=1 to (getNumVerts item.mesh) collect v if subObjectLevel != 0 then theVerts = for v=1 to item.selectedVerts.count collect item.selectedVerts[v].index local progMsg = "Calculating positions for: " + item.name progressStart progMsg progressUpdate 1 --for each vert get its position for v in theVerts do ( if getProgressCancel() then ( progressEnd() max undo return false ) local vertPos = meshop.getVert item.mesh v node:item local hitPosZ = vertPos -- fire a ray down from that position local hits = intersectRayScene (ray vertPos [0, 0, -1]) --take the hit objects that are not our dropee local hitObj = for o in hits where o[1] != item collect o[2] --collect rays --set the new position if we have one if hitObj.count != 0 then ( if hitObj.count > 1 then --find the closest ( local nearestDist = 9999999.0 local nearestObj = hitObj[1] for hit in hitObj where abs(vertPos.z - hit.pos.z) < nearestDist do ( nearestDist = abs(vertPos.z - hit.pos.z) nearestObj = hit ) hitPosZ = nearestObj.pos.z ) else ( hitPosZ = hitObj[1].pos.z ) --add any push value the user set in the UI vertPos.z = hitPosZ + spnPush.value --set the vert position to be the new position case geoClass of ( Editable_Poly: ( polyop.setVert item v vertPos ) Editable_Mesh: ( setVert item v vertPos ) )--end case )-- end hit progressUpdate (100.0 * (v / numVerts as Float)) --progressUpdate v )--end vert progressEnd() ) CenterPivot item )--end item CompleteRedraw() ) -------------------------------------------------------------- -- Move to Ground -------------------------------------------------------------- on btnAlignZ pressed do ( undo "move to ground" on ( local selObjs = for obj in selection where (isProperty obj "mesh") collect obj if (selObjs.count == 0) do ( messagebox "Please select at least one object with a mesh." return false ) if (pbn_ground.object == undefined) do ( messagebox "Please set a ground object first." return false ) if (chkGndAlign.checked) then RSmoveToGround selObjs pbn_ground.object spnPercentOfGroundMin.value spnPercentOfGroundMax.value else RSmoveToGround selObjs pbn_ground.object 0 0 ) ) -------------------------------------------------------------- -- Random Rotate -------------------------------------------------------------- on btnRandRotZ pressed do ( undo "random rotate" on ( for obj in selection do ( local rndRot = eulerangles 0 0 (random -180.0 180.0) case radAxisChoice.state of ( 1:(in coordSys local rotate obj rndRot) 2:(in coordSys world rotate obj rndRot) ) ) ) ) -------------------------------------------------------------- -- Random Scale -------------------------------------------------------------- on btnRandScale pressed do ( undo "random scale" on ( local nonDynSel = for obj in selection where ( case of ( (isRsRef obj):((obj.refDef != undefined) and (not obj.refDef.isDynamic)) (isInternalRef obj):(not RsMapObjectIsDynamic (getIRefSource obj)) default:(not RsMapObjectIsDynamic obj) ) ) collect obj -- Deselect non-dynamic objects: if nonDynSel.count != selection.count do ( clearSelection() select nonDynSel ) for obj in nonDynSel do ( local jitterAmt = spnScaleJitter.value / 100.0 local rndScale = random (1 - jitterAmt) (1 + jitterAmt) scale obj [rndScale, rndScale, rndScale] ) ) ) -------------------------------------------------------------- -- Instancer -------------------------------------------------------------- on btnChooseInst picked obj do ( global RSinstMasterobj = obj ) on btnChangeModels pressed do ( if (RSinstMasterobj == undefined) then ( messagebox "Please select an object to instance." ) else ( local models = selection as array instanceMgr.MakeObjectsUnique models #group local mastermat=RSinstMasterobj.material copyAttrs RSinstMasterobj for model in models do ( model.objectoffsetpos = [0,0,0] model.objectoffsetrot = (quat 0 0 0 1) instancereplace model RSinstMasterobj model.material=mastermat pasteAttrs model ) if isRSrefSuperClass RSinstMasterobj do ( RsRefFuncs.clearObjRememberNames models RSrefFuncs.setObjNames models ) ) ) -------------------------------------------------------------- -- World-Sector Calculator -------------------------------------------------------------- on spnWorldX changed val do ( Refresh true ) on spnWorldY changed val do ( Refresh true ) on spnSecX changed val do ( Refresh false ) on spnSecY changed val do ( Refresh false ) on GtaWorldSectorRoll open do ( Refresh true ) ) --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --------------------------------- CHECKS --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// rollout ChecksRoll "Checks" ( --//////////////////////////////////////////////////////////// -- interface --//////////////////////////////////////////////////////////// hyperlink lnkHelp "Help?" address:"https://devstar.rockstargames.com/wiki/index.php/Placement_Toolkit#Checks" align:#right color:(color 0 0 255) hoverColor:(color 0 0 255) visitedColor:(color 0 0 255) group "Path Node Checker" ( spinner spnDeviation "Deviation " width:97 align:#right button btnFixPos "Fix Positions" width:130 across:2 offset:[-2,0] button btnCheck "Check Link Deviation"width:130 offset:[2,0] ) group "Bad Object Position Finder" ( button btnFind "Find" width:130 across:2 offset:[-2,0] button btnSelect "Select" width:130 offset:[2,0] ) progressBar barLoad "ProgressBar" width:280 offset:[-10,0] height:10 color:red --//////////////////////////////////////////////////////////// -- methods --//////////////////////////////////////////////////////////// -------------------------------------------------------------- -- Path Node Checker -------------------------------------------------------------- fn RecGetFixNodes rootobj setlist = ( for i = 1 to rootobj.children.count do ( obj = rootobj.children[i] if obj.ishidden == false then ( if classof obj == Col_Mesh then ( append RsCollisionList obj ) else if classof obj == VehicleNode then ( append setlist obj ) ) RecGetFixNodes obj setlist ) ) fn RecGetCheckNodes rootobj setlist = ( for i = 1 to rootobj.children.count do ( obj = rootobj.children[i] if obj.ishidden == false then ( if classof obj == Col_Mesh then ( append RsCollisionList obj ) else if classof obj == VehicleLink then ( append setlist obj ) ) RecGetCheckNodes obj setlist ) ) --//////////////////////////////////////////////////////////// -- events --//////////////////////////////////////////////////////////// -------------------------------------------------------------- -- Path Node Checker -------------------------------------------------------------- on btnFixPos pressed do ( local RsPathNodeList = #() RecGetFixNodes rootnode RsPathNodeList for i = 1 to RsPathNodeList.count do ( obj = RsPathNodeList[i] barLoad.value = 100.0 * ((i as float)/ RsPathNodeList.count) checkNodePathAgainstCollision obj ) ) on btnCheck pressed do ( local RsPathList = #() RecGetCheckNodes rootnode RsPathList RsErrorList = #() for i = 1 to RsPathList.count do ( obj = RsPathList[i] barLoad.value = 100.0 * ((i as float)/ RsPathList.count) if checkLinkAgainstCollision obj spnDeviation.value then ( append RsErrorList obj.name ) ) rollout RsLinkErrors "Errors" ( listbox lstErrors "Links:" items:RsErrorList height:10 button btnOK "OK" on lstErrors selected item do ( foundobj = getnodebyname RsErrorList[item] exact:true if (foundobj != undefined) then ( if isdeleted foundobj == false then ( select foundobj max zoomext sel ) ) ) on btnOK pressed do ( DestroyDialog RsLinkErrors ) ) CreateDialog RsLinkErrors width:300 modal:false ) -------------------------------------------------------------- -- Bad Object Position Finder -------------------------------------------------------------- on btnFind pressed do ( format "Finding bad objects...\n" barLoad.value = 0 i = 0 cnt = 0 for obj in $objects do ( i += 1 barLoad.value = 100.* i / $objects.count if ( bit.isFinite( obj.pos.x ) and bit.isFinite( obj.pos.y ) and bit.isFinite( obj.pos.z ) ) then continue cnt += 1 format "Invalid object position: %s %s\n" obj.name obj.pos ) format "% objects found.\n" cnt ) on btnSelect pressed do ( clearSelection() barLoad.value = 0 i = 0 cnt = 0 for obj in $objects do ( i += 1 barLoad.value = 100.* i / $objects.count if ( bit.isFinite( obj.pos.x ) and bit.isFinite( obj.pos.y ) and bit.isFinite( obj.pos.z ) ) then continue cnt += 1 selectMore obj ) format "% objects selected.\n" cnt ) ) --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --------------------------------- FILE IMPORTER --/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// rollout FileImportRoll "File Importer" ( --//////////////////////////////////////////////////////////// -- interface --//////////////////////////////////////////////////////////// hyperlink lnkHelp "Help?" address:"https://devstar.rockstargames.com/wiki/index.php/Placement_Toolkit#File_Importer" align:#right color:(color 0 0 255) hoverColor:(color 0 0 255) visitedColor:(color 0 0 255) group "DAT Importer" ( button btnImport "Import" width:130 across:2 offset:[-2,0] button btnExport "Export" width:130 offset:[2,0] ) group "Handhold Importer" ( button btnLoadHandHoldXml "Load File" width:260 ) --//////////////////////////////////////////////////////////// -- events --//////////////////////////////////////////////////////////// -------------------------------------------------------------- -- DAT Importer -------------------------------------------------------------- on btnImport pressed do ( file = getopenfilename caption:"dat file to import" filename:"x:\\gta\\build\\common\\data\\paths\\" types:"DAT (*.dat)|*.dat|" if file != undefined then BuildLine file ) on btnExport pressed do ( file = getsavefilename caption:"dat file to export" filename:"x:\\gta\\build\\common\\data\\paths\\" types:"DAT (*.dat)|*.dat|" if file != undefined then ExportSpline file ) -------------------------------------------------------------- -- Handhold Importer -------------------------------------------------------------- on btnLoadHandHoldXml pressed do ( LoadHandholdFile (getOpenFilename caption:"Handhold File" types:"xml file (*.xml)|*.xml") ) ) try CloseRolloutFloater PlacementToolkit catch() global PlacementToolkit = newRolloutFloater "Placement Toolkit" 300 750 50 96 addRollout Eplanter_roll PlacementToolkit addRollout PlaceHelpersRoll PlacementToolkit addRollout ChecksRoll PlacementToolkit addRollout FileImportRoll PlacementToolkit