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

339 lines
12 KiB
Plaintext
Executable File

/*###########
Vertex Density Visualizer
Description:
--Used for analyzing a mesh to see if areas are too vertex dense
History:
--Kevin Ala-Pantti - January, 2014
--Created
--Kevin Ala-Pantti - April 2014
--Adding auto calculations for segment counts, vert limits
Wiki:
--https://devstar.rockstargames.com/wiki/index.php/Vert_Density_Visualizer
###########*/
try destroyDialog(RSVertDensityVisualizer)catch()
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_createVisualizerPlane
Creates a plane over top of the selected mesh that matches the min/max X and Y points
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_createVisualizerPlane obj wSpinner hSpinner autoCalcSegs:true=
(
-- Get min and max values of the object to analyze
local minX = obj.min.x
local maxX = obj.max.x
local minY = obj.min.y
local maxY = obj.max.y
local maxZ = obj.max.z
-- Get distance between min and max to use for length / height of plane
local xDist = minX - maxX
if ( xDist < 0 ) then xDist = ( xDist * ( -1 ) )
local yDist = minY - maxY
if ( yDist < 0 ) then yDist = ( yDist * ( -1 ) )
-- Make the plane
local visualPlane = plane()
visualPlane.name = "Density Visualizer"
visualPlane.width = xDist
visualPlane.length = yDist
if ( autoCalcSegs ) then
(
hSpinner.value = ( ( yDist / 10 ) as integer )
visualPlane.lengthsegs = hSpinner.value
wSpinner.value = ( ( xDist / 10 ) as integer )
visualPlane.widthsegs = wSpinner.value
)
else
(
visualPlane.lengthsegs = hSpinner.value
visualPlane.widthsegs = wSpinner.value
)
visualPlane.pivot.x = visualPlane.min.x
visualPlane.pivot.y = visualPlane.min.y
visualPlane.pos = [minX, minY, maxZ]
visualPlane.showVertexColors = true
convertToPoly visualPlane
visualPlane
)
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_faceRayTest
Shoots a ray from the center of each face of the visual plane down towards the mesh being analyzed
If the ray doesn't hit, the plane face is deleted
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_faceRayTest obj visualPlane=
(
-- Array of faces to delete
local delFaces = #()
local faceNum = polyOp.getNumFaces visualPlane
for i = 1 to faceNum do
(
-- Get pos of face center, create a ray from there, shoot it at the analyzed mesh
local faceLoc = polyOp.getFaceCenter visualPlane i
local testRay = ray faceLoc [0,0,-1]
local nodeMaxZ = obj.max.z
testRay.pos.z = nodeMaxZ + 0.0001 * abs nodeMaxZ
local rayHit = intersectRay obj testRay
-- If the ray doesn't hit anything, add that face to the array to delete
if rayHit == undefined then append delFaces i
)
if delFaces.count > 0 then polyOp.deleteFaces visualPlane delFaces delIsoVerts:true
)
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_buildMinMaxArrays
Gets the min/max X and Y values for each face of the visualizer plane
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_buildMinMaxArrays visualPlane &faceMinMaxArray =
(
local numFaces = polyOp.getNumFaces visualPlane
for face = 1 to numFaces do
(
-- Get verts for each face; compare those verts to find min/max values for that face
local vertBits = polyOp.getVertsUsingFace visualPlane face
vertBits = ( vertBits as array )
local vert = polyOp.getVert visualPlane vertBits[1]
local minMax = #( vert.x,vert.x,vert.y,vert.y )
faceMinMaxArray[face] = minMax
for j = 2 to 4 do
(
vert = polyOp.getVert visualPlane vertBits[j]
if vert.x < faceMinMaxArray[face][1] then faceMinMaxArray[face][1] = vert.x
if vert.x > faceMinMaxArray[face][2] then faceMinMaxArray[face][2] = vert.x
if vert.y < faceMinMaxArray[face][3] then faceMinMaxArray[face][3] = vert.y
if vert.y > faceMinMaxArray[face][4] then faceMinMaxArray[face][4] = vert.y
)
)
)
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_findMatches
Compares each vert of the analyzed mesh to each min/max value of the visualizer plane until a match is found
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_findMatches obj faceMinMaxArray &foundCountArray=
(
local numVerts = polyOp.getNumVerts obj
for i = 1 to numVerts do
(
local vert = polyOp.getVert obj i
-- Loop through the faces of the visualizer plane until a match is found
local found = false
local arrIndex = 1
while found == false do
(
if ( vert.x > faceMinMaxArray[arrIndex][1] and vert.x < faceMinMaxArray[arrIndex][2] and vert.y > faceMinMaxArray[arrIndex][3] and vert.y < faceMinMaxArray[arrIndex][4] ) then
(
found = true
if ( foundCountArray[arrIndex] == undefined ) then foundCountArray[arrIndex] = 1
else foundCountArray[arrIndex] += 1
)
arrIndex += 1
if ( arrIndex > faceMinMaxArray.count ) then
(
found = true
)
)
)
)
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_colourFaces
Colours the faces of the visualizer plane based on how many verts were found inisde its bounds
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_colourFaces visualPlane foundCountArray threshold=
(
local numFaces = polyOp.getNumFaces visualPlane
for i = 1 to numFaces do
(
local clrPercent = 255
-- Blue colour to use if the face had no verts inside
local faceClr = color 0 0 125
-- Calculate the colour to use for the face; colour is a percentage of allowed verts found in that face's bounds
if ( foundCountArray[i] != undefined ) then
(
clrPercent = (((foundCountArray[i] as float) / threshold) * 255.0)
faceClr = color clrPercent clrPercent clrPercent
)
-- Use red if the face had more than the allowed # verts
if ( clrPercent > 255 ) then faceClr = red
polyop.setFaceSelection visualPlane i
visualPlane.SetFaceColor faceClr 0
)
)
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_calcSegmentArea
Calculates the area of a segment to use in formula for getting vert limit
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_calcSegmentArea visualPlane =
(
local faceVerts = polyop.getVertsUsingFace visualPlane 1
faceVerts = ( faceVerts as array )
local v1 = polyOp.getVert visualPlane faceVerts[1]
local v2 = polyOp.getVert visualPlane faceVerts[2]
local v3 = polyOp.getVert visualPlane faceVerts[3]
local xLen = v1.x - v2.x
if xLen < 0 then xLen = (xLen * (-1))
local yLen = v1.y - v3.y
if yLen < 0 then yLen = (yLen * (-1))
format "xLen: %\n" xLen
format "yLen: %\n" yLen
local faceArea = xLen * yLen
)
/*-------------------------------------------------------------------------------------------------------------------------
RsTa_calcVertsPerSeg
Calculates max allowed verts per segment
-------------------------------------------------------------------------------------------------------------------------*/
fn RsTa_calcVertsPerSeg obj visualPlane segSpinner =
(
local segArea = RsTa_calcSegmentArea visualPlane
local idxLODDist = GetAttrIndex "Gta Object" "LOD distance"
local objLODDist = GetAttr obj idxLODDist
local vertsPerSeg = ( ( 100 * segArea ) / objLODDist )
segSpinner.value = ( vertsPerSeg as integer )
format "segArea: %\n" segArea
format "vertsPerSeg: %\n" vertsPerSeg
)
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
rollout RSVertDensityVisualizer "Vert Density Visualizer"
(
local visualPlane = undefined
local foundCountArray = #()
dotNetControl rsBannerPanel "Panel" pos:[0,0] height:32 width:(RSVertDensityVisualizer.width)
local banner = makeRsBanner dn_Panel:rsBannerPanel wiki:"Vert_Density_Visualizer" filename:(getThisScriptFilename())
checkbox cbx_autoCalcVerts "Auto-Calculate Vertex Limits" checked:true
spinner spn_wSegments "Number of Width Segments:" range:[2,150,30] type:#integer fieldWidth:40 align:#right enabled:false
spinner spn_hSegments "Number of Height Segments:" range:[2,150,30] type:#integer fieldWidth:40 align:#right enabled:false
spinner spn_numAllowedVerts "Target # Verts Per Segment" range:[1,10000,1000] type:#integer fieldWidth:40 align:#right enabled:false
button btn_createPlane "Create / Update Visualizer Plane" width:200 height:30 align:#center
checkBox cbx_autoUpdate "Auto Update On # Verts Change" align:#left tooltip:"Warning: This can be slow."
checkBox cbx_cullBounds "Cull Outlying Segments" align:#left tooltip:"Warning: This checks from the face centre, so can be inaccurate."
on btn_createPlane pressed do
(
if ( visualPlane != undefined and isValidNode visualPlane ) then delete visualPlane
local curSel = getCurrentSelection()
if ( curSel.count != 1 ) then messageBox "Please select one editable_poly object." title:"Error"
else
(
curSel = curSel[1]
if ( classOf curSel == Editable_Poly ) then
(
visualPlane = RsTa_createVisualizerPlane curSel spn_wSegments spn_hSegments autoCalcSegs:cbx_autoCalcVerts.checked
if ( cbx_cullBounds.checked ) then RsTa_faceRayTest curSel visualPlane
local faceMinMaxArray = #()
RsTa_buildMinMaxArrays visualPlane &faceMinMaxArray
foundCountArray = #()
RsTa_findMatches curSel faceMinMaxArray &foundCountArray
if ( cbx_autoCalcVerts.checked ) then RsTa_calcVertsPerSeg curSel visualPlane spn_numAllowedVerts
local threshold = spn_numAllowedVerts.value
RsTa_colourFaces visualPlane foundCountArray threshold
)
)
)
on cbx_autoCalcVerts changed state do
(
if ( state ) then
(
spn_wSegments.enabled = false
spn_hSegments.enabled = false
spn_numAllowedVerts.enabled = false
)
else
(
spn_wSegments.enabled = true
spn_hSegments.enabled = true
spn_numAllowedVerts.enabled = true
)
)
on spn_numAllowedVerts changed verts do
(
if ( cbx_autoUpdate.checked ) then
(
if ( visualPlane != undefined and isValidNode visualPlane ) then RsTa_colourFaces visualPlane foundCountArray verts
)
)
on RSVertDensityVisualizer open do
(
banner.setup()
)
on RSVertDensityVisualizer close do
(
if ( visualPlane != undefined and isValidNode visualPlane ) then delete visualPlane
)
)
createDialog RSVertDensityVisualizer 220 200 style:#(#style_titlebar, #style_toolwindow, #style_sysmenu)