1538 lines
37 KiB
Plaintext
Executable File
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
|
|
)
|
|
)
|