/*########### 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)