----------------------------------------------------------------------------- -- 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 ) )