1061 lines
32 KiB
Plaintext
Executable File
1061 lines
32 KiB
Plaintext
Executable File
--try (destroydialog RsLodModellingRoll) catch ()
|
|
|
|
rollout RsLodModellingRoll "LOD Modelling" width:300
|
|
(
|
|
local railShapeLabels = #("^", "+", "X", "<>","--", "|")
|
|
local geoDoubleDefaults = #{1..4}
|
|
|
|
local textureDir = (RsConfigGetTextureSourceDir() + "LOD/Fizzy/")
|
|
local texDiffFilename = (textureDir + "railing_white.bmp")
|
|
local texAlphaFilename = (textureDir + "Rail_Basic_A.bmp")
|
|
|
|
local tintChan = gRsMeshTintFuncs.tintChannel
|
|
local uvChan = 1
|
|
local artifAOChan = -1
|
|
|
|
group "Railing Lodder [v1.11: Upset Juice]" -- Name generated via: (filein (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms"); print (RsRandomPhrase count:100))
|
|
(
|
|
groupBox grpRailFinder "Shape-Finder Settings:" width:(RsLodModellingRoll.width - 26) height:42
|
|
spinner spnClusterSize "Max Vertex-Cluster Size:" range:[0,1,0.16] scale:0.01 width:100 offset:[4, (-grpRailFinder.height + 12)]
|
|
tooltip:"Source-verts closer than this will be combined to generate a single spline-node"
|
|
label lblDummyA "" pos:grpRailFinder.pos height:grpRailFinder.height visible:False
|
|
|
|
groupBox grpRailMesh "Rail LOD Mesh:" width:(grpRailFinder.width) height:295
|
|
radioButtons rdoRailShape "Top-Bar Shape:" labels:railShapeLabels offset:[0, (-grpRailMesh.height + 12)] default:6
|
|
tooltip:"Top-bars of generated railings will have this cross-section"
|
|
checkBox chkUpShape "Same shape for Uprights" align:#center checked:True pos:(rdoRailShape.pos + [0, 15])
|
|
tooltip:"Give upright-bars the same cross-section as top-bars, instead of the default +"
|
|
radioButtons rdoDoubleType "Double-sided:" labels:#("Geometry", "Material") offset:[-29,-2] default:(if geoDoubleDefaults[rdoRailShape.state] then 1 else 2)
|
|
tooltip:"Either doubles-up faces (not on angled/diamond cross-sections) or sets material as two-sided"
|
|
spinner spnRailingSize "Set Thickness:" range:[0,3,0.06] scale:0.01 width:86 enabled:False offset:[-14,2]
|
|
tooltip:"Set all railings to this thickness"
|
|
|
|
spinner spnRailingRatio "Height/Width Ratio" range:[0,4,1.0] scale:0.01 width:86 offset:[-3,-3]
|
|
tooltip:"Alters generated railing-height to match this ratio"
|
|
checkBox chkAutoSize "Auto Thickness" offset:[-13,-5] align:#center checked:True
|
|
tooltip:"Automatically use average railing-thickness found on source-mesh"
|
|
spinner spnIncreaseSize "+ Size%" offset:[30,-4] range:[0,100,25] align:#center tooltip:"" width:62 type:#integer
|
|
tooltip:"Increase auto-thickness by this percentage, to account for rail-lod texture's alpha-fringe"
|
|
|
|
checkBox chkAddMat "Add Default Material" offset:[0,-3] align:#center checked:True
|
|
tooltip:"Adds material with default textures to generated meshes"
|
|
checkBox chkCopyTint "Copy Source's MeshTint" pos:(chkAddMat.pos + [8,16]) checked:False enabled:chkAddMat.checked
|
|
tooltip:"Copy MeshTint from source, if used."
|
|
checkBox chkDiffuseTints "Texture-colours => MeshTint" pos:(chkCopyTint.pos + [0,16]) checked:False enabled:chkAddMat.checked
|
|
tooltip:"Gets diffuse-colour from source-object's textures (plus any meshtinting) and applies that as mesh-tint to generated meshes"
|
|
|
|
checkBox chkDrawableLod "Set as Drawable LOD" offset:[1,-2] align:#center checked:False
|
|
tooltip:"Automatically set generated meshes as Drawable LODs for their source-meshes"
|
|
checkBox chkVertNorms "Vertical Normals on Cross-Polys" pos:(chkDrawableLod.pos + [0,16]) checked:True
|
|
tooltip:"Sets normals of crossed polys to point upwards (if used)"
|
|
|
|
button btnGenRailLodMesh "Generate Railing LOD Meshes" width:160 height:30
|
|
tooltip:"Generate LOD-meshes for selected railing-objects"
|
|
label lblDummyB "" pos:grpRailMesh.pos height:grpRailMesh.height visible:False
|
|
|
|
groupBox grpRailSpline "Rail Spline:" pos:[grpRailMesh.pos.x, grpRailMesh.pos.y + grpRailMesh.height + 4] width:grpRailMesh.width height:56
|
|
button btnGenRailSplines "Generate Railing Splines" width:160 height:30 offset:[0, (-grpRailSpline.height + 14)]
|
|
tooltip:"Generate splines from selected railing-objects"
|
|
label lblDummyC "" pos:grpRailSpline.pos height:(grpRailSpline.height + 3) visible:False
|
|
)
|
|
|
|
group ""
|
|
(
|
|
button btnComboLodder "ComboLodder" width:160 height:24 offset:[0,-4]
|
|
)
|
|
|
|
fn generateMat lodName:"LodObj" doTint:True doubleMat:False =
|
|
(
|
|
local shaderName = "cutout"
|
|
if doTint do (append shaderName "_tnt")
|
|
|
|
local objSubMat = Rage_Shader name:(lodName + "_mat")
|
|
RstSetShaderName objSubMat shaderName
|
|
RstSetIsTwoSided objSubMat doubleMat
|
|
|
|
-- Find HardAlpha index, set value to zero:
|
|
local hardAlphaIdx
|
|
for i = 1 to (RstGetVariableCount objSubMat) while (hardAlphaIdx == undefined) do
|
|
(
|
|
if (RstGetVariableName objSubMat i) == "Hard Alpha" do
|
|
(
|
|
hardAlphaIdx = i
|
|
RstSetVariable objSubMat hardAlphaIdx 0.0
|
|
)
|
|
)
|
|
|
|
local subTexMap = bitmapTexture()
|
|
subTexMap.filename = texDiffFilename
|
|
setSubTexmap objSubMat 1 subTexMap
|
|
|
|
local alphaIdxOffset = (getNumSubTexmaps objSubMat) / 2
|
|
local subTexMap = bitmapTexture()
|
|
subTexMap.filename = texAlphaFilename
|
|
setSubTexmap objSubMat (1 + alphaIdxOffset) subTexMap
|
|
|
|
local objMat = MultiMaterial name:(lodName + "_multiMat") numsubs:1
|
|
objMat.materialList[1] = objSubMat
|
|
|
|
return objMat
|
|
)
|
|
|
|
fn getCrossSect railShape radius doDouble:False =
|
|
(
|
|
local crossSect = case railShape of
|
|
(
|
|
#angle:
|
|
(
|
|
doDouble = False
|
|
|
|
local triBaseHeight = radius * sin 30
|
|
local triBaseRadius = radius * cos 30
|
|
#(#([triBaseRadius,-triBaseHeight,0], [0,radius,0], [-triBaseRadius,-triBaseHeight,0]))
|
|
)
|
|
#plusCross:
|
|
(
|
|
#(#([0,-radius,0], [0,radius,0]), #([radius,0,0], [-radius,0,0]))
|
|
)
|
|
#xCross:
|
|
(
|
|
local diag = sqrt (0.5 * (radius ^ 2))
|
|
#(#([diag,diag,0], [-diag,-diag,0]), #([diag,-diag,0], [-diag,diag,0]))
|
|
)
|
|
#diamond:
|
|
(
|
|
doDouble = False
|
|
|
|
#(#([0,-radius,0], [radius,0,0], [0,radius,0], [-radius,0,0], [0,-radius,0]))
|
|
)
|
|
#horizontal:
|
|
(
|
|
#(#([radius,0,0], [-radius,0,0]))
|
|
)
|
|
#vertical:
|
|
(
|
|
#(#([0,-radius,0], [0,radius,0]))
|
|
)
|
|
)
|
|
|
|
-- Double-back the lines:
|
|
if doDouble do
|
|
(
|
|
for lineNum = 1 to crossSect.count do
|
|
(
|
|
local lineVerts = crossSect[lineNum]
|
|
local addVerts = for vertNum = (lineVerts.count - 1) to 1 by -1 collect lineVerts[vertNum]
|
|
join lineVerts addVerts
|
|
)
|
|
)
|
|
|
|
return crossSect
|
|
)
|
|
|
|
fn genRailingLodMesh obj maxClusterSize:0.16 setRailingSize: railShape:#angle upShape:#plusCross doubleGeom:True doubleMat:False sizeMult:1.25 heightMult:1.0 \
|
|
copyTint:True tintFromDiffuse:True setAsDrawable:False addMat:True verticalNorms:True returnSpline:False =
|
|
(
|
|
if not ((isKindOf obj Editable_Mesh) or (isKindOf obj Editable_Poly)) do return Undefined
|
|
|
|
local setRailingRadius = if (isKindOf setRailingSize Number) then (setRailingSize * 0.5) else undefined
|
|
|
|
local doTint = tintFromDiffuse
|
|
|
|
local objOp = RsMeshPolyOp obj
|
|
local objGetFace = RsGetFaceFunc obj
|
|
local objGetVert = RsGetVertFunc obj
|
|
local objGetMapVert = RsGetMapFaceFunc obj
|
|
|
|
local doneVertNums = #{}
|
|
local doneFaceNums = #{}
|
|
|
|
-- Get face-selection:
|
|
local selType = RsGetSubObjLevelName()
|
|
local selFaces = if (selType == #Face) then (getFaceSelection obj) else
|
|
(
|
|
local faceCount = obj.numFaces
|
|
|
|
if (faceCount == 0) then #{} else #{1..faceCount}
|
|
)
|
|
|
|
struct vertCluster (num, pos, horizPos, radius, verts = #{}, adjVerts = #{}, adjClusters = #(), doLine = True, canFollow = True, meshTint = [1,1,1])
|
|
local clusterNum = 0
|
|
|
|
local railingLines = #()
|
|
local uprightLines = #()
|
|
|
|
-- Get all vert-positions:
|
|
local vertPosList = for vertNum = 1 to obj.numVerts collect
|
|
(
|
|
objGetVert obj vertNum
|
|
)
|
|
|
|
local geomFaces = #()
|
|
|
|
local uvMapFaces = #()
|
|
local uvMapVerts = #()
|
|
local uvMapFaceCentres = #()
|
|
|
|
local tintMapFaces = #()
|
|
local tintMapVertVals = #()
|
|
|
|
-- Does this object have a tint-channel?
|
|
local hasTintChan = (not returnSpline) and (objOp.getmapsupport obj tintChan)
|
|
local tintedFaces = #{}
|
|
|
|
-- Get materials used on object, and find texturemap faces per material:
|
|
local objMats = #()
|
|
local faceBitmaps = #()
|
|
|
|
if (doTint or (copyTint and hasTintChan)) do
|
|
(
|
|
local objMatFaces = #()
|
|
RsGetMaterialsOnObjFaces obj materials:objMats faceLists:objMatFaces
|
|
|
|
-- If object has tint-channel, does it actually use any tint-shaders?
|
|
if hasTintChan do
|
|
(
|
|
hasTintChan = False
|
|
|
|
for matNum = 1 to objMats.count do
|
|
(
|
|
local mat = objMats[matNum]
|
|
|
|
if (isKindOf mat Rage_Shader) and (matchPattern (RstGetShaderName mat) pattern:"*_tnt*") do
|
|
(
|
|
tintedFaces += objMatFaces[matNum]
|
|
hasTintChan = True
|
|
)
|
|
)
|
|
)
|
|
|
|
if hasTintChan then
|
|
(
|
|
doTint = True
|
|
)
|
|
else
|
|
(
|
|
copyTint = False
|
|
)
|
|
|
|
if tintFromDiffuse do
|
|
(
|
|
faceBitmaps.count = obj.numFaces
|
|
|
|
-- Collect diffuse-maps per material used on object:
|
|
local objMatDiffuses = for mat in objMats collect
|
|
(
|
|
local diffuseBitmap = undefined
|
|
|
|
if (isKindOf mat Rage_Shader) do
|
|
(
|
|
for n = 1 to (RstGetVariableCount mat) while (diffuseBitmap == undefined) do
|
|
(
|
|
if RsIsDiffuseMap (RstGetVariableName mat n) do
|
|
(
|
|
local texMap = (getSubTexmap mat n)
|
|
|
|
if (texMap != undefined) and (doesFileExist texMap.filename) do
|
|
(
|
|
diffuseBitmap = texMap.bitmap
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
diffuseBitmap
|
|
)
|
|
|
|
-- Set up faceBitmaps: a list of per-face diffuse bitmaps:
|
|
for matIdx = 1 to objMats.count do
|
|
(
|
|
local faceDiffuseBmp = objMatDiffuses[matIdx]
|
|
|
|
if (faceDiffuseBmp != undefined) do
|
|
(
|
|
for faceNum = objMatFaces[matIdx] do
|
|
(
|
|
faceBitmaps[faceNum] = faceDiffuseBmp
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
-- Step through each selected face-element:
|
|
local success = True
|
|
progressStart "Collecting rail-lines"
|
|
local selCount = selFaces.numberSet
|
|
for faceNum = selFaces where (not doneFaceNums[faceNum]) while (success = progressUpdate (100.0 * (doneFaceNums * selFaces).numberSet / selCount)) do
|
|
(
|
|
-- Get all faces on selected face's element:
|
|
local elemFaces = objOp.getElementsUsingFace obj #{faceNum}
|
|
doneFaceNums += elemFaces
|
|
|
|
-- Collect verts per face:
|
|
local faceVerts = #()
|
|
faceVerts.count = obj.numFaces
|
|
for faceNum = elemFaces do
|
|
(
|
|
faceVerts[faceNum] = objOp.getVertsUsingFace obj faceNum
|
|
)
|
|
|
|
-- Filter element-faces to selected faces:
|
|
local selElemFaces = (elemFaces * selFaces)
|
|
local selElemVerts = #{}
|
|
for faceNum in selElemFaces do
|
|
(
|
|
selElemVerts += faceVerts[faceNum]
|
|
)
|
|
|
|
-- Find selected faces' vertex clusters:
|
|
local vertClusters = for vertNum = selElemVerts where (not doneVertNums[vertNum]) collect
|
|
(
|
|
doneVertNums[vertNum] = True
|
|
local thisVertPos = vertPosList[vertNum]
|
|
|
|
local clusterVertNums = #{vertNum}
|
|
|
|
for thatVertNum = selElemVerts where (not doneVertNums[thatVertNum]) do
|
|
(
|
|
if ((distance thisVertPos vertPosList[thatVertNum]) < maxClusterSize) do
|
|
(
|
|
doneVertNums[thatVertNum] = True
|
|
clusterVertNums[thatVertNum] = True
|
|
)
|
|
)
|
|
|
|
local clusterPosList = for vertNum in clusterVertNums collect vertPosList[vertNum]
|
|
local clusterSphere = RsFindMinSphere clusterPosList showProgress:false
|
|
|
|
local clusterMeshTints = #()
|
|
|
|
local clusterAdjVerts = #{}
|
|
for vertNum in clusterVertNums do
|
|
(
|
|
local vertFaces = selElemFaces * (objOp.getFacesUsingVert obj vertNum)
|
|
|
|
for faceNum in vertFaces do
|
|
(
|
|
-- Collect meshtint colours for cluster:
|
|
if doTint do
|
|
(
|
|
-- Get geom/map face-data, to get the matching mapping-vert index for this geom-vert:
|
|
if (geomFaces[faceNum] == undefined) do
|
|
(
|
|
geomFaces[faceNum] = objGetFace obj faceNum
|
|
uvMapFaces[faceNum] = objGetMapVert obj uvChan faceNum
|
|
|
|
local facePos = [0,0,0]
|
|
uvMapVerts[faceNum] = for uvVertNum in uvMapFaces[faceNum] collect
|
|
(
|
|
local val = objOp.getMapVert obj uvChan uvVertNum
|
|
facePos += val
|
|
val
|
|
)
|
|
facePos /= uvMapVerts[faceNum].count
|
|
|
|
uvMapFaceCentres[faceNum] = facePos
|
|
|
|
if hasTintChan then
|
|
(
|
|
tintMapFaces[faceNum] = objGetMapVert obj tintChan faceNum
|
|
)
|
|
else
|
|
(
|
|
tintMapFaces[faceNum] = (deepCopy uvMapFaces[faceNum])
|
|
)
|
|
)
|
|
|
|
-- Get mapping-vert index:
|
|
local vertIdx = findItem geomFaces[faceNum] vertNum
|
|
local tintVertNum = tintMapFaces[faceNum][vertIdx]
|
|
|
|
-- Get tint-colour (from cache-array, if it's already been found)
|
|
if (tintMapVertVals[tintVertNum] == undefined) do
|
|
(
|
|
local vertTintVal = [1,1,1]
|
|
local vertBmpClr = [1,1,1]
|
|
|
|
-- Get tint-colour (for faces with tint-shader)
|
|
if copyTint and tintedFaces[faceNum] do
|
|
(
|
|
vertTintVal = (objOp.getMapVert obj tintChan tintVertNum)
|
|
)
|
|
|
|
-- Get bitmap-colour from texturemap, if collected/used:
|
|
local faceBitmap = faceBitmaps[faceNum]
|
|
if (faceBitmap != undefined) do
|
|
(
|
|
local bmpMaxX = (faceBitmap.width - 1)
|
|
local bmpMaxY = (faceBitmap.height - 1)
|
|
|
|
local vertUvPos = uvMapVerts[faceNum][vertIdx]
|
|
local faceCentre = uvMapFaceCentres[faceNum]
|
|
|
|
-- Get uv-positions on line from vert to face-centre (a quarter and halfway to there)
|
|
local centreOffset = (faceCentre - vertUvPos)
|
|
local uvPositions = #(vertUvPos, vertUvPos + (0.5 * centreOffset), vertUvPos + (0.25 * centreOffset))
|
|
|
|
-- Collect bitmap-coordinates for line of UVs:
|
|
local bmpPosList = for uvPos in uvPositions collect
|
|
(
|
|
-- Convert UV-coords to texture-coords:
|
|
[integer (abs (mod uvPos.x 1.0) * bmpMaxX), integer ((1 - (abs (mod uvPos.y 1.0))) * bmpMaxY)]
|
|
)
|
|
|
|
bmpPosList = makeUniqueArray bmpPosList
|
|
|
|
local totalClr = [0,0,0]
|
|
for bmpPos in bmpPosList do
|
|
(
|
|
-- Get colour from texture:
|
|
totalClr += ((getPixels faceBitmap bmpPos 1)[1] as point3)
|
|
)
|
|
|
|
vertBmpClr = totalClr / (bmpPosList.count * 255)
|
|
)
|
|
|
|
-- Combine tint and bitmap colours:
|
|
tintMapVertVals[tintVertNum] = (vertTintVal * vertBmpClr)
|
|
)
|
|
|
|
append clusterMeshTints tintMapVertVals[tintVertNum]
|
|
)
|
|
|
|
clusterAdjVerts += faceVerts[faceNum]
|
|
)
|
|
|
|
-- Don't include cluster's verts, for easier eyeballing:
|
|
clusterAdjVerts -= clusterVertNums
|
|
)
|
|
|
|
local meshTint = [1,1,1]
|
|
if doTint do
|
|
(
|
|
-- Find average of cluster's vert-colours:
|
|
local tintTotal = [0,0,0]
|
|
for clr in clusterMeshTints do
|
|
(
|
|
tintTotal += clr
|
|
)
|
|
|
|
meshTint = tintTotal / clusterMeshTints.count
|
|
)
|
|
|
|
-- Collect cluster-data struct:
|
|
local midPos = clusterSphere.pos
|
|
|
|
-- Bump radius up by multiplier, to push out alpha'd portion of generated faces:
|
|
local clusterRadius = (clusterSphere.radius * sizeMult)
|
|
|
|
vertCluster pos:midPos radius:clusterRadius horizPos:[midPos.x, midPos.y] verts:clusterVertNums adjVerts:clusterAdjVerts meshTint:meshTint
|
|
)
|
|
|
|
-- Set up node-links between clusters:
|
|
for n = 1 to vertClusters.count do
|
|
(
|
|
local thisCluster = vertClusters[n]
|
|
local adjVerts = thisCluster.adjVerts
|
|
|
|
for m = (n + 1) to vertClusters.count do
|
|
(
|
|
local thatCluster = vertClusters[m]
|
|
|
|
if ((thatCluster.verts * adjVerts).numberSet != 0) do
|
|
(
|
|
append thisCluster.adjClusters thatCluster
|
|
append thatCluster.adjClusters thisCluster
|
|
)
|
|
)
|
|
)
|
|
|
|
fn sortAdjClusters v1 v2 pos: =
|
|
(
|
|
local distA = distance pos v1.horizPos
|
|
local distB = distance pos v1.horizPos
|
|
|
|
case of
|
|
(
|
|
(distA > distB):-1
|
|
(distA < distB):1
|
|
Default:0
|
|
)
|
|
)
|
|
|
|
-- Sort adjacent-cluster links by horizontal distance, so that uprights will be found last if there's a choice:
|
|
for thisCluster in vertClusters do
|
|
(
|
|
qsort thisCluster.adjClusters sortAdjClusters pos:thisCluster.horizPos
|
|
)
|
|
|
|
-- Order clusters by connection-count, so we process lines with lots of offshoots first:
|
|
qsort vertClusters (fn sorter v1 v2 = (v2.adjClusters.count - v1.adjClusters.count))
|
|
|
|
-- Apply index-numbers to clusters:
|
|
for clusterNum = 1 to vertClusters.count do
|
|
(
|
|
vertClusters[clusterNum].num = clusterNum
|
|
)
|
|
|
|
-- Function orders two clusters by position, to make it easier to link ends up:
|
|
fn orderClustersByPos v1 v2 =
|
|
(
|
|
local posDiff = 100 * (v1.pos - v2.pos)
|
|
|
|
local retVal = 0
|
|
for n = 1 to 3 while (retVal == 0) do
|
|
(
|
|
retVal = (posDiff[n] as integer)
|
|
)
|
|
|
|
return retVal
|
|
)
|
|
|
|
for clusterNum = 1 to vertClusters.count do
|
|
(
|
|
local thisCluster = vertClusters[clusterNum]
|
|
|
|
for thatCluster in thisCluster.adjClusters where (thatCluster.num < clusterNum) do
|
|
(
|
|
local linePair = #(thisCluster, thatCluster)
|
|
qsort linePair orderClustersByPos
|
|
|
|
-- Lines are separated off if they're not tilted from vertical by a minimum angle (around four or five degrees)
|
|
local isUpright = False
|
|
if (not returnSpline) do
|
|
(
|
|
local clusterDiffNorm = normalize (thisCluster.pos - thatCluster.pos)
|
|
local tiltRatio = length [clusterDiffNorm.X, clusterDiffNorm.Y]
|
|
isUpright = (tiltRatio < 0.08)
|
|
)
|
|
|
|
-- Should this line go in the uprights-list, or the rails list?
|
|
if isUpright then
|
|
(
|
|
append uprightLines linePair
|
|
)
|
|
else
|
|
(
|
|
append railingLines linePair
|
|
)
|
|
)
|
|
)
|
|
)
|
|
progressEnd()
|
|
|
|
-- If previous step was cancelled...
|
|
if not success do return false
|
|
|
|
-- Connect up the individual upright/rail lines:
|
|
local toDoCount = (railingLines.count + uprightLines.count)
|
|
local thisNum = 0
|
|
|
|
progressStart "Connecting rail-lines"
|
|
for lineArray in #(railingLines, uprightLines) while success do
|
|
(
|
|
local doLoop = True
|
|
local lineArrayCount = lineArray.count
|
|
|
|
while doLoop and success do
|
|
(
|
|
doLoop = False
|
|
|
|
for n = 1 to lineArray.count where (lineArray[n] != undefined) while (success = progressUpdate (100.0 * (thisNum += 1) / toDoCount)) do
|
|
(
|
|
if success do
|
|
(
|
|
local thisLine = lineArray[n]
|
|
local thisLineEnd = thisLine[thisLine.count]
|
|
|
|
for m = 1 to lineArray.count where (lineArray[m] != undefined) and (n != m) do
|
|
(
|
|
local thatLine = lineArray[m]
|
|
|
|
-- Does start of that line match end of this one?
|
|
local isMatch = (thatLine[1] == thisLineEnd)
|
|
|
|
-- If the end of that line matches, use a reversed version as a match:
|
|
if (not isMatch) and (thatLine[thatLine.count] == thisLineEnd) do
|
|
(
|
|
isMatch = True
|
|
thatLine = (for n = thatLine.count to 1 by -1 collect thatLine[n])
|
|
)
|
|
|
|
if isMatch do
|
|
(
|
|
thisLine.count = (thisLine.count - 1)
|
|
join thisLine thatLine
|
|
thisLineEnd = thisLine[thisLine.count]
|
|
lineArray[m] = undefined
|
|
|
|
if not doLoop do
|
|
(
|
|
toDoCount += lineArrayCount
|
|
doLoop = True
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
progressEnd()
|
|
|
|
-- If previous step was cancelled...
|
|
if not success do return false
|
|
|
|
-- Filter out undefined values:
|
|
railingLines = for item in railingLines where (item != undefined) collect item
|
|
uprightLines = for item in uprightLines where (item != undefined) collect item
|
|
|
|
local railingRadius = 0.0
|
|
local uprightRadius = 0.0
|
|
|
|
if (setRailingRadius != undefined) then
|
|
(
|
|
railingRadius = setRailingRadius
|
|
uprightRadius = setRailingRadius
|
|
)
|
|
else
|
|
(
|
|
-- Get average railing/upright vert-cluster radii:
|
|
if (railingLines.count != 0) do
|
|
(
|
|
local itemCount = 0
|
|
for lineList in railingLines do
|
|
(
|
|
for item in lineList do
|
|
(
|
|
itemCount += 1
|
|
railingRadius += item.radius
|
|
)
|
|
)
|
|
railingRadius /= itemCount
|
|
)
|
|
|
|
-- Get average upright vert-cluster radius:
|
|
if (uprightLines.count != 0) do
|
|
(
|
|
local itemCount = 0
|
|
for lineList in uprightLines do
|
|
(
|
|
for item in lineList do
|
|
(
|
|
itemCount += 1
|
|
uprightRadius += item.radius
|
|
)
|
|
)
|
|
uprightRadius /= itemCount
|
|
)
|
|
)
|
|
|
|
-- Optimise out clusters that don't change line's direction:
|
|
(
|
|
fn optimiseLine clusterList =
|
|
(
|
|
local newClusterList = #(clusterList[1])
|
|
|
|
local prevPos = clusterList[1].pos
|
|
|
|
local clusterCount = clusterList.count
|
|
for n = 2 to clusterCount do
|
|
(
|
|
if (n == clusterCount) then
|
|
(
|
|
append newClusterList clusterList[n]
|
|
)
|
|
else
|
|
(
|
|
local thisCluster = clusterList[n]
|
|
local nextCluster = clusterList[n + 1]
|
|
|
|
local thisDir = normalize (thisCluster.pos - prevPos)
|
|
local nextDir = normalize (nextCluster.pos - prevPos)
|
|
|
|
-- Only add clusters if the next one is in a different direction:
|
|
if ((distance thisDir nextDir) > 0.001) do
|
|
(
|
|
append newClusterList thisCluster
|
|
prevPos = thisCluster.pos
|
|
)
|
|
)
|
|
)
|
|
|
|
return newClusterList
|
|
)
|
|
railingLines = for clusterList in railingLines collect (optimiseLine clusterList)
|
|
uprightLines = for clusterList in uprightLines collect (optimiseLine clusterList)
|
|
)
|
|
|
|
local newLodObj
|
|
|
|
-- Generate Lod-object:
|
|
pushPrompt "Generating object..."
|
|
if (railingLines.count != 0) or (uprightLines.count != 0) do
|
|
(
|
|
local lodName = obj.name + "_LOD"
|
|
if ((getNodeByName lodName) != undefined) do
|
|
(
|
|
lodName = uniqueName lodName
|
|
)
|
|
|
|
-- These cross-poly faces will be given vertical normals, to fix their lighting:
|
|
local crossPolys = #{}
|
|
|
|
local shapesList = #()
|
|
append shapesList (dataPair lines:railingLines crossSectVals:(dataPair name:railShape radius:railingRadius))
|
|
append shapesList (dataPair lines:uprightLines crossSectVals:(dataPair name:upShape radius:uprightRadius))
|
|
|
|
for shapeNum = 1 to shapesList.count where (shapesList[shapeNum].lines.count != 0) do
|
|
(
|
|
local crossSectName
|
|
|
|
local isRailShape = (shapeNum == 1)
|
|
local splineList = shapesList[shapeNum]
|
|
local tempSplineObj = SplineShape pos:obj.pos name:(lodName + "_TEMP")
|
|
|
|
local lineNum = 0
|
|
for thisLine in splineList.lines where (thisLine.count > 1) do
|
|
(
|
|
addNewSpline tempSplineObj
|
|
lineNum += 1
|
|
|
|
for cluster in thisLine do
|
|
(
|
|
addKnot tempSplineObj lineNum #corner #line cluster.pos
|
|
)
|
|
|
|
if (thisLine.count > 3) and (thisLine[1].pos == thisLine[thisLine.count].pos) do
|
|
(
|
|
close tempSplineObj lineNum
|
|
)
|
|
)
|
|
updateShape tempSplineObj
|
|
|
|
-- Add cross-section and use it with Sweep modifier:
|
|
if not returnSpline do
|
|
(
|
|
crossSectName = splineList.crossSectVals.name
|
|
local sectionSpline = SplineShape()
|
|
(
|
|
local crossSectLines = getCrossSect crossSectName splineList.crossSectVals.radius doDouble:doubleGeom
|
|
local sectionMult = if isRailShape then [1,heightMult,1] else [1,1,1]
|
|
|
|
for splineNum = 1 to crossSectLines.count do
|
|
(
|
|
addNewSpline sectionSpline
|
|
|
|
local thisSpline = crossSectLines[splineNum]
|
|
|
|
for pos in thisSpline do
|
|
(
|
|
addKnot sectionSpline splineNum #corner #line (pos * sectionMult)
|
|
)
|
|
|
|
if (thisSpline.count > 3) and (thisSpline[1] == thisSpline[thisSpline.count]) do
|
|
(
|
|
close sectionSpline splineNum
|
|
)
|
|
)
|
|
|
|
updateShape sectionSpline
|
|
)
|
|
|
|
-- Apply sweep-modifier:
|
|
(
|
|
local sweepMod = sweep CustomShape:1 SmoothPath:False SmoothSection:False GenMatIDs:False GenerateMappingCoords:True Banking:False
|
|
sweepMod.Shapes[1] = sectionSpline
|
|
addModifier tempSplineObj sweepMod
|
|
delete sectionSpline
|
|
)
|
|
|
|
-- Rotate UVs to horizontal:
|
|
(
|
|
local uvXformMod = UVW_Xform Rotation_Angle:90 Rotation_Center:1
|
|
|
|
local doubledFaces = doubleGeom and not (matchPattern crossSectName pattern:"*angle*")
|
|
if doubledFaces do
|
|
(
|
|
uvXformMod.V_Tile = 2.0
|
|
)
|
|
|
|
addModifier tempSplineObj uvXformMod
|
|
)
|
|
|
|
-- Convert extruded spline-object to Poly:
|
|
convertToPoly tempSplineObj
|
|
|
|
-- Set Artificial Ambient Occlusion (vertex-illumination) channel to black:
|
|
polyOp.setVertColor tempSplineObj artifAOChan #all Black
|
|
|
|
if doTint do
|
|
(
|
|
-- Activate tint-channel:
|
|
polyOp.setFaceColor tempSplineObj tintChan #all White
|
|
|
|
local clusterList = #()
|
|
for item in splineList.lines do (join clusterList item)
|
|
clusterList = (makeUniqueArray clusterList)
|
|
local clusterPosList = for cluster in clusterList collect cluster.pos
|
|
|
|
-- Apply cluster-tints to verts:
|
|
for vertNum = 1 to tempSplineObj.numVerts do
|
|
(
|
|
-- Find closest cluster:
|
|
local vertPos = polyOp.getVert tempSplineObj vertNum
|
|
local clusterDists = for clusterPos in clusterPosList collect (distance vertPos clusterPos)
|
|
local closestIdx = findItem clusterDists (aMin clusterDists)
|
|
|
|
-- Get vertex-colour from closest cluster:
|
|
polyOp.setMapVert tempSplineObj tintChan vertNum clusterList[closestIdx].meshTint
|
|
)
|
|
)
|
|
)
|
|
|
|
local firstAddedFace = 1
|
|
|
|
-- Make first temp-object be lod-object:
|
|
if (newLodObj == undefined) then
|
|
(
|
|
newLodObj = tempSplineObj
|
|
newLodObj.name = lodName
|
|
|
|
if returnSpline do
|
|
(
|
|
newLodObj.name = (lodName + "_spline")
|
|
)
|
|
)
|
|
else
|
|
(
|
|
firstAddedFace += newLodObj.numFaces
|
|
|
|
-- Attach temp-object into lod-object:
|
|
polyOp.attach newLodObj tempSplineObj
|
|
)
|
|
|
|
-- Make note of cross-poly faces:
|
|
if (crossSectName != undefined) and (matchPattern crossSectName pattern:"*cross*") do
|
|
(
|
|
crossPolys += #{firstAddedFace..newLodObj.numFaces}
|
|
)
|
|
)
|
|
|
|
-- Final object-setup:
|
|
if not returnSpline do
|
|
(
|
|
-- Point normals for cross-polys upwards:
|
|
if verticalNorms and (crossPolys.numberSet != 0) do
|
|
(
|
|
if (getCommandPanelTaskMode() != #modify) do (setCommandPanelTaskMode #modify)
|
|
local normalMod = Edit_Normals selectBy:3
|
|
addModifier newLodObj normalMod
|
|
modPanel.setCurrentObject normalMod ui:True
|
|
|
|
-- Convert face-list to normals-list:
|
|
local selNorms = #{}
|
|
normalMod.ConvertFaceSelection crossPolys selNorms
|
|
|
|
for n = selNorms do
|
|
(
|
|
normalMod.SetNormalExplicit n
|
|
normalMod.SetNormal n [0,0,1]
|
|
)
|
|
)
|
|
|
|
collapseStack newLodObj
|
|
|
|
if doubleGeom do
|
|
(
|
|
newLodObj.backFaceCull = True
|
|
)
|
|
)
|
|
)
|
|
|
|
if (isValidNode newLodObj) do
|
|
(
|
|
-- Add new object to prop-objects' container:
|
|
local objCont = RsGetObjContainer obj
|
|
if (objCont != undefined) do
|
|
(
|
|
objCont.addNodeToContent newLodObj
|
|
)
|
|
|
|
if not returnSpline do
|
|
(
|
|
-- Set up object's material:
|
|
if addMat do
|
|
(
|
|
newLodObj.material = (generateMat lodName:lodName dotint:dotint doubleMat:doubleMat)
|
|
)
|
|
|
|
-- Set new mesh as Drawable LOD:
|
|
if setAsDrawable do
|
|
(
|
|
RsLodDrawable_SetLowerDetailModel obj newLodObj
|
|
)
|
|
)
|
|
)
|
|
popPrompt()
|
|
|
|
return newLodObj
|
|
)
|
|
|
|
on rdoRailShape changed val do
|
|
(
|
|
-- Set default doubled-faces value for selected cross-section:
|
|
rdoDoubleType.state = if geoDoubleDefaults[val] then 1 else 2
|
|
)
|
|
|
|
on chkAutoSize changed state do
|
|
(
|
|
spnRailingSize.enabled = not state
|
|
spnIncreaseSize.enabled = state
|
|
)
|
|
|
|
on chkAddMat changed state do
|
|
(
|
|
chkCopyTint.enabled = state
|
|
chkDiffuseTints.enabled = state
|
|
--chkDiffuseTints.checked = state
|
|
)
|
|
|
|
fn useObjs =
|
|
(
|
|
local retVal = for obj in selection where ((isKindOf obj Editable_Mesh) or (isKindOf obj Editable_Poly)) collect obj
|
|
|
|
if (retVal.count == 0) do
|
|
(
|
|
messageBox "No Editable Poly/Mesh objects are selected, nothing to do!\n\n(Rail Lodder only works on these object-classes)" title:"Rail Lodder: Nothing to do"
|
|
)
|
|
|
|
return retVal
|
|
)
|
|
|
|
-- Generate railing lod-meshes:
|
|
on btnGenRailLodMesh pressed do
|
|
(
|
|
local selObjs = useObjs()
|
|
if (selObjs.count == 0) do (return False)
|
|
|
|
local clusterSize = spnClusterSize.value
|
|
local railSize = if chkAutoSize.checked then undefined else spnRailingSize.value
|
|
local railSizeMult = ((spnIncreaseSize.value + 100) * 0.01)
|
|
local heightMult = spnRailingRatio.value
|
|
local setDrawable = chkDrawableLod.state
|
|
local addMat = chkAddMat.checked
|
|
local copyTint = chkCopyTint.checked
|
|
local tintFromDiffuse = chkDiffuseTints.checked
|
|
local doubleGeom = (rdoDoubleType.state == 1)
|
|
local doubleMat = (rdoDoubleType.state == 2)
|
|
local verticalNorms = chkVertNorms.checked
|
|
|
|
local railShape = case rdoRailShape.state of
|
|
(
|
|
1:#angle
|
|
2:#plusCross
|
|
3:#xCross
|
|
4:#diamond
|
|
5:#horizontal
|
|
6:#vertical
|
|
)
|
|
local upShape = if chkUpShape.checked then railShape else #plusCross
|
|
|
|
undo "Generate Railing LODs" on
|
|
(
|
|
setWaitCursor()
|
|
|
|
local success = True
|
|
local newObjs = #()
|
|
for obj in selObjs while success do
|
|
(
|
|
local newObj = genRailingLodMesh obj maxClusterSize:clusterSize setRailingSize:railSize sizeMult:railSizeMult heightMult:heightMult \
|
|
railShape:railShape upShape:upShape setAsDrawable:setDrawable addMat:addMat copyTint:copyTint tintFromDiffuse:tintFromDiffuse \
|
|
doubleGeom:doubleGeom doubleMat:doubleMat verticalNorms:verticalNorms
|
|
|
|
if (newObj == False) then
|
|
(
|
|
success = False
|
|
)
|
|
else
|
|
(
|
|
append newObjs newObj
|
|
)
|
|
)
|
|
|
|
newObjs = for obj in newObjs where (isValidNode obj) collect obj
|
|
|
|
if success then
|
|
(
|
|
clearSelection()
|
|
select newObjs
|
|
)
|
|
else
|
|
(
|
|
delete newObjs
|
|
)
|
|
|
|
setArrowCursor()
|
|
completeRedraw()
|
|
)
|
|
)
|
|
|
|
-- Generate railing-splines:
|
|
on btnGenRailSplines pressed do
|
|
(
|
|
local selObjs = useObjs()
|
|
if (selObjs.count == 0) do (return False)
|
|
|
|
local clusterSize = spnClusterSize.value
|
|
|
|
undo "Generate Railing Splines" on
|
|
(
|
|
setWaitCursor()
|
|
|
|
local success = True
|
|
local newObjs = #()
|
|
|
|
for obj in selObjs while success do
|
|
(
|
|
local newObj = (genRailingLodMesh obj maxClusterSize:clusterSize setRailingSize:0 returnSpline:True)
|
|
|
|
if (newObj == False) then
|
|
(
|
|
success = False
|
|
)
|
|
else
|
|
(
|
|
append newObjs newObj
|
|
)
|
|
)
|
|
newObjs = for obj in newObjs where (isValidNode obj) collect obj
|
|
|
|
if success then
|
|
(
|
|
clearSelection()
|
|
select newObjs
|
|
)
|
|
else
|
|
(
|
|
delete newObjs
|
|
)
|
|
|
|
setArrowCursor()
|
|
completeRedraw()
|
|
)
|
|
)
|
|
|
|
-- Run ComboLodder script:
|
|
on btnComboLodder pressed do
|
|
(
|
|
filein (::RsConfigGetWildwestDir() + "Script/3dsMax/Maps/lodCombinerTool_UI.ms")
|
|
)
|
|
)
|
|
|
|
-- TEST BITS --
|
|
if FALSE do
|
|
(
|
|
local obj = $
|
|
local newobj = RsLodModellingRoll.genRailingLodMesh obj railShape:#plusCross --returnSpline:True
|
|
--obj.isHidden = true
|
|
--select newobj
|
|
)
|
|
--createDialog RsLodModellingRoll |