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

1538 lines
37 KiB
Plaintext
Executable File

-----------------------------------------------------------------------------
-- RsModelFuncs.ms
-- Functions for accessing basic model data
-----------------------------------------------------------------------------
-- Get one object-instance for each instance-set used by objs:
fn RsGetInstGroupLeaders objs =
(
local instLeaders = #()
local doneObjNums = #{}
local instanceObjs
if (objs.count != 0) do
(
for objNum = 1 to objs.count where not doneObjNums[objNum] do
(
append instLeaders objs[objNum]
instanceMgr.GetInstances objs[objNum] &instanceObjs
for instObj in instanceObjs do
(
local objNum = findItem objs instObj
if objNum != 0 do
(
doneObjNums[objNum] = true
)
)
)
)
return instLeaders
)
-- Returns True if matrices matA and matB are the same (or close enough)
fn RsCompareMatrix matA matB accuracy:0.001 translation:true =
(
if (matA == undefined) or (matB == undefined) then return false else
(
local matched = true
local partsToCompare = if translation then 4 else 3
for n = 1 to partsToCompare while matched
where ((distance matA[n] matB[n]) > accuracy)
do (matched = false)
return matched
)
)
-- Returns True if object is Editable Mesh/Poly or PolyMesh
fn isEditPolyMesh obj =
(
(isKindof obj Editable_Poly) or
(isKindof obj Editable_Mesh) or
(isKindof obj PolyMeshObject)
)
-- Provides either meshOp or polyOp, depending on the class of Obj:
fn RsMeshPolyOp obj =
(
case (classOf obj) of
(
Editable_Mesh:(meshOp)
Editable_Poly:(polyOp)
TriMesh:(meshOp)
PolyMeshObject:(polyOp)
Default:undefined
)
)
-- Provides either getMatID or polyOp.getMatID, depending on the class of Obj:
fn RsGetFaceMatIDFunc obj =
(
case (classOf obj) of
(
Editable_Poly:(polyOp.getFaceMatID)
Editable_Mesh:(getFaceMatID)
TriMesh:(getFaceMatID)
PolyMeshObject:(polyOp.getFaceMatID)
Default:undefined
)
)
-- Provides either setMatID or polyOp.setMatID, depending on the class of Obj:
fn RsSetFaceMatIDFunc obj =
(
case (classOf obj) of
(
Editable_Poly:(polyOp.setFaceMatID)
Editable_Mesh:(setFaceMatID)
TriMesh:(setFaceMatID)
PolyMeshObject:(polyOp.setFaceMatID)
Default:undefined
)
)
-- Sets matId on one or more mesh-faces:
fn RsMeshSetFaceMatIDs Obj FaceNums MatId =
(
-- Convert alternative argument-types:
-- (to maintain interchangability with PolyOp.SetFaceMatID)
case of
(
-- Convert non-arrayed numbers to array
(isKindOf FaceNums Number):
(
FaceNums = #(FaceNums)
)
-- Flag all faces for change:
(FaceNums == #All):
(
local NumFaces = GetNumFaces Obj
FaceNums = if (NumFaces == 0) then #{} else #{1..NumFaces}
)
)
-- Change MatIds for FaceNums:
for FaceNum in FaceNums do
(
SetFaceMatID Obj FaceNum MatId
)
)
-- Gets function for setting multiple matids on a given object:
-- (PolyOp.SetFaceMatId allows this by default)
fn RsSetFaceMatIDsFunc Obj =
(
local Func = RsSetFaceMatIDFunc Obj
-- This mesh-function can only change one face at a time - pass to our iterative function instead:
if (Func == SetFaceMatID) do
(
Func = RsMeshSetFaceMatIDs
)
return Func
)
-- Returns Editable Mesh face verts in array form:
fn RsMeshGetFace obj faceId =
(
local face = getFace obj faceId
#(face[1] as integer, face[2] as integer, face[3] as integer)
)
-- Returns Editable Mesh map-face verts in array form:
fn RsMeshGetMapFace obj chan faceId =
(
local face = meshOp.getMapFace obj chan faceId
#(face[1] as integer, face[2] as integer, face[3] as integer)
)
-- Provides getFace for obj
fn RsGetFaceFunc obj =
(
case (RsMeshPolyOp obj) of
(
polyOp:polyop.getFaceVerts
meshOp:RsMeshGetFace
default:undefined
)
)
-- Provides getMapFace for obj
fn RsGetMapFaceFunc obj =
(
case (RsMeshPolyOp obj) of
(
polyOp:polyop.getMapFace
meshOp:RsMeshGetMapFace
default:undefined
)
)
-- Provides either getVert or polyOp.getVert, depending on the class of Obj:
fn RsGetVertFunc obj =
(
case (RsMeshPolyOp obj) of
(
polyOp:(polyOp.getVert)
meshOp:(getVert)
default:undefined
)
)
-- Make list of poly-object poly-faces corresponding to its trimesh faces
fn RsMakeTriToPolyList obj =
(
polyOp.CollapseDeadStructs obj
local triPolys = #()
for polyNum = 1 to obj.numFaces do
(
for polyTriNum = 3 to polyOp.getFaceDeg obj polyNum do
(
append triPolys polyNum
)
)
return triPolys
)
-- Make list of trimesh faces corresponding to a poly-object's poly-faces
fn RsMakePolyToTriList obj =
(
local triPolys = RsMakeTriToPolyList obj
local polyTris = for n = 1 to triPolys[triPolys.count] collect #{}
for item in polyTris do (item.count = triPolys.count)
for triNum = 1 to triPolys.count do
(
local polyNum = triPolys[triNum]
polyTris[polyNum][triNum] = true
)
return polyTris
)
fn RsGetModelFaceIds obj =
(
local getMatIDFunc = RsGetFaceMatIDFunc obj
local ids = #()
if (getMatIDFunc != undefined) do
(
for n = 1 to obj.numfaces do
(
appendIfUnique ids (getMatIDFunc obj n)
)
)
return ids
)
-- Returns (by reference) the max/min corner-points of a point-list:
fn RsGetBBox positions &minPos &maxPos =
(
if not isKindOf minPos Point3 do minPos = [0,0,0]
if not isKindOf maxPos Point3 do maxPos = [0,0,0]
if (positions.count == 0) then (return false) else
(
for xyz = 1 to 3 do
(
local axisVals = for pos in positions collect pos[xyz]
minPos[xyz] = amin axisVals
maxPos[xyz] = amax axisVals
)
return true
)
)
-- Converts Editable Mesh objects to Editable Poly, but with improved vertex-colour transfer.
-- Unwelded edges on mapping-channels are set as visible edges on geometry, so mapping doesn't get mangled when polys are generated.
-- Set "checkChans" to a list of channel-numbers to limit the channels checked, speeding up process.
fn RsConvertToPoly objs checkChans: =
(
--local timeStart = timeStamp()
case of
(
(isKindOf objs objectSet):(objs = objs as array)
(not isKindOf objs array):(objs = #(objs))
)
local notCancelled = true
for obj in objs while notCancelled do
(
local isMesh = isKindOf obj Editable_mesh
local useVertChans
if isMesh do
(
-- Work out which vertex-channels can be or should be checked for open edges:
local objChans = if (checkChans != unsupplied) then checkChans else (for n = -2 to ((meshop.getNumMaps obj) - 3) collect n)
-- Filter out inactive channels:
useVertChans = for chanNum in objChans where (meshop.getMapSupport obj chanNum) collect chanNum
)
-- Pass straight over to converter if this isn't an Editable Mesh, or has no vert-colours:
if not isMesh or (useVertChans.count == 0) or (obj.numFaces == 0) then
(
convertToPoly obj
)
else
(
-- Ensures that undo works on edge-visibility:
convertToMesh obj
-- Make edges visible between tris with different MatIds:
::RsMakeMeshMatBordersVisible Obj
local faceCount = obj.numFaces
local edgeVisibilities = #{}
edgeVisibilities.count = (faceCount * 3)
local edgeNum = 0
-- See which edges are already visible:
for faceNum = 1 to faceCount do
(
for edgeIdx = 1 to 3 do
(
edgeNum += 1
edgeVisibilities[edgeNum] = getEdgeVis obj faceNum edgeIdx
)
)
-- Edges are qsorted to allow bsearch:
fn sortEdges v1 v2 =
(
case of
(
(v1[1] < v2[1]):-1
(v1[1] > v2[1]):1
(v1[2] < v2[2]):-1
(v1[2] > v2[2]):1
default:0
)
)
for mapChan = useVertChans do
(
pushPrompt (obj.name + ": Checking for open faces on channel " + (mapChan as string))
local mapFaceCount = meshop.getNumMapFaces obj mapChan
local mapEdges = #()
local edgeNum = 0
-- Make a list of UV-channel face-edges, for edges marked as invisible on mesh:
for faceNum = 1 to mapFaceCount do
(
local verts = meshop.getMapFace obj mapChan faceNum
local faceEdges = #(#(verts[1], verts[2]), #(verts[2], verts[3]), #(verts[3], verts[1]))
for faceEdge in faceEdges do
(
edgeNum += 1
if not edgeVisibilities[edgeNum] do
(
faceEdge[1] = faceEdge[1] as integer
faceEdge[2] = faceEdge[2] as integer
sort faceEdge
append faceEdge edgeNum
append mapEdges faceEdge
)
)
)
--clearListener()
--print (mapEdges)-- as string)
-- Sort edges for fast bsearch:
qsort mapEdges sortEdges
local edgesChecked = #{}
-- Find open map-edges that are invisible on mesh:
for edgeIdx = 1 to (mapEdges.count - 1) do
(
local mapEdge = mapEdges[edgeIdx]
local edgeNum = mapEdge[3]
if not (edgeVisibilities[edgeNum] or edgesChecked[edgeNum]) do
(
-- Find matching edge:
local foundEdge = bsearch mapEdge mapEdges sortEdges start:(edgeIdx + 1)
--format "% -> %\n" edgeNum foundEdge
-- Set visibility to true if no partner-edge was found in channel:
if (foundEdge == undefined) then
(
edgeVisibilities[edgeNum] = true
)
else
(
edgesChecked[foundEdge[3]] = true
)
edgesChecked[edgeNum] = true
)
)
popPrompt()
)
-- Set open map-edges as visible on mesh:
local edgeNum = 0
for faceNum = 1 to faceCount do
(
for edgeIdx = 1 to 3 do
(
edgeNum += 1
if edgeVisibilities[edgeNum] do
(
setEdgeVis obj faceNum edgeIdx true
)
)
)
convertToPoly obj
)
)
--format "Time taken: %s\n" ((timeStamp() - timeStart) / 1000.0)
)
-- Provide list of visible-edge vert-lists used in given mesh:
fn RsGetEdgeVertLists meshIn =
(
local edgeStringList = #()
local meshEdgeVerts = #()
-- Get mesh's visible edges:
for faceNum = 1 to meshIn.numFaces do
(
local faceVerts = getFace meshIn faceNum
for edgeNum = 1 to 3 where (getEdgeVis meshIn faceNum edgeNum) do
(
local edgeVerts = case edgeNum of
(
1:#(faceVerts.x, faceVerts.y)
2:#(faceVerts.y, faceVerts.z)
3:#(faceVerts.z, faceVerts.x)
)
-- Avoid adding edges multiple times (one edge counts as two when between two faces)
sort edgeVerts
if (appendIfUnique edgeStringList (edgeVerts as string)) do
(
append meshEdgeVerts #(edgeVerts[1] as integer, edgeVerts[2] as integer)
)
)
)
return meshEdgeVerts
)
-- Converts the visible edges of a mesh into a set of point-array lists, for optimal display by manipulators
fn RsMeshToEdgeList meshIn =
(
local timeStart = timeStamp()
local meshEdgeVerts = RsGetEdgeVertLists meshIn
-- Combine as many edge-lists as possible:
local changed = true
while changed do
(
changed = false
for listNum = 1 to (meshEdgeVerts.count - 1) where (meshEdgeVerts[listNum] != undefined) do
(
local listA = meshEdgeVerts[listNum]
local listChanged = true
while listChanged do
(
listChanged = false
for otherListNum = (listNum + 1) to meshEdgeVerts.count where (meshEdgeVerts[otherListNum] != undefined) do
(
local listB = meshEdgeVerts[otherListNum]
case of
(
(listB[1] == listA[listA.count]):
(
join listA (for n = 2 to listB.count collect listB[n])
meshEdgeVerts[otherListNum] = undefined
changed = true
listChanged = true
)
(listB[listB.count] == listA[listA.count]):
(
join listA (for n = (listB.count - 1) to 1 by -1 collect listB[n])
meshEdgeVerts[otherListNum] = undefined
changed = true
listChanged = true
)
)
)
)
)
)
local outArray = for vertList in meshEdgeVerts where vertList != undefined collect (for vert in vertList collect (getVert meshIn vert))
--format "Time: %ms\n" (timeStamp() - timeStart)
return outArray
)
-- Returns the name of the current subobject-level:
fn RsGetSubObjLevelName selClass: =
(
if selClass == unsupplied do
(
selClass = classOf (modPanel.getCurrentObject())
)
local selType = case of
(
((selClass == Editable_Poly) or (selClass == Poly_Select) or (selClass == Edit_Poly)):
(
case subobjectLevel of
(
0:#object
1:#vertex
2:#edge
3:#edge
default:#face
)
)
((selClass == Editable_Mesh) or (selClass == Mesh_Select) or (selClass == Edit_Mesh)):
(
case subobjectLevel of
(
0:#object
1:#vertex
2:#edge
default:#face
)
)
default:#object
)
return selType
)
-- Returns the selected vertnums for the specified object, converting from other subobj types if need be:
fn RsGetSelVertNums obj &useMesh: delUnusedFaces:false selClass: selType: =
(
if not (isProperty obj "mesh") do
(
return #{}
)
if (selClass == unsupplied) do
(
selClass = classOf (modPanel.getCurrentObject())
)
if (selType == unsupplied) do
(
selType = RsGetSubObjLevelName selClass:selClass
)
getMesh = case selClass of
(
#Editable_Mesh:(copy obj.baseobject.mesh)
#Editable_Poly:(copy obj.baseobject.mesh)
default:(copy obj.mesh)
)
if useMesh != unsupplied do
(
useMesh = getMesh
)
local selVerts
local selFaces = #{}
selFaces.count = getMesh.numFaces
case selType of
(
#object:
(
selVerts = ((for n = 1 to getMesh.numVerts collect n) as bitarray)
selFaces = -selFaces
)
#vertex:
(
selVerts = getVertSelection getMesh
if delUnusedFaces do
(
selFaces = meshop.getFacesUsingVert getMesh selVerts
)
)
#edge:
(
local edgeSel = getEdgeSelection getMesh
selVerts = meshop.getVertsUsingEdge getMesh edgeSel
if delUnusedFaces do
(
selFaces = meshop.getFacesUsingEdge getMesh edgeSel
)
)
#face:
(
selFaces = getFaceSelection getMesh
selVerts = meshop.getVertsUsingFace getMesh selFaces
)
)
if delUnusedFaces and (selFaces.numberSet != selFaces.count) do
(
meshop.deleteFaces getMesh -selFaces delIsoVerts:false
)
return selVerts
)
-- Returns the selected facenums for the specified object, converting from other subobj types if need be
fn RsGetSelFaceNums obj &useMesh: selType: selClass: =
(
if not (isProperty obj "mesh") do
(
return #{}
)
if (selClass == unsupplied) do
(
selClass = classOf (modPanel.getCurrentObject())
)
if (selType == unsupplied) do
(
selType = RsGetSubObjLevelName selClass:selClass
)
getMesh = case selClass of
(
#Editable_Mesh:(obj.baseobject.mesh)
#Editable_Poly:(copy obj.baseobject.mesh)
default:(copy obj.mesh)
)
if useMesh != unsupplied do
(
useMesh = getMesh
)
local selFaces = case selType of
(
#object:
(
((for n = 1 to getMesh.numFaces collect n) as bitarray)
)
#face:
(
getFaceSelection getMesh
)
)
if (selFaces == undefined) do
(
selFaces = #{}
)
return selFaces
)
-- Returns the area of triangle defined by points [v1,v2,v3]
-- Has option to show negative area if face-order is inverted
fn RsGetTriangleArea v1 v2 v3 absolute:true =
(
local retVal = (0.5 * (cross (v3 - v1) (v3 - v2))).z
if absolute then (abs retVal) else retVal
)
-- Returns a Convex Hull mesh for mesh/point-array "objMesh"
-- (optional argument "useVerts" will make it only use a specific set of verts)
fn RsBuildConvexHull objMesh optimise:4.0 showProgress:true useVerts: =
(
local timeStart = timeStamp()
-- Progress-bar will only be shown if process takes longer than this time:
local showProgressAtTime = timeStart + 300
-- Struct used to test which side of a plane a point lies:
struct planeInfoStruct
(
normal, dist,
-- Add offset to dist, to stop inside-test from being triggered by parallel-face verts:
fn init vertAPos vertBPos vertCPos =
(
normal = normalize (cross (vertBPos - vertAPos) (vertCPos - vertAPos))
dist = (dot normal vertAPos) + 0.00001
),
-- True if plane is facing towards pnt:
fn inside pnt =
(
(dot normal pnt) > dist
)
)
local vertList
if isKindOf objMesh Array then
(
vertList = objMesh
)
else
(
objMesh = if isKindOf objMesh TriMesh then (copy objMesh) else (copy objMesh.mesh)
-- Remove non-specified verts:
if (useVerts != unsupplied) do
(
useVerts.count = objMesh.numverts
meshop.deleteVerts objMesh -useVerts
)
-- Quickly get rid of coplanar faces and suchlike:
if (optimise != false) and (optimise > 0) do
(
meshop.optimize objMesh optimise 1.0 0.0 0.0 saveMatBoundries:false saveSmoothBoundries:false autoEdge:false
)
vertList = for n = 1 to objMesh.numverts collect (getVert objMesh n)
)
local usedVerts = #{}
-- Don't bother checking and building faces if there's fewer than three verts:
if (vertList.count < 3) do
(
local outMesh = TriMesh()
setMesh outMesh vertices:vertList
return outMesh
)
-- Find initial vert, with minimum y:
local vertA = 1
local vertAPos = vertList[vertA]
for n = 2 to vertList.count where (vertList[n].y < vertAPos.y) do
(
vertA = n
vertAPos = vertList[n]
)
-- Find initial edge, which has no verts to its right:
local initialEdge
local edgeFound = false
local edges = #()
local faces = #()
local edgesDone = #{}
for vertB = 1 to vertList.count where (vertB != vertA) while not edgeFound do
(
local vertBPos = vertList[vertB]
local planeInfo = planeInfoStruct()
planeInfo.init vertAPos vertBPos (vertAPos + [0,0,1])
edgeFound = true
for n = 1 to vertList.count where (n != vertA) and (n != vertB) while edgeFound do
(
if planeInfo.inside vertList[n] do
(
edgeFound = false
)
)
if edgeFound do
(
initialEdge = [vertA, vertB]
append edges initialEdge
append edges [vertB,vertA]
)
)
local usedVerts = #{initialEdge[1], initialEdge[2]}
usedVerts.count = vertList.count
local showingProgress = false
local notCancelled = true
local edgeNum = 0
local planeInfo = planeInfoStruct()
-- For edges in edge-list, find new faces:
while (edgeNum < edges.count) and (notCancelled and (not showingProgress or (notCancelled = progressUpdate (100.0 * edgeNum / edges.count)))) do
(
if showProgress and not showingProgress and (timeStamp() > showProgressAtTime) do
(
showingProgress = true
progressStart "Building convex hull:"
)
edgeNum += 1
if not edgesDone[edgeNum] do
(
local vertA = edges[edgeNum][1]
local vertB = edges[edgeNum][2]
local vertAPos = vertList[vertA]
local vertBPos = vertList[vertB]
-- Check used verts first:
local vertNums = for n = usedVerts where (n != vertA) and (n != vertB) collect n
join vertNums ((-usedVerts) as array)
local faceFound = false
for vertC = vertNums while not faceFound and (notCancelled = not getProgressCancel()) do
(
local okayToUse = true
-- Don't use this vert if it tries to make an existing edge:
for checkEdge in #([vertB, vertC], [vertC, vertA]) while okayToUse do
(
local findEdgeNum = findItem edges checkEdge
if (findEdgeNum != 0) do
(
okayToUse = not edgesDone[findEdgeNum]
)
)
if okayToUse do
(
planeInfo.init vertAPos vertBPos vertList[vertC]
local failVert
faceFound = true
for n = 1 to vertList.count where (n != vertA) and (n != vertB) and (n != vertC) while faceFound do
(
-- See if any verts are on the outside of this face:
if planeInfo.inside vertList[n] do
(
faceFound = false
)
)
if faceFound do
(
edgesDone[edgeNum] = true
append faces [vertA, vertB, vertC]
usedVerts[vertC] = true
local addEdges = #([vertB, vertC], [vertC, vertA])
for faceEdge in addEdges do
(
-- First, block this face's edges from being used again:
local findEdgeNum = findItem edges faceEdge
if (findEdgeNum == 0) then
(
append edges faceEdge
edgesDone[edges.count] = true
)
else
(
edgesDone[findEdgeNum] = true
)
-- Add edges to grow out:
local newEdge = [faceEdge[2],faceEdge[1]]
appendIfUnique edges [faceEdge[2],faceEdge[1]]
)
)
)
)
)
)
if showingProgress do
(
progressEnd()
)
if not notCancelled do
(
return undefined
)
local newVertNums = #()
newVertNums.count = vertList.count
local newVertPositions = #()
local newVertNum = 0
for vertNum in usedVerts do
(
newVertNums[vertNum] = (newVertNum += 1)
append newVertPositions vertList[vertNum]
)
local newFaces = for face in faces collect [newVertNums[face[1]], newVertNums[face[2]], newVertNums[face[3]]]
local newMesh = triMesh()
setMesh newMesh vertices:newVertPositions faces:newFaces smGroup:(for n = 1 to newFaces.count collect 0)
for n = 1 to newFaces.count do
(
setFaceSmoothGroup newMesh n 0
)
--format "Hull Time taken: %ms\n" (Timestamp() - TimeStart)
return newMesh
)
-- Returns positions of outer verts, as seen from above:
fn RsFindConvexOutline verts fatVerts:false =
(
--format "RsFindConvexOutline fatVerts:%\n" fatVerts
if (verts.count < 3) do
(
fatVerts = true
)
-- Flatten verts, or convert from Point2:
local useVerts = for vert in verts collect [vert.x, vert.y, 0]
-- format "% -> (%)\n" verts.count fatVerts
-- Expand verts out before calculating outline:
if fatVerts do
(
local biggerVerts = #()
for vert in useVerts do
(
join biggerVerts #(vert - [-0.1, 0, 0], vert - [0, -0.1, 0], vert - [0.1, 0, 0], vert - [0, 0.1, 0])
)
useVerts = biggerVerts
)
local vertCount = useVerts.count
if (vertCount <= 3) do return verts
-- Find initial vert, with minimum y:
local vertNumA = 1
local vertAPos = useVerts[1]
for n = 2 to vertCount where (useVerts[n].y < vertAPos.y) do
(
vertNumA = n
vertAPos = useVerts[n]
)
local outlineList = #(vertAPos)
-- Don't set vertNumA as true, as we want to detect loop-back:
local usedVerts = #{}
usedVerts.count = vertCount
local lastVertNum = vertNumA
local lastVertPos = vertAPos
--for vert in useVerts do print vert
local nonLooping = True
while nonLooping do
(
--format "%\n" -usedVerts
nonLooping = False
-- Find edge which has no verts to its right:
local edgeFound = false
local planeUpVec = lastVertPos + [0,0,1]
for newVertNum = 1 to vertCount where (not usedVerts[newVertNum]) and (newVertNum != lastVertNum) while (not edgeFound) do
(
local newVertPos = useVerts[newVertNum]
local normal = normalize (cross (newVertPos - lastVertPos) (planeUpVec - lastVertPos))
local dist = (dot normal lastVertPos) + 0.0001
-- Does this vert have any others to its right-hand side?
edgeFound = true
for n = 1 to vertCount where (n != lastVertNum) and (n != newVertNum) while edgeFound do
(
-- True if plane is facing towards verts[n]:
if (dot normal useVerts[n]) > dist do
(
edgeFound = false
)
)
if edgeFound do
(
nonLooping = (newVertNum != vertNumA)
if nonLooping do
(
usedVerts[newVertNum] = true
lastVertNum = newVertNum
lastVertPos = newVertPos
append outlineList newVertPos
)
)
)
)
-- Only two outline-verts were found!
-- The input-verts were probably all in a line - try again with a fatter vert-set:
if (outlineList.count < 3) do
(
if fatVerts then
(
--print "WARNING: RsFindConvexOutline FATVERTS FAILURE"
)
else
(
--format " "
outlineList = RsFindConvexOutline useVerts fatVerts:true
)
)
--format "-> % \n" outlineList.count
return outlineList
)
-- Returns list of vert-index bitarrays, one for each of meshIn's face-elements.
-- (Singleton verts are given separate bitarrays)
fn RsGetMeshVertElements meshIn useVerts: =
(
if (useVerts == unsupplied) do
(
useVerts = #{1..meshIn.numverts}
)
pushPrompt "Getting vert-elements"
local vertsFound = #{}
local elementVertLists = #()
for vertNum = useVerts where not vertsFound[vertNum] do
(
local elVerts
local vertFaces = meshop.getFacesUsingVert meshIn vertNum
if (vertFaces.numberSet == 0) then
(
elVerts = #{vertNum}
)
else
(
local faceElement = meshop.getElementsUsingFace meshIn vertFaces
elVerts = meshop.getVertsUsingFace meshIn faceElement
-- Bitarray union, get rid of irrelevant verts:
elVerts *= useVerts
)
append elementVertLists (copy elVerts)
vertsFound += elVerts
)
popPrompt()
return elementVertLists
)
-- Hashes a value, and returns the hash as a Point3 which can be saved to a UVW-channel without loss of accuracy:
-- Data is saved to
fn RsGetHashAsPoint hashVal =
(
local numString = hashVal as string
local offsetGet = if (hashVal < 0) then 2 else 1
local half = numString.count / 2
local xVal = (substring numString 1 (half + (offsetGet - 1))) as float
local yVal = (substring numString (half + offsetGet) -1)
local zVal = 0
-- Make values beginning with "0" start with "-1" instead:
if yVal[1] != "0" then (yVal = yVal as float) else
(
yVal[1] = "1"
yVal = -(yVal as float)
)
return ([xVal, yVal, zVal] )--* 0.1)
)
-- Convert X/Y coords into a hash-integer:
fn RsGetPointAsHash hashPoint =
(
local valX = (hashPoint[1] as integer) as string
local valY = (hashPoint[2] as integer) as string
-- If Y-coord begins with -1, convert that to "0"
if valY[1] == "-" do (valY = "0" + substring valY 3 -1)
local hashVal = (valX + valY) as integer
return hashVal
)
-- Returns list of coordinates scattered over obj's mesh:
fn RsGenerateRandomPointsOnSurface obj density faceNums: faceDirs: objFaceNums: rndSeed:12345 minFaceSize:0.15 triToPolyList: =
(
local st = timestamp() --get the start time
if (isKindOf rndSeed number) do
(
seed rndSeed --set the random seed to a static number to get the same distribution each run
)
local meshObj = obj
local localVals = false
local getFaceDirs = (faceDirs != unsupplied)
local getObjFaceNums = (objFaceNums != unsupplied)
local isPoly = isKindOf obj Editable_Poly
local triToPoly = #()
-- Generate tri-to-poly lookup table:
if isPoly and getObjFaceNums and (triToPolyList == unsupplied) do
(
triToPolyList = RsMakeTriToPolyList obj
)
if (faceNums == unsupplied) then
(
if isPoly do
(
meshObj = copy obj.mesh
localVals = true
)
faceNums = for n = 1 to meshObj.numfaces collect n
)
else
(
-- Convert poly to mesh, converting the face-selection in the process:
if isPoly do
(
local curfaceSel = getFaceSelection obj
setFaceSelection obj faceNums
meshObj = copy obj.mesh
localVals = true
faceNums = getFaceSelection meshObj
setFaceSelection obj curfaceSel
)
)
local totalArea = 0
local randomLookupList = for faceNum in faceNums collect
(
local faceVerts = getFace meshObj faceNum
local collectVal = dontCollect
local vertPosList = for n = 1 to 3 collect
(
local getVal = getVert meshObj faceVerts[n]
if localVals do (getVal *= obj.objectTransform)
getVal
)
local bigEnough = true
for vertIds in #(#(1,2), #(2,3), #(3,1)) while bigEnough do
(
bigEnough = (distance vertPosList[vertIds[1]] vertPosList[vertIds[2]] > minFaceSize)
)
if bigEnough do
(
local faceArea = (distance [0,0,0] (cross (vertPosList[2] - vertPosList[1]) (vertPosList[3] - vertPosList[1]))) / 2
local faceAngle = if getFaceDirs then
(
local faceNormal = getFaceNormal meshObj faceNum
if (dot faceNormal [0,0,1]) > 0.99999 then (eulerangles 0 0 0) --if the vector is nearly parallel to Z, assume 0
else
(
local theY = normalize (cross [0,0,1] faceNormal) --this is the Y axis orthogonal to the Normal and Up
local theX = normalize (cross theY faceNormal) --this is the X orthogonal to Normal and Y
local theTM = matrix3 theX theY faceNormal [0,0,0] --this is the matrix3 describing the orientation of the Normal
theTM.rotationpart as eulerangles --return its Euler rotation
)
) else undefined
collectVal = #(totalArea, totalArea += faceArea, vertPosList, faceAngle, faceNum)
)
collectVal
)
local procObjCount = (totalArea * density) as integer
--format "ProcObjCount: %\n" procObjCount
if getObjFaceNums do
(
objFaceNums.count = procObjCount
)
-- Function used for binary-searching the lookup-array:
fn findFunc key item =
(
case of
(
(key < item[1]):-1
(key > item[2]):1
default:0
)
)
local thePositions = for procObjNum = 1 to procObjCount collect
(
-- Choose a random face-item, weighted by face-area:
local randomFace = bsearch (random 0.0 totalArea) randomLookupList findFunc
-- Put this object-number on this face's obj-list:
if getObjFaceNums do
(
local triNum = randomFace[5]
objFaceNums[procObjNum] = if isPoly then triToPolyList[triNum] else triNum
)
--Get vert-list for the random face:
local faceVerts = randomFace[3]
if getFaceDirs do
(
append faceDirs randomFace[4]
)
-- Choose a random barycentric coordinate on this face:
local faceX = random 0.0 1.0 --get a random X
local faceY = random 0.0 1.0 --get a random Y
if (faceX + faceY > 1.0) do --if the sum is greater than 1, subtract them from 1.0
(
faceX = 1.0 - faceX
faceY = 1.0 - faceY
)
local faceZ = 1.0 - faceX - faceY --the third bary coord is 1.0 minus the other two
(faceVerts[1] * faceX + faceVerts[2] * faceY + faceVerts[3] * faceZ)
)
--format "Placement time: %s\n" ((timestamp()-st) / 1000.0) --report the time
return thePositions --return the position array
)
--RsGenerateRandomPointsOnSurface $ 1.0 faceNums:#{1} faceDirs:#()
-- Imports a text-format version 11 R* mesh:
-- (currently doesn't bother with loading materials or vert-channels)
-- Format is partially described here: https://devstar.rockstargames.com/wiki/index.php/Mesh_File_Description
fn RsImportMeshFile filename weldThresh:0.01 edgeThresh:24.0 =
(
local timeStart = timeStamp()
local readFile
try
(
local readFile = (replace_CRLF_with_LF ((dotNetClass "System.IO.File").ReadAllText filename)) as stringStream
)
catch
(
return undefined
)
skipToString readFile "Mtl "
local matCount = readValue readFile
local verts = #()
local faces = #()
for matNum = 1 to matCount do
(
local vertCount = (verts.count + 1)
skipToString readFile "Prim "
local primCount = readValue readFile
for primNum = 1 to primCount do
(
skipToString readFile "TRIANGLES"
skipToString readFile "{"
local faceStringsArray = filterString (readDelimitedString readFile "}") " \t\n"
local newFaces = for n = 1 to faceStringsArray.count by 3 collect
(
local face = [0,0,0]
for vertNum = 1 to 3 do
(
face[vertNum] = (faceStringsArray[n + vertNum - 1] as integer) + vertCount
)
face
)
join faces newFaces
)
skipToString readFile "Verts"
skipToString readFile "{"
local vertsStringArray = filterString (readDelimitedString readFile "}") "\n"
local newVerts = for item in vertsStringArray collect
(
local trimmed = trimLeft item
local vertChannels = filterString trimmed "/"
if (vertChannels.count == 0) then dontCollect else
(
-- Just use the vert positions, don't bother getting the rest of the vertex-data:
local posArray = filterString vertChannels[1] " "
local newVert = [0,0,0]
for n = 1 to 3 do
(
newVert[n] = (posArray[n] as float)
)
newVert
)
)
join verts newVerts
)
-- Create a mesh from the loaded data:
local newMesh = TriMesh()
if (verts.count != 0) do
(
setMesh newMesh vertices:verts faces:faces
meshop.weldVertsByThreshold newMesh #all weldThresh
meshop.autoEdge newMesh #all edgeThresh
)
--format "Load-Time: %, % ms (% verts)\n" (getFilenameFile filename) (timestamp() - timeStart) newMesh.numVerts
return newMesh
)
fn RsCheckUserPropValidity obj =
(
local propBuffer = ( getUserPropBuffer obj )
local propBufferComponents = ( filterString propBuffer "\r\n" )
for propComponent in propBufferComponents do
(
if ""==propComponent then
continue
local propParts = ( filterString propComponent " = " )
if propParts.count>2 then
(
msg = "Corrupt user defined properties on " + obj.name + ". Please reset collision types in the Collision Set tool"
gRsULog.LogError msg context:obj
return false
)
)
return true
)
-- Generates a good distinct colour for an object, with plenty of gap between colours
-- Excludes greys, or any colours close to common colours, or to specified colours:
fn RsGetRandomColour excludes:#() =
(
local defaultExcludes = #(black, white, red, yellow)
local allExcludes = #()
join allExcludes defaultExcludes
join allExcludes excludes
local excludesAsPoint3 = for exclude in allExcludes collect (exclude as point3)
local retVal
local tryCount = 0
while (retVal == undefined) do
(
retVal = [0,0,0]
for n = 1 to 3 do
(
retVal[n] = (16 * (random 1 16)) - 1
)
-- Loop may get stuck if "excludes" argument is full of colours, so allow repeats eventually:
tryCount += 1
if (tryCount == 5) do
(
allExcludes = defaultExcludes
)
for exclude in allExcludes while (retVal != undefined) do
(
if (retVal[1] == retVal[2] == retVal[3]) or ((distance retVal exclude) < 32) do
(
--format "EXCLUDED: %\n" retVal
retVal = undefined
)
)
)
return (retVal as color)
)
-- Returns a list of edges on an object's mesh:
fn RsGetMeshEdges obj includeInvisible:false =
(
local edges = #()
local edgeMesh = case classOf obj of
(
triMesh:(obj)
editable_mesh:(obj.mesh)
default:(copy obj.mesh)
)
for faceNum = 1 to edgeMesh.numFaces do
(
local faceVerts = getFace edgeMesh faceNum
for edgeNum = 1 to 3 where includeInvisible or (getEdgeVis edgeMesh faceNum edgeNum) do
(
local edgeVerts = case edgeNum of
(
1:#(faceVerts[1] as integer, faceVerts[2] as integer)
2:#(faceVerts[2] as integer, faceVerts[3] as integer)
3:#(faceVerts[3] as integer, faceVerts[1] as integer)
)
append edges edgeVerts
)
)
return edges
)
-- Set edges to visible between mesh-triangles that have different MatIds.
-- (otherwise one of those MatIds will be lost if this object is converted to Poly)
fn RsMakeMeshMatBordersVisible Obj Material: =
(
-- Can only be used on Editable Mesh or TriMesh:
if not ((isKindOf Obj Editable_Mesh) or (isKindOf Obj TriMesh)) do return False
local FaceCount = (GetNumFaces Obj)
if (FaceCount == 0) do return False
PushPrompt "Finding invisible MatId borders..."
local TimeStart = TimeStamp()
-- Get per-matid facelists:
local MatIdFaceLists = #()
::RsGetMaterialsOnObjFaces Obj Material:Material FaceLists:MatIdFaceLists
-- Split temp mesh up, so each MatId is an element:
local TempMesh = if (isValidNode Obj) then (copy Obj.Mesh) else (Copy Obj)
if (MatIdFaceLists.Count > 1) do
(
for MatIdFaceList in MatIdFaceLists do
(
MeshOp.DetachFaces TempMesh MatIdFaceList
)
)
Free MatIdFaceLists
local InvisBorderEdges = #{}
InvisBorderEdges.Count = (3 * FaceCount)
-- Get list of open edges on exploded temp-mesh:
local ObjOpenEdges = (Meshop.GetOpenEdges TempMesh)
local EdgeNum = 0
for FaceNum = 1 to FaceCount do
(
for EdgeIdx = 1 to 3 do
(
EdgeNum += 1
-- Is this an invisible open edge?
if ObjOpenEdges[EdgeNum] and not (GetEdgeVis Obj FaceNum EdgeIdx) do
(
InvisBorderEdges[EdgeNum] = True
)
)
)
Free TempMesh
-- Set found material-border edges to Visible:
if (InvisBorderEdges.NumberSet != 0) do undo "Make Edges Visible" on
(
-- Makes visibility-changes undoable:
if (isValidNode Obj) do (ConvertToMesh Obj)
for EdgeNum in InvisBorderEdges do
(
-- Convert edge-number to face/edge indices:
local TriNum = (1 + (EdgeNum - 1) / 3)
local EdgeIdx = (1 + mod (EdgeNum - 1) 3)
SetEdgeVis Obj TriNum EdgeIdx True
)
Update Obj
--SetEdgeSelection Obj InvisBorderEdges
)
--format "[MatId-Border Search: % seconds; Edges Visibled: %]\n" ((timeStamp() - timeStart) / 1000.0) (InvisBorderEdges.NumberSet)
PopPrompt()
return OK
)
-- Sets Editable Mesh map-face verts passed in as an array:
fn RsMeshSetMapFace obj chan faceId faceVerts =
(
meshOp.setMapFace obj chan faceId [faceVerts[1], faceVerts[2], faceVerts[3]]
)
fn RsSetMapFaceFunc obj =
(
case (RsMeshPolyOp obj) of
(
polyOp:polyop.setMapFace
meshOp:RsMeshSetMapFace
default:undefined
)
)