Files
2025-09-29 00:52:08 +02:00

473 lines
13 KiB
Plaintext
Executable File

--
-- File:: rockstar/helpers/TerrainLodder.ms
-- Description:: Terrain Lodder
--
-- Author:: Stuart Macdonald <stuart.macdonald@rockstarnorth.com
--
-----------------------------------------------------------------------------
-- HISTORY
--
--v1.1 (SM) Altered default vert percentage to 60 from 30
-- (SM) Added prefix or postfix option for LOD tag
--v1.2 (SM) Added checkbox for Maintain Base Vertices option of MultiRes modifier
--
-----------------------------------------------------------------------------
filein "pipeline/util/meshutil.ms" -- Used for checking for verts in same position
-----------------------------------------------------------------------------
-- Rollouts
-----------------------------------------------------------------------------
rollout TerrainLodder_ProOptimize "Pro_Optimize"
(
--variables
local LODmat = undefined
group ""
(
radioButtons rbt_lod "Name Position:" width:118 height:30 labels:#("LOD_", "_LOD") default:2 columns:2 align:#left across:2
materialbutton picklodmtl "Pick LOD Mat" width:100 height:23 align:#right
)
group "Optimisation Options"
(
spinner optrange "Vertex %" range:[1,100,60] type:#integer width:104 height:16 across:2 align:#left
spinner pushrange "Push" range:[-10.0,10.0,-0.2] width:104 height:16 range:[0,100,0]
checkbox chkExcludeBorders "Exclude Borders:" checked:true tooltip:"On = Exclude, Off = Protect"
checkbox chkKeepMaterialBoundaries "Keep Material Boundaries:" checked:true
checkbox chkKeepVertexColours "Keep Vertex Colours:" checked:true
checkbox chkKeepTextures "Keep Textures:" checked:true
checkbox chkKeepUVBoundaries "Keep UV Boundaries:" checked:true
checkbox chkLockVertexPos "Lock Vertex Position:" checked:false
)
button lodit "LOD IT" width:243 height:24
--checkbox chbx_mverts "Keep Base Verts" checked:true tooltip:"keep this on for terrain" pos:[150,85] width:103 height:17
--select inner verts of polymesh
fn selInnerVerts n =
(
--Get exterior vertices
OpenEdges = polyOp.getOpenEdges n
SelVerts = polyOp.getVertsUsingEdge n OpenEdges
n.selectedVerts = -SelVerts
)
--select outer verts of polymesh
fn selOuterVerts n =
(
--Get exterior vertices
OpenEdges = polyOp.getOpenEdges n
SelVerts = polyOp.getVertsUsingEdge n OpenEdges
n.selectedVerts = SelVerts
)
--optimize function
fn optlod =
(
local proOptMod = ProOptimizer()
modPanel.addModToSelection proOptMod
proOptMod.VertexPercent = (optrange.value as Float)
proOptMod.OptimizationMode = if chkExcludeBorders.checked then 2 else 1
proOptMod.LockMat = chkKeepMaterialBoundaries.checked
proOptMod.KeepVC = chkKeepVertexColours.checked
proOptMod.KeepUV = chkKeepTextures.checked
proOptMod.LockUV = chkKeepUVBoundaries.checked
proOptMod.LockPoints = chkLockVertexPos.checked
proOptMod.Calculate = on
)
--push function
fn pushlod =
(
convertTo $ Editable_Poly
selInnerVerts $
subObjectLevel = 1
--Add the Push mod
pushmod = push()
pushmod.push_value = pushrange.value
modPanel.addModtoSelection( pushmod )
)
fn projectVertexMap source target channel:0 =
(
addModifier source (Turn_to_Mesh())
collapseStack source
local sourceMesh = source --needs to be editable mesh
--Check map support
--return if there is no map data on the source
if (meshop.getMapSupport source -2) == false then return false
local targetMesh = snapshotAsMesh target
if not ( meshop.getMapSupport sourceMesh channel ) then (
meshop.setMapSupport sourceMesh channel True
)
if not ( meshop.getMapSupport targetMesh channel ) then (
meshop.setMapSupport targetMesh channel True
)
--setup RayMeshGridIntersect
local rayMesh = RayMeshGridIntersect()
rayMesh.Initialize 10
rayMesh.addNode target
--print (rayMesh.nodelist())
raymesh.BuildGrid()
--raycast form source vert normal into the the target
for vtx=1 to (getNumVerts sourceMesh) do
(
--the Position
local position = in coordsys #world (meshop.getVert sourceMesh vtx node:source)
--get the normal
local normal = normalize(getnormal sourceMesh 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 mapverts for the face hit
local faceMapVerts = meshop.getMapFace targetMesh channel theFace --will be in index order
--local faceMapVerts = (meshop.getMapVertsUsingMapFace targetMesh channel theFace) as Array
--get the mapvert values for the face hit
local mapVertValues = #((meshop.getMapVert targetMesh channel faceMapVerts.x),
(meshop.getMapVert targetMesh channel faceMapVerts.y),
(meshop.getMapVert targetMesh channel faceMapVerts.z))
--result is the barycentric offset weighted value of the mapvert values
local colour = (baryPt.x * mapVertValues[1]) + (baryPt.y * mapVertValues[2]) + (baryPt.z * mapVertValues[3])
--set the colour
local geoFaces = meshop.getFacesUsingVert sourceMesh vtx as Array
for f in geoFaces do
(
local geoFaceVerts = getFace sourceMesh f
local mapFaceVerts = meshop.getMapFace sourceMesh channel f --will be in index order
--match up indices
mapVert = case of
(
(geoFaceVerts.x == vtx): mapFaceVerts.x
(geoFaceVerts.y == vtx): mapFaceVerts.y
(geoFaceVerts.z == vtx): mapFaceVerts.z
)
meshop.setMapVert sourceMesh channel mapVert colour
)
)
)
update source
)
on picklodmtl picked mtl do
(
LODmat = mtl
picklodmtl.caption = mtl.name
)
on lodit pressed do
(
selset = getCurrentSelection()
lodset = #()
-- If it's not an editable mesh or poly, exit here rather than possibly after changing some
-- selected objects in the next loop.
for obj in selset do
(
if (( classOf obj != editable_Mesh) and ( classOf obj != editable_Poly)) then
(
messagebox "One of the selected objects is not an editable mesh or poly"
return false
)
)
undo "Create Terrain LOD" on
(
clearselection()
for n = 1 to selset.count do
(
-- Delete any faces with 0 area, as that causes a crash after Multires
-- whenever garbage collection is next done.
RSFindFacesWithZeroArea selset[n] deleteFaces:true
--check it is editable mesh, convert
if classOf selset[n] == editable_Mesh then convertTo selset[n] Editable_Poly
--check obj is editable poly
if classOf selset[n] == editable_Poly then
(
selOuterVerts selset[n]
newlod = copy selset[n]
highLodModel = selset[n]
--check whether prefix LOD_ or postfix _LOD
if rbt_lod.state == 1 then
(
newlod.name = "LOD_" + selset[n].name
) else newlod.name = selset[n].name + "_LOD"
-- Automatically link the LOD
if (newlod != undefined and highLodModel != undefined) do (
LinkType_LOD = 0
RsSceneLink.AddContainer LinkType_LOD selset[n]
RsSceneLink.SetParent LinkType_LOD selset[n] newlod
)
append lodset newlod
)
)
max modify mode
--run push on each object
for n = 1 to lodset.count do
(
select lodset[n]
optlod()
pushlod()
projectVertexMap lodset[n] selset[n] channel:-2
collapseStack lodset[n]
PolyToolsModeling.Quadrify false false
--Apply standard material if none picked
if LODmat == undefined then
(
lodset[n].material = standardMaterial()
lodset[n].material.name = "CHANGE_THIS"
) else lodset[n].material = LODmat
)
--select the lods
select lodset
selectMore selset
clearselection()
select lodset
)
)
)
rollout TerrainLodder_MultiRes "MultiRes"
(
--variables
local LODmat
button lodit "LOD IT" pos:[19,125] width:243 height:24
radioButtons rbt_lod "Name Position:" pos:[17,27] width:118 height:30 labels:#("LOD_", "_LOD") default:2 columns:2
materialbutton picklodmtl "Pick LOD Mat" pos:[149,36] width:100 height:23
spinner optrange "Vert" range:[1,100,60] type:#integer pos:[27,81] width:104 height:16
spinner pushrange "Push" range:[-10.0,10.0,-0.2] pos:[27,101] width:104 height:16 range:[0,100,0]
label lblVert "Percentage of verts left:" pos:[17,64] width:128 height:16
checkbox chbx_mverts "Keep Base Verts" checked:true tooltip:"keep this on for terrain" pos:[150,85] width:103 height:17
--select inner verts of polymesh
fn selInnerVerts n =
(
--Get exterior vertices
OpenEdges = polyOp.getOpenEdges n
SelVerts = polyOp.getVertsUsingEdge n OpenEdges
n.selectedVerts = -SelVerts
)
--select outer verts of polymesh
fn selOuterVerts n =
(
--Get exterior vertices
OpenEdges = polyOp.getOpenEdges n
SelVerts = polyOp.getVertsUsingEdge n OpenEdges
n.selectedVerts = SelVerts
)
--optimize function
fn optlod =
(
modPanel.addModToSelection (Multires())
$.modifiers[#MultiRes].resetParams = true
$.modifiers[#MultiRes].vertexPercent = optrange.value
$.modifiers[#MultiRes].baseVertices = chbx_mverts.state
$.modifiers[#MultiRes].reqGenerate = true
)
--push function
fn pushlod =
(
convertTo $ Editable_Poly
selInnerVerts $
subObjectLevel = 1
--Add the Push mod
pushmod = push()
pushmod.push_value = pushrange.value
modPanel.addModtoSelection( pushmod )
)
on picklodmtl picked mtl do
(
LODmat = mtl
picklodmtl.caption = mtl.name
)
on lodit pressed do
(
if ( false == querybox "Previous MAX crash with Multires has been avoided - do you want to continue? (Save recommended just incase)" ) do
(
return false
)
gc light:true
selset = getCurrentSelection()
lodset = #()
-- If it's not an editable mesh or poly, exit here rather than possibly after changing some
-- selected objects in the next loop.
for obj in selset do (
if (( classOf obj != editable_Mesh) and ( classOf obj != editable_Poly)) then (
messagebox "One of the selected objects is not an editable mesh or poly"
return false
)
)
clearselection()
for n = 1 to selset.count do
(
-- Delete any faces with 0 area, as that causes a crash after Multires
-- whenever garbage collection is next done.
RSFindFacesWithZeroArea selset[n] deleteFaces:true
--check it is editable mesh, convert
if classOf selset[n] == editable_Mesh then convertTo selset[n] Editable_Poly
--check obj is editable poly
if classOf selset[n] == editable_Poly then
(
selOuterVerts selset[n]
newlod = copy selset[n]
highLodModel = selset[n]
--check whether prefix LOD_ or postfix _LOD
if rbt_lod.state == 1 then
(
newlod.name = "LOD_" + selset[n].name
) else newlod.name = selset[n].name + "_LOD"
-- Automatically link the LOD
if (newlod != undefined and highLodModel != undefined) do (
LinkType_LOD = 0
RsSceneLink.AddContainer LinkType_LOD selset[n]
RsSceneLink.SetParent LinkType_LOD selset[n] newlod
)
append lodset newlod
)
)
max modify mode
--run push on each object
for n = 1 to lodset.count do
(
select lodset[n]
optlod()
pushlod()
convertTo lodset[n] Editable_Mesh
convertTo selset[n] Editable_Mesh
--Apply standard material if none picked
if LODmat == undefined then
(
lodset[n].material = standardMaterial()
lodset[n].material.name = "CHANGE_THIS"
) else lodset[n].material = LODmat
)
--select the lods
select lodset
selectMore selset
clearselection()
select lodset
)
)
rollout TerrainLodder_roll "Terrain Lodder"
(
--ui
hyperlink lnkHelp "Help?" address:"https://devstar.rockstargames.com/wiki/index.php/LOD_Toolkit#Terrain_Lodder" align:#right color:(color 0 0 255) hoverColor:(color 0 0 255) visitedColor:(color 0 0 255)
dropDownList ddlMode "Mode:" items:#("ProOptimize", "MultiRes")
subRollout lodMode "TerrainLodder_ProOptimize" width:280 height:380 offset:[-10, 0]
fn selectedMode arg =
(
case ddlMode.selected of
(
"ProOptimize":
(
RemoveSubRollout lodMode TerrainLodder_MultiRes
AddSubRollout lodMode TerrainLodder_ProOptimize
lodMode.height = 380
TerrainLodder_roll.height = 380
)
"MultiRes":
(
RemoveSubRollout lodMode TerrainLodder_ProOptimize
AddSubRollout lodMode TerrainLodder_MultiRes
lodMode.height = 250
TerrainLodder_roll.height = 250
)
)
)
on ddlMode selected arg do selectedMode arg
on TerrainLodder_roll open do
(
AddSubRollout lodMode TerrainLodder_ProOptimize
lodMode.height = 380
TerrainLodder_roll.height = 400
)
-- Save rolled-up state:
on TerrainLodder_roll rolledUp down do
(
RsSettingWrite "TerrainLodder_roll" "rollup" (not down)
)
)