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

1579 lines
46 KiB
Plaintext
Executable File

Filein (RsConfigGetWildwestDir() + "Script/3dsMax/Maps/Materials/terrain_helperFunctions.ms")
global gRsTerrainSeamFinder = Undefined
-- RstaCompressBitArray:
-- Reduce size of bitarray to its last-used bit, to help out with memory-usage.
fn RstaCompressBitArray thisArray =
(
local newSize = 0
for val in thisArray do
(
newSize = val
)
thisArray.count = newSize
)
-- Struct for collecting terrain-seam find/fix data:
struct RstaTerrainSeam
(
verts = #{}, name = #Default, obj, chan, subChan,
fn Dispose =
(
verts.count = 0
)
)
struct RstaTerrainSeams
(
items = #(),
itemNames = #(),
vertWeights = #(),
commonBlends = #(),
terrainFaces = #{},
borderVerts = #{},
hasUberTerrain = False,
fn GetItem thisName =
(
local outVal = Undefined
local findNum = (FindItem itemNames thisName)
if (findNum != 0) do
(
outVal = items[findNum]
)
return outVal
),
fn AddItem thisName obj:Undefined chan:Undefined subChan:Undefined =
(
local outVal = (GetItem thisName)
if (outVal == Undefined) do
(
outVal = (RstaTerrainSeam name:thisName obj:obj chan:chan subChan:subChan)
Append items outVal
Append itemNames thisName
)
return outVal
),
-- Does struct store data for a specific seam-type?
fn HasItem thisName =
(
(FindItem itemNames thisName) != 0
),
-- Is fix required/requested for a specific seam-type?
fn ItemNeedsFix thisName =
(
local outVal = False
local thisItem = GetItem thisName
if (thisItem != Undefined) do
(
outVal = (thisItem.verts.numberSet != 0)
)
return outVal
),
-- Does this struct store seam-items that will require blend-weights to fix?
fn FixNeedsWeights =
(
local lookupChan = gRsTerrainSeamFinder.lookupChan
-- Looku at fix-seam items using the Lookup Colours channel
local usesLookupChan = False
for thisItem in items where (thisItem.chan == lookupChan) while (not usesLookupChan) do
(
-- Does this item have any verts flagged?
if (thisItem.verts.numberSet != 0) do
(
usesLookupChan = True
)
)
return usesLookupChan
),
-- Return verts stored for a particular seam-type name, if any:
fn GetVertsByName thisName =
(
local thisItem = GetItem thisName
if (thisItem == Undefined) then
(
return #{}
)
else
(
return thisItem.verts
)
),
-- Remove unwanted fix-items:
fn Clean =
(
-- Remove empty/unwanted items:
items = for item in items where (item.verts.numberSet != 0) collect item
itemNames = for item in items collect item.name
-- Compress bitarrays:
for item in items do
(
RstaCompressBitArray item.verts
)
return OK
),
fn Dispose =
(
for item in items do item.Dispose()
for item in vertWeights do item.count = 0
vertWeights.count = 0
terrainFaces.count = 0
borderVerts.count = 0
)
)
struct sRsTerrainSeamFinder
(
-- Terrain-colour channels:
effectsMaskChan = (RsGetChanByName #TerrainEffectsMask),
displaceMaskSubChan = 1,
curveMaskSubChan = 2,
lookupChan = (RsGetChanByName #TerrainLookup),
wetnessChan = (RsGetChanByName #TerrainWetness),
-- We can't currently test to see if texture-lookups use uncommon blends
-- between materials, so we can flag these for replacement with
-- common-blend vertexcolour-lookups.
assumeTexLookupCantBlend = True,
-- MakeUniqueVecs:
-- Extends MakeUniqueArray, now combines close-enough vector3/4 values.
fn MakeUniqueVecs vals threshold:0.001 =
(
-- Initial quick uniquification:
local outVals = MakeUniqueArray vals
-- Double-check for near-enough matches...
if (outVals.count > 1) do
(
for n = 1 to outVals.count do
(
local valA = outVals[n]
for m = outVals.count to (n + 1) by -1 do
(
if (Distance valA outVals[m] < threshold) do
(
DeleteItem outVals m
)
)
)
)
return outVals
),
-- GetMappingValsPerVert:
-- Returns lists of channel-values per geometry-vertex:
fn GetMappingValsPerVert obj chan faces: geomToMapVerts: =
(
local objOp = (RsMeshPolyOp obj)
local objGetFaceVerts = (RsGetFaceFunc obj)
local objGetMapFace = (RsGetMapFaceFunc obj)
local objGetMapVert = (objOp.GetMapVert)
local facesCount = (objOp.GetNumFaces obj)
local geomVertsCount = (objOp.GetNumVerts obj)
if (faces == Unsupplied) do
(
faces = #{1..facesCount}
)
if (faces.numberSet == 0) do return #()
-- Are we going to collect geometry-to-map-vert array?
local collectMapVerts = (IsKindOf geomToMapVerts Array)
if collectMapVerts do
(
Join geomToMapVerts (for n = 1 to geomVertsCount collect #())
)
local mapValsPerVert = for n = 1 to geomVertsCount collect #()
for faceNum in faces do
(
-- Get face's geometry/mapping verts:
local faceGeomVerts = (objGetFaceVerts obj faceNum)
local faceMapVerts = (objGetMapFace obj chan faceNum)
-- Collect values from each face-corner:
for vertIdx = 1 to faceGeomVerts.count do
(
local mapVert = faceMapVerts[vertIdx]
local geomVert = faceGeomVerts[vertIdx]
-- Collect face-corner's mask-colour:
local vertVal = (objGetMapVert obj chan mapVert)
Append mapValsPerVert[geomVert] vertVal
-- Collect geometry-to-map vert:
if collectMapVerts do
(
AppendIfUnique geomToMapVerts[geomVert] mapVert
)
)
)
return mapValsPerVert
),
-- Get per-vertex texmap-weight data for obj:
fn GetObjWeights obj terrMatInfos: =
(
PushPrompt "Collecting per-vertex weight data..."
-- Get weights per face-corner:
if (terrMatInfos == Unsupplied) do
(
terrMatInfos = (gRsTerrainHelpers.GetObjTerrainFaceData obj)
--terrMatInfos = for item in terrMatInfos where (item.typeDef.isUberTerrain) collect item
)
if (terrMatInfos.count == 0) do return #()
local objOp = (RsMeshPolyOp obj)
local objGetFaceVerts = (RsGetFaceFunc obj)
-- Build per-vertex list:
local geomVertsCount = (objOp.GetNumVerts obj)
local geomVertLookupWeights = for n = 1 to geomVertsCount collect #()
-- Colect face-corner texmap-weights per geometry-vert:
for terrMatInfo in terrMatInfos do
(
-- Get terrain-material descriptor's faces-list:
local matFaces = (terrMatInfo.GetMatFaces())
-- Get texmap-weighting for each faces' vertices:
-- (ignore texture-lookup, only pay attention to vertex-lookup)
local matFaceWeights = terrMatInfo.GetFaceVertTexWeights ignoreBitmaps:True
-- Get number of diffuse-texture slots in material
local diffuseCount = terrMatInfo.diffSlotNums.count
-- Convert face-vertex texmap-weights, texmap-indices, and lookup-values to per-face arrays:
local faceIdx = 0
for faceNum in matFaces do
(
-- Get face's geometry-verts:
local faceGeomVerts = objGetFaceVerts obj faceNum
-- Get corresponding face-corner texmap-weights:
faceIdx += 1
local thisFaceWeights = matFaceWeights[faceIdx]
-- Face's vert-weights list will be empty if vertex-colour lookup-channel was inactive:
if (thisFaceWeights.count != 0) do
(
-- Convert texmap-weight arrays to Point4 vector-values, to allow for quick distance-comparison:
for vertIdx = 1 to faceGeomVerts.count do
(
-- Convert this face-vert's weights to Point4, to allow for quick distance-tests:
local vertWeights = thisFaceWeights[vertIdx]
local weightsVec = [0,0,0,0]
for n = 1 to vertWeights.count do
(
weightsVec[n] = vertWeights[n]
)
vertWeights.count = 0
-- Collect converted Point4:
local geomVert = faceGeomVerts[vertIdx]
Append geomVertLookupWeights[geomVert] weightsVec
)
)
)
)
PopPrompt()
return geomVertLookupWeights
),
-- Collects texmap-comparison info between two Terrain Materials:
fn GetBlendCompareInfo terrInfoA terrInfoB =
(
-- Get diffuse-paths and material-Ids:
local diffPathsA = terrInfoA.diffuseTexPaths
local diffPathsB = terrInfoB.diffuseTexPaths
local matNumA = terrInfoA.matId
local matNumB = terrInfoB.matId
local outInfo = DataPair matIds:#(matNumA, matNumB) texmaps:#()
local texmapsInfo = outInfo.texmaps
-- Find lowest diffuse-paths count:
local compareCount = (Amin diffPathsA.count diffPathsB.count)
for texIdx = 1 to compareCount do
(
-- Get UV-channels used for this texmap-index:
local texUVchanA = (terrInfoA.GetTexmapUVchan texIdx)
local texUVchanB = (terrInfoB.GetTexmapUVchan texIdx)
-- Get texmap-paths for this texmap-index:
local diffPathA = diffPathsA[texIdx]
local diffPathB = diffPathsB[texIdx]
Append texmapsInfo (DataPair paths:#(diffPathA, diffPathB) chans:#(texUVchanA, texUVchanB))
)
return outInfo
),
-- Calculates common-blends between two Terrain Material descriptors:
fn GetCommonBlends terrInfoA terrInfoB =
(
local commonTexIdxs = #{}
local compareInfo = (this.GetBlendCompareInfo terrInfoA terrInfoB)
-- Find texmap-paths/indexes that are common between these two terrain-materials:
local texmapsInfo = compareInfo.texmaps
for texIdx = 1 to texmapsInfo.count do
(
-- Get materials' data for this texmap-index:
local texIdxInfo = texmapsInfo[texIdx]
-- Do these materials use the same UV-channel for this texmap-index?
local texUVchanA = texIdxInfo.chans[1]
local texUVchanB = texIdxInfo.chans[2]
local isMatchingTexmap = (texUVchanA == texUVchanB)
-- Do these materials have the same texmap-path for this texmap-index?
if isMatchingTexmap do
(
local diffPathA = texIdxInfo.paths[1]
local diffPathB = texIdxInfo.paths[2]
isMatchingTexmap = (PathConfig.PathsResolveEquivalent diffPathA diffPathB)
)
commonTexIdxs[texIdx] = isMatchingTexmap
)
return commonTexIdxs
),
------------------------------------------------------------------------------------------------------
-- Find verts using two-layer terrain-shader:
-- these should only have red/blue blend-colours
------------------------------------------------------------------------------------------------------
fn GetTwoLayerVerts obj terrMatInfos: =
(
-- Get weights per face-corner:
if (terrMatInfos == Unsupplied) do
(
terrMatInfos = (gRsTerrainHelpers.GetObjTerrainFaceData obj)
terrMatInfos = for item in terrMatInfos where (item.typeDef.isUberTerrain) collect item
)
local objOp = (RsMeshPolyOp obj)
local twoLayerFaces = #{}
for item in terrMatInfos where (item.diffSlotNums.count == 2) do
(
Join twoLayerFaces (item.GetMatFaces())
)
local twoLayerVerts = (objOp.GetVertsUsingFace obj twoLayerFaces)
RstaCompressBitArray twoLayerVerts
return twoLayerVerts
),
------------------------------------------------------------------------------------------------------
-- FindSeams:
-- Finds vertices that have seamed or invalid terrain-paint data
------------------------------------------------------------------------------------------------------
fn FindProblems obj getFixData:False justSelVerts:False =
(
if (not IsValidNode obj) do return Undefined
if not ((IsKindOf obj Editable_Poly) or (IsKindOf obj Editable_Mesh)) do return Undefined
if (obj.modifiers.count != 0) do return Undefined
-- Set up error-collection struct for object:
local seamInfo = RstaTerrainSeams()
local objOp = (RsMeshPolyOp obj)
local facesCount = (objOp.GetNumFaces obj)
local geomVertsCount = (objOp.GetNumVerts obj)
if (facesCount == 0) or (geomVertsCount == 0) do return seamInfo
-- Get object's terrain_uber material-descriptors:
local terrMatInfos = (gRsTerrainHelpers.GetObjTerrainFaceData obj)
--terrMatInfos = for item in terrMatInfos where (item.typeDef.isUberTerrain) collect item
-- Does this mesh use Terrain_Uber shaders?
seamInfo.hasUberTerrain = False
for item in terrMatInfos while (not seamInfo.hasUberTerrain) do
seamInfo.hasUberTerrain = item.typeDef.isUberTerrain
-- Abort if no terrain-materials are used on this object:
if (terrMatInfos.count == 0) do
return seamInfo
-- Build list of all terrain-material faces, and material-border-verts:
local terrainFaces = #{}
for terrMatInfo in terrMatInfos do
(
Join terrainFaces (terrMatInfo.GetMatFaces())
)
local selVerts = #{}
if justSelVerts do
(
-- Get selected verts on obj
-- This is converted from other subobject selections, if currently non-vertex
local objVertSel = RSTAGeometry_GetSelectionDetails objs:#(obj) selType:#Vertex
selVerts = objVertSel[1].selSubObjs
)
local maskChan = Undefined
local tintMaskSubChan = Undefined
local lookupMaskSubChan = Undefined
-- Process tint/lookup mask-channel:
(
local tintMaskVerts = Undefined
local lookupMaskSeamVerts = Undefined
-- Bitarrays for finding mask-seams
if seamInfo.hasUberTerrain then
(
maskChan = (RsGetChanByName #TerrainMask)
tintMaskSubChan = 1
lookupMaskSubChan = 2
tintMaskVerts = (seamInfo.AddItem #TintMask obj:obj chan:maskChan subChan:tintMaskSubChan).verts
lookupMaskSeamVerts = (seamInfo.AddItem #LookupMask obj:obj chan:maskChan subChan:lookupMaskSubChan).verts
)
else
(
maskChan = (RsGetChanByName #TerrainCbMask)
lookupMaskSeamVerts = (seamInfo.AddItem #LookupMask obj:obj chan:maskChan).verts
)
local hasVertLookupVerts = #{}
local hasTexLookupVerts = #{}
-- Is mask-channel active?
local hasMaskChan = (objOp.GetMapSupport obj maskChan)
if (hasMaskChan) do
(
PushPrompt "Finding Tint/Lookup Mask seams..."
-- Initialise sizes of bitarrays:
local maskSeamArrays = for item in \
#(tintMaskVerts, hasVertLookupVerts, hasTexLookupVerts, lookupMaskSeamVerts) \
where (item != Undefined) collect item
for thisArray in maskSeamArrays do
thisArray.count = geomVertsCount
-- Find geometry-verts with multiple mask-values:
local geomVertVals = GetMappingValsPerVert obj maskChan faces:terrainFaces
for geomVert = 1 to geomVertsCount where ((not justSelVerts) or selVerts[geomVert]) do
(
local maskVals = geomVertVals[geomVert]
-- Ignore verts where no data was collected:
if (maskVals.count != 0) do
(
-- This vertex is probably a seam if it has multiple mask-values:
maskVals = (MakeUniqueVecs maskVals)
-- Process tint-mask value(s)
if seamInfo.hasUberTerrain and (tintMaskVerts != Undefined) do
(
local maxTintMask = Undefined
if (maskVals.count == 1) then
(
maxTintMask = maskVals[1][tintMaskSubChan]
)
else
(
local vals = for val in maskVals collect val[tintMaskSubChan]
maxTintMask = (Amax vals)
vals.count = 0
)
-- Does vert have non-zero tint-mask value?
-- (these should all be set to zero, i.e. should completely use Texture Tint)
if (maxTintMask > 0.001) do
tintMaskVerts[geomVert] = True
)
-- Process lookup-mask value(s)
(
local minLookupMask
local maxLookupMask
if (maskVals.count == 1) then
(
local val = maskVals[1]
if seamInfo.hasUberTerrain then
minLookupMask = val[lookupMaskSubChan]
else
minLookupMask = Amin val[1] val[2] val[3]
maxLookupMask = minLookupMask
)
else
(
local vals = MakeUniqueArray \
(
for val in maskVals collect
(
if seamInfo.hasUberTerrain then
val[lookupMaskSubChan]
else
(Amin val[1] val[2] val[3])
)
)
minLookupMask = (Amin vals)
maxLookupMask = (Amax vals)
-- Is this a lookup-mask seam?
if (vals.count > 1) do
(
lookupMaskSeamVerts[geomVert] = True
)
vals.count = 0
)
-- Does vert have non-zero lookup-mask?
-- (i.e. does it use any Vertex Colour lookup)
if (maxLookupMask > 0.001) do
(
hasVertLookupVerts[geomVert] = True
)
-- Does vert have non-full lookup-mask?
-- (i.e. does it use any Texture lookup)
if (minLookupMask < 0.999) do
(
hasTexLookupVerts[geomVert] = True
)
)
-- Dipose of array:
maskVals.count = 0
)
)
geomVertVals.count = 0
-- Reduce bitarrays down to minimal-required sizes:
-- (i.e. set to size to max-used bit)
for thisArray in maskSeamArrays do
RstaCompressBitArray thisArray
PopPrompt()
)
)
-- (end of tint/lookup mask channel processing)
-- Process effects-mask channel
if seamInfo.hasUberTerrain do
(
-- Bitarrays for finding mask-seams:
local curvMaskSeamVerts = (seamInfo.AddItem #CurveMask obj:obj chan:effectsMaskChan subChan:curveMaskSubChan).verts
local dispMaskSeamVerts = (seamInfo.AddItem #DisplaceMask obj:obj chan:effectsMaskChan subChan:displaceMaskSubChan).verts
-- Is mask-channel active?
local hasEffectsMaskChan = (objOp.GetMapSupport obj effectsMaskChan)
if (hasEffectsMaskChan) do
(
PushPrompt "Finding Effects Mask seams..."
-- Initialise sizes of bitarrays:
local maskSeamArrays = #(curvMaskSeamVerts, dispMaskSeamVerts)
for thisArray in maskSeamArrays do
(
thisArray.count = geomVertsCount
)
-- Find geometry-verts with multiple mask-values:
local geomVertVals = GetMappingValsPerVert obj effectsMaskChan faces:terrainFaces
for geomVert = 1 to geomVertsCount where ((not justSelVerts) or selVerts[geomVert]) do
(
local maskVals = geomVertVals[geomVert]
-- Ignore verts where no data was collected:
if (maskVals.count != 0) do
(
-- This vertex is probably a seam if it has multiple mask-values:
maskVals = (MakeUniqueVecs maskVals)
-- Process curvature-mask value(s)
if (maskVals.count > 1) do
(
local curveMaskVals = MakeUniqueArray (for val in maskVals collect val[curveMaskSubChan])
-- Is this a displacement-mask seam?
if (curveMaskVals.count > 1) do
(
curvMaskSeamVerts[geomVert] = True
)
)
-- Process displacement-mask value(s)
if (maskVals.count > 1) do
(
local displaceMaskVals = MakeUniqueArray (for val in maskVals collect val[displaceMaskSubChan])
-- Is this a displacement-mask seam?
if (displaceMaskVals.count > 1) do
(
dispMaskSeamVerts[geomVert] = True
)
)
-- Dipose of array:
maskVals.count = 0
)
)
geomVertVals.count = 0
-- Reduce bitarrays down to minimal-required sizes:
-- (i.e. set to size to max-used bit)
for thisArray in maskSeamArrays do
(
RstaCompressBitArray thisArray
)
PopPrompt()
)
)
-- (end of effects-mask channel processing)
-- Process wetness-channel
if seamInfo.hasUberTerrain do
(
-- Is wetness-channel active?
local hasWetnessChan = (objOp.GetMapSupport obj wetnessChan)
if (hasWetnessChan) do
(
PushPrompt "Finding Wetness seams..."
local wetnessSeamVerts = (seamInfo.AddItem #WetnessSeam obj:obj chan:wetnessChan).verts
wetnessSeamVerts.count = geomVertsCount
-- Find geometry-verts with multiple wetness-values:
local geomVertVals = GetMappingValsPerVert obj wetnessChan faces:terrainFaces
for geomVert = 1 to geomVertsCount where ((not justSelVerts) or selVerts[geomVert]) do
(
local wetVals = geomVertVals[geomVert]
if (wetVals.count > 1) do
(
wetVals = MakeUniqueArray (for val in wetVals collect val[1])
if (wetVals.count > 1) do
(
wetnessSeamVerts[geomVert] = True
)
)
)
geomVertVals.count = 0
RstaCompressBitArray wetnessSeamVerts
PopPrompt()
)
)
-- (end of wetness channel processing)
-- Collect lookup-weights for each terrain-material
local geomVertLookupWeights = this.GetObjWeights obj terrMatInfos:terrMatInfos
-- Find material-border verts:
(
PushPrompt "Finding material-border verts..."
-- We'll find material-border verts:
local matBorderVerts = #()
for terrMatInfo in terrMatInfos do
(
-- Get terrain-material descriptor's faces-list:
local matFaces = (terrMatInfo.GetMatFaces())
-- Find verts shared between this material and other terrain-materials:
local matVerts = (objOp.GetVertsUsingFace obj matFaces)
local nonMatVerts = (objOp.GetVertsUsingFace obj (terrainFaces - matFaces))
local thisMatBorderVerts = (matVerts * nonMatVerts)
-- Collect indices for currently-selected verts:
Append matBorderVerts thisMatBorderVerts
)
for thisArray in matBorderVerts do
(
RstaCompressBitArray thisArray
)
PopPrompt()
)
-- (end of lookup-weight collection)
-- Find all texture-weight seams:
(
PushPrompt "Finding texmap-weight seams..."
local weightSeamVerts = (seamInfo.AddItem #WeightSeam obj:obj chan:lookupChan).verts
weightSeamVerts.count = geomVertsCount
for geomVert = 1 to geomVertsCount where ((not justSelVerts) or selVerts[geomVert]) do
(
local weights = MakeUniqueVecs geomVertLookupWeights[geomVert]
-- Is there more than one vertex-weight in use here?
if (weights.count > 1) do
(
weightSeamVerts[geomVert] = True
)
)
RstaCompressBitArray weightSeamVerts
PopPrompt()
)
-- (end of weight-seam processing)
-- Find material-border (i.e. non-common-blend seams)
(
PushPrompt "Finding blend-seams..."
local blendSeamVerts = (seamInfo.AddItem #BlendSeam obj:obj chan:lookupChan).verts
blendSeamVerts.count = geomVertsCount
-- Collect common-blend info, if requested:
local commonsPerVert = #()
if getFixData do
(
commonsPerVert.count = geomVertsCount
)
-- Test verts on borders between each matId-descriptor:
for matIdxA = 1 to (terrMatInfos.Count - 1) do
(
-- Get info for first comparison-material:
local terrInfoA = terrMatInfos[matIdxA]
for matIdxB = (matIdxA + 1) to terrMatInfos.count do
(
-- Find verts that are shared by compared matids:
local betweenMatVerts = (matBorderVerts[matIdxA] * matBorderVerts[matIdxB])
-- Skip comparison if these matids don't have any shared verts:
if (betweenMatVerts.numberSet != 0) do
(
-- Get info for second comparison-material:
local terrInfoB = terrMatInfos[matIdxB]
-- Get bitarray of common-blend texture-indices:
local commonTexIdxs = (this.GetCommonBlends terrInfoA terrInfoB)
-- Do these terrain-materials have any common texmaps?
local hasCommonTex = (commonTexIdxs.numberSet != 0)
-- Test verts that haven't already been marked as being seams:
if (not hasCommonTex) then
(
-- Consider all border-verts to be a blend-seam if they have no common blend:
Join blendSeamVerts betweenMatVerts
)
else
(
-- Create multiplier-vector, for fixer to use:
local commonTexVec = [0,0,0,0]
for texIdx in commonTexIdxs do
(
commonTexVec[texIdx] = 1.0
)
local uncommonTexIdxs = (-commonTexIdxs) as Array
for geomVert in betweenMatVerts where ((not justSelVerts) or selVerts[geomVert]) do
(
local isBlendSeamVert = False
local checkForVertSeam = True
if hasMaskChan and hasTexLookupVerts[geomVert] do
(
-- We can't validate common blends for texture-lookup, so we can assume it has a seam:
if assumeTexLookupCantBlend then
(
isBlendSeamVert = True
)
else
(
-- Only check vertex-colours if vert is not fully-masked
checkForVertSeam = hasVertLookupVerts[geomVert]
)
)
if checkForVertSeam and (not isBlendSeamVert) do
(
local vertWeights = MakeUniqueVecs geomVertLookupWeights[geomVert]
-- Find non-zero weights for non-common texture-ids:
local onlyCommon = True
for weights in vertWeights while onlyCommon do
(
for texIdx in uncommonTexIdxs while onlyCommon do
(
if (weights[texIdx] > 0.001) do
(
onlyCommon = False
)
)
)
-- Mark vert as being a seam if it uses any uncommon blends:
if (not onlyCommon) do
(
isBlendSeamVert = True
)
)
if isBlendSeamVert do
(
blendSeamVerts[geomVert] = True
-- Store common-blends multiplier for vert:
if getFixData do
(
for vertNum in betweenMatVerts do
(
commonsPerVert[vertNum] = commonTexVec
)
)
)
)
)
)
)
)
RstaCompressBitArray blendSeamVerts
-- Don't include common-blend seams in generic weight-seams list, as they'll be fixed separately:
weightSeamVerts -= blendSeamVerts
PopPrompt()
)
-- Find verts whose texmap-weights don't add up to 1.0
-- and two-layer-shader verts with lookup colours for more than two blends
(
PushPrompt "Finding unbalanced vert-blends..."
-- Find verts using two-layer terrain-shader:
-- (these should only have red/green blend-colours)
local twoLayerVerts = GetTwoLayerVerts obj terrMatInfos:terrMatInfos
local hasTwoLayer = (twoLayerVerts.numberSet != 0)
if hasTwoLayer do
(
local badTwoLayerVerts = (seamInfo.AddItem #BadTwoLayer obj:obj chan:lookupChan).verts
badTwoLayerVerts.count = geomVertsCount
)
local badBlendSumVerts = (seamInfo.AddItem #BadBlendSum obj:obj chan:lookupChan).verts
badBlendSumVerts.count = geomVertsCount
for geomVert = 1 to geomVertsCount where ((not justSelVerts) or selVerts[geomVert]) do
(
local blendsOnVert = MakeUniqueVecs geomVertLookupWeights[geomVert]
local isTwoLayerVert = (hasTwoLayer and twoLayerVerts[geomVert])
local isBadBlendSum = False
local isBadTwoLayer = False
for weights in blendsOnVert do
(
-- Find two-layer lookups with blue/black values
if isTwoLayerVert do
(
for n = 3 to 4 do
(
if (weights[n] != 0) do
(
isBadTwoLayer = True
isBadBlendSum = True
)
)
)
-- Validate sum of rgb values
local weightSum = 0.0
local texCount = if isTwoLayerVert then 2 else 4
for n = 1 to texCount do
(
weightSum += weights[n]
)
if isTwoLayerVert then
(
-- Two-layer red/green must sum to 1.0
if (weightSum < 0.999) or (weightSum > 1.001) do
(
isBadBlendSum = True
)
)
else
(
-- Four-layer values shouldn't sum to more than 1.0
if (weightSum > 1.001) do
(
isBadBlendSum = True
)
)
)
if isBadBlendSum do
(
badBlendSumVerts[geomVert] = True
)
if isBadTwoLayer do
(
badTwoLayerVerts[geomVert] = True
)
)
-- Resize bitarrays down to last-required bit
RstaCompressBitArray badBlendSumVerts
if hasTwoLayer do
(
RstaCompressBitArray badTwoLayerVerts
)
PopPrompt()
)
-- Remove unused items:
seamInfo.Clean()
------------------------------------------------------
-- STORE DATA FOR FIX:
------------------------------------------------------
-- Store terrain-faces list, if required/requested:
if getFixData and (seamInfo.items.count != 0) then
(
-- Store terrain-shader faces:
(
seamInfo.terrainFaces = terrainFaces
)
-- Store material-border edges:
(
--local objOpenEdges = (objOp.GetOpenEdges obj)
--local borderVerts = (objOp.GetVertsUsingEdge obj objOpenEdges)
--objOpenEdges.count = 0
local borderVerts = #{}
for thisArray in matBorderVerts do
(
Join borderVerts thisArray
)
RstaCompressBitArray borderVerts
seamInfo.borderVerts = borderVerts
)
)
else
(
terrainFaces.count = 0
)
-- Store per-vertex texmap-weights, if required/requested:
if getFixData and seamInfo.FixNeedsWeights() then
(
seamInfo.vertWeights = geomVertLookupWeights
)
else
(
-- Dispose of unneeded weight-arrays:
for item in geomVertLookupWeights do
(
item.count = 0
)
geomVertLookupWeights.count = 0
)
-- Store per-vertex common-blends, if required/requested:
if getFixData and (seamInfo.ItemNeedsFix #BlendSeam) then
(
seamInfo.commonBlends = commonsPerVert
)
else
(
-- Dispose of unneeded blend-arrays:
commonsPerVert.count = 0
)
-- Dispose of arrays:
for item in matBorderVerts do
(
item.count = 0
)
return seamInfo
),
fn FindProblemVerts obj =
(
local seamInfo = gRsTerrainSeamFinder.FindProblems obj
if (not IsKindOf seamInfo RstaTerrainSeams) do return #{}
local verts = #{}
for item in seamInfo.items do
(
Join verts item.verts
)
return verts
),
------------------------------------------------------------------------------------------------------
-- FixSeams:
-- Finds and fixes (where possible) seams in terrain-paint channels
------------------------------------------------------------------------------------------------------
fn FixObjProblems obj fixNames: justSelVerts:False =
(
-- Collect current seam-error data for object:
local seamInfo = this.FindProblems obj getFixData:True justSelVerts:justSelVerts
if (not IsKindOf seamInfo RstaTerrainSeams) do
return False
-- Only fix requested seam-types, if specified:
if (fixNames != Unsupplied) do
(
for item in seamInfo.items do
(
if (FindItem fixNames item.name == 0) do
(
item.verts.count = 0
)
)
)
-- Clear irrelevant/unwanted items:
seamInfo.Clean()
-- Abort if no fix-items were found, or remain:
if (seamInfo.items.count == 0) do
return OK
local objOp = (RsMeshPolyOp obj)
local objGetFaceVerts = (RsGetFaceFunc obj)
local objGetMapFace = (RsGetMapFaceFunc obj)
local objSetMapVert = (objOp.SetMapVert)
local maskChan = if seamInfo.hasUberTerrain then (RsGetChanByName #TerrainMask) else (RsGetChanByName #TerrainCbMask)
local lookupMaskSubChan = if seamInfo.hasUberTerrain then 2 else Undeined
-- Make sure we have corresponding lookup-mask edits queued for all lookup-edits:
(
-- Will we be fixing texmap-lookup seams?
local lookupFixVerts = #{}
for seamName in #(#WeightSeam, #BlendSeam) do
(
local seamVerts = (seamInfo.GetVertsByName seamName)
Join lookupFixVerts seamVerts
)
if (lookupFixVerts.numberSet != 0) do
(
-- Get/create lookup-mask edit:
local lookupMaskFix = seamInfo.GetItem #LookupMask
if (lookupMaskFix == Undefined) do
lookupMaskFix = (seamInfo.AddItem #LookupMask chan:maskChan subChan:lookupMaskSubChan)
-- These verts will now all be modified to show Vertexcolour Lookup:
Join lookupMaskFix.verts lookupFixVerts
)
lookupFixVerts.count = 0
)
-- Perform changes on copy of node's baseObject, to simplify undo:
undo off
(
local editObj = (Copy obj.baseObject)
if (objOp == meshOp) do
(
editObj = editObj.mesh
)
local facesCount = (objOp.GetNumFaces editObj)
local geomVertsCount = (objOp.GetNumVerts editObj)
-- Fix tint/lookup mask seams:
local maskFixes = for thisName in #(#TintMask, #LookupMask) collect (seamInfo.GetItem thisName)
maskFixes = for item in maskFixes where (item != Undefined) collect item
if (maskFixes.count != 0) do
(
PushPrompt "Fixing Terrain Mask seams..."
local maskChan = (seamInfo.GetItem #LookupMask).chan
local tintMaskSubChan = Undefined
if (seamInfo.GetItem #TintMask) != Undefined do
tintMaskSubChan = (seamInfo.GetItem #TintMask).subChan
local lookupMaskSubChan = (seamInfo.GetItem #LookupMask).subChan
local usesSubChans = (lookupMaskSubChan != Undefined)
-- Make sure verts aren't inappropriately welded:
RsDecompressVertChan editObj maskChan
-- Get verts that need to be fixed on Mask channel:
local fixGeomVerts = #{}
for item in maskFixes do
(
fixGeomVerts += item.verts
)
local fixFaces = (objOp.GetFacesUsingVert editObj fixGeomVerts) * (seamInfo.terrainFaces)
local tintMaskFixVerts = (seamInfo.GetVertsByName #TintMask)
local lookupMaskFixVerts = (seamInfo.GetVertsByName #LookupMask)
-- Get map-verts/colours per geometry-vert:
local geomToMapVerts = #()
local perVertVals = GetMappingValsPerVert editObj maskChan faces:fixFaces geomToMapVerts:geomToMapVerts
-- Which verts are on the edges of materials?
local borderVerts = seamInfo.borderVerts
-- Set average colour for each mask-seam vert:
for geomVert in fixGeomVerts do
(
local mapVerts = geomToMapVerts[geomVert]
local vertVals = perVertVals[geomVert]
-- Which fixes need to be made here?
local fixTint = tintMaskFixVerts[geomVert]
local fixLookup = lookupMaskFixVerts[geomVert]
for vertIdx = 1 to mapVerts.count do
(
local mapVert = mapVerts[vertIdx]
local vertVal = vertVals[vertIdx]
-- Set tint-mask to 0% (i.e. Texture Tint only)
if fixTint do
(
if usesSubChans then
vertVal[tintMaskSubChan] = 0.0
else
vertVal = [0,0,0]
)
-- Set lookup-mask value to 1.0% (i.e. Vertex Tint only)
if fixLookup do
(
if usesSubChans then
vertVal[lookupMaskSubChan] = 1.0
else
vertVal = [1,1,1]
)
objSetMapVert editObj maskChan mapVert vertVal
)
)
-- Dipose of arrays:
for thisArray in #(geomToMapVerts, perVertVals) do
(
for item in thisArray do (item.count = 0)
thisArray.count = 0
)
PopPrompt()
)
-- Fix curvature/displacement effects-mask seams:
local maskFixes = for thisName in #(#CurveMask, #DisplaceMask) collect (seamInfo.GetItem thisName)
maskFixes = for item in maskFixes where (item != Undefined) collect item
if (maskFixes.count != 0) do
(
PushPrompt "Fixing Terrain Mask seams..."
-- Make sure verts aren't inappropriately welded:
RsDecompressVertChan editObj effectsMaskChan
-- Get verts that need to be fixed on Mask channel:
local fixGeomVerts = #{}
for item in maskFixes do
(
fixGeomVerts += item.verts
)
local fixFaces = (objOp.GetFacesUsingVert editObj fixGeomVerts) * (seamInfo.terrainFaces)
local curveMaskFixVerts = (seamInfo.GetVertsByName #CurveMask)
local displaceMaskFixVerts = (seamInfo.GetVertsByName #DisplaceMask)
-- Get map-verts/colours per geometry-vert:
local geomToMapVerts = #()
local perVertVals = GetMappingValsPerVert editObj effectsMaskChan faces:fixFaces geomToMapVerts:geomToMapVerts
-- Which verts are on the edges of materials?
local borderVerts = seamInfo.borderVerts
-- Set average colour for each mask-seam vert:
for geomVert in fixGeomVerts do
(
local mapVerts = geomToMapVerts[geomVert]
local vertVals = perVertVals[geomVert]
-- Which fixes need to be made here?
local fixCurve = curveMaskFixVerts[geomVert]
local fixDisplace = displaceMaskFixVerts[geomVert]
for vertIdx = 1 to mapVerts.count do
(
local mapVert = mapVerts[vertIdx]
local vertVal = vertVals[vertIdx]
-- Set as fully-masked - i.e. no curvature:
if fixCurve do
(
vertVal[curveMaskSubChan] = 1.0
)
-- Set as fully-masked - i.e. no displacement:
if fixDisplace do
(
vertVal[displaceMaskSubChan] = 1.0
)
objSetMapVert editObj effectsMaskChan mapVert vertVal
)
)
-- Dipose of arrays:
for thisArray in #(geomToMapVerts, perVertVals) do
(
for item in thisArray do (item.count = 0)
thisArray.count = 0
)
PopPrompt()
)
-- Make sure verts aren't inappropriately welded on lookup-channel, if it's going to be edited:
local fixingLookup = False
for lookupFixName in #(#WeightSeam, #BlendSeam, #BadBlendSum, #BadTwoLayer) while (not fixingLookup) do
(
fixingLookup = (seamInfo.ItemNeedsFix lookupFixName)
)
if fixingLookup do
(
RsDecompressVertChan editObj lookupChan
)
-- Fix blend-lookup seams:
local lookupDecompressed = False
if (seamInfo.ItemNeedsFix #WeightSeam) or (seamInfo.ItemNeedsFix #BlendSeam) do
(
PushPrompt "Fixing Texmap Lookup seams..."
local vertWeights = seamInfo.vertWeights
local commonsPerVert = seamInfo.commonBlends
local weightSeamVerts = (seamInfo.GetVertsByName #WeightSeam)
local blendSeamVerts = (seamInfo.GetVertsByName #BlendSeam)
-- Get combined list of verts that need to be fixed:
local fixGeomVerts = #{}
Join fixGeomVerts weightSeamVerts
Join fixGeomVerts blendSeamVerts
-- Get faces used by verts that need to be fixed:
local fixFaces = (objOp.GetFacesUsingVert editObj fixGeomVerts) * (seamInfo.terrainFaces)
-- Collect geometry-to-mapping vertex-lookup data:
local geomToMapVerts = (for n = 1 to geomVertsCount collect #())
for faceNum in fixFaces do
(
-- Get face's geometry/mapping verts:
local faceGeomVerts = (objGetFaceVerts editObj faceNum)
local faceMapVerts = (objGetMapFace editObj lookupChan faceNum)
-- Collect values from each face-corner:
for vertIdx = 1 to faceGeomVerts.count do
(
local mapVert = faceMapVerts[vertIdx]
local geomVert = faceGeomVerts[vertIdx]
AppendIfUnique geomToMapVerts[geomVert] mapVert
)
)
local BalanceTexWeights = gRsTerrainHelpers.BalanceTexWeights
local GetClrFromWeights = if seamInfo.hasUberTerrain then
RsTerrainShaderType_Uber.GetClrFromWeights
else
RsTerrainShaderType_CB.GetClrFromWeights
local zeroWeights = [0,0,0,0]
for geomVert in fixGeomVerts do
(
local newWeights = Undefined
-- Is this a material-border or mid-material vertex?
if blendSeamVerts[geomVert] then
(
-- Find average common-blend values for material-border verts:
local commonMult = commonsPerVert[geomVert]
-- We can only fix this vert if it has any Common Blends available:
if (commonMult != Undefined) do
(
local newWeights = [0,0,0,0]
local geomVertWeights = vertWeights[geomVert]
for weights in geomVertWeights do
(
newWeights += weights
)
-- Get rid of non-common weights:
newWeights *= commonMult
-- Use all common-weights by default, if none were used on this vert:
if (Distance newWeights zeroWeights < 0.001) do
(
newWeights = commonMult
)
)
)
else
(
-- Find total weight-values for mid-material verts:
local newWeights = [0,0,0,0]
local geomVertWeights = vertWeights[geomVert]
for weights in geomVertWeights do
(
newWeights += weights
)
)
if (newWeights != Undefined) do
(
-- Convert to array, balance values to add to 1.0:
local weightsArray = for n = 1 to 4 collect newWeights[n]
weightsArray = (BalanceTexWeights weightsArray)
-- Update value in source-array, to allow the following step to use this data
local newVal = [weightsArray[1], weightsArray[2], weightsArray[3], weightsArray[4]]
vertWeights[geomVert] = #(newVal)
-- Convert weights to vertex-colour, and apply to mapping-verts:
newVal = (GetClrFromWeights newVal)
local mapVerts = geomToMapVerts[geomVert]
for mapVert in mapVerts do
(
objSetMapVert editObj lookupChan mapVert newVal
)
)
)
-- Dipose of arrays:
for thisArray in #(geomToMapVerts) do
(
for item in thisArray do (item.count = 0)
thisArray.count = 0
)
PopPrompt()
)
-- Fix two-layer verts that have blends for layer 3/4,
-- and bad blend-weight sums
if (seamInfo.ItemNeedsFix #BadBlendSum) or (seamInfo.ItemNeedsFix #BadTwoLayer) do
(
local BalanceTexWeights = gRsTerrainHelpers.BalanceTexWeights
local vertWeights = seamInfo.vertWeights
-- All verts with inappropriate blend-weight sum
local badBlendSumVerts = (seamInfo.GetVertsByName #BadBlendSum)
-- Two-layer verts with blend-problems
local badTwoLayerVerts = (seamInfo.GetVertsByName #BadTwoLayer)
local hasTwoLayer = (badTwoLayerVerts.numberSet != 0)
-- Get faces used by verts that need to be fixed:
local fixVerts = (badBlendSumVerts + badTwoLayerVerts)
local fixFaces = (objOp.GetFacesUsingVert editObj fixVerts) * (seamInfo.terrainFaces)
-- Collect geometry-to-mapping vertex-lookup data:
local geomToMapVerts = (for n = 1 to geomVertsCount collect #())
for faceNum in fixFaces do
(
-- Get face's geometry/mapping verts:
local faceGeomVerts = (objGetFaceVerts editObj faceNum)
local faceMapVerts = (objGetMapFace editObj lookupChan faceNum)
-- Collect values from each face-corner:
for vertIdx = 1 to faceGeomVerts.count do
(
local mapVert = faceMapVerts[vertIdx]
local geomVert = faceGeomVerts[vertIdx]
AppendIfUnique geomToMapVerts[geomVert] mapVert
)
)
fixFaces.count = 0
for geomVert in fixVerts do
(
-- Find total weight-values for vert:
local newWeights = [0,0,0,0]
local geomVertWeights = vertWeights[geomVert]
for weights in geomVertWeights do
(
newWeights += weights
)
-- How many texmap-weights should this vert use?
local isTwoLayerVert = (hasTwoLayer and badTwoLayerVerts[geomVert])
local texCount = if isTwoLayerVert then 2 else 4
-- Convert to weights-array, then balance values to add to 1.0:
local weightsArray = for n = 1 to texCount collect newWeights[n]
if isTwoLayerVert and (weightsArray[1] == 0) and (weightsArray[2] == 0) then
(
-- If both two-layer values are zero, default to full-blend on texmap id 1
weightsArray[1] = 1.0
)
else
(
-- Balance values to add up to 1.0
weightsArray = (BalanceTexWeights weightsArray)
)
-- Convert modified blend-weights to lookup-colour value
local newVal = [0,0,0]
for n = 1 to (if isTwoLayerVert then 2 else 3) do
(
newVal[n] = weightsArray[n]
)
-- Apply new value to mapping-verts:
local mapVerts = geomToMapVerts[geomVert]
for mapVert in mapVerts do
(
objSetMapVert editObj lookupChan mapVert newVal
)
)
-- Dipose of arrays:
for thisArray in #(geomToMapVerts) do
(
for item in thisArray do (item.count = 0)
thisArray.count = 0
)
)
-- Fix wetness seams:
if (seamInfo.ItemNeedsFix #WetnessSeam) do
(
PushPrompt "Fixing Terrain Wetness Seams..."
-- Make sure verts aren't inappropriately welded:
RsDecompressVertChan editObj wetnessChan
-- Get values used on each vert that needs to be fixed:
local fixGeomVerts = (seamInfo.GetVertsByName #WetnessSeam)
local fixFaces = (objOp.GetFacesUsingVert editObj fixGeomVerts) * (seamInfo.terrainFaces)
local geomToMapVerts = #()
local perVertVals = GetMappingValsPerVert editObj wetnessChan faces:fixFaces geomToMapVerts:geomToMapVerts
-- Set average colour for each wetness-seam vert:
for geomVert in fixGeomVerts do
(
local vertVals = perVertVals[geomVert]
if (vertVals.count != 0) do
(
-- Find average colour:
local newVal = [0,0,0]
for thisVal in vertVals do
(
newVal += thisVal
)
newVal /= vertVals.count
-- Apply new colour to mapping-verts corresponding to this geometry-vert:
local mapVerts = geomToMapVerts[geomVert]
for mapVert in mapVerts do
(
objSetMapVert editObj wetnessChan mapVert newVal
)
)
)
-- Dipose of arrays:
for thisArray in #(geomToMapVerts, perVertVals) do
(
for item in thisArray do (item.count = 0)
thisArray.count = 0
)
PopPrompt()
)
)
-- Apply changed baseobject to original node:
undo "Fix Terrain Seams" on
(
obj.baseObject = editObj
)
return OK
)
)
global gRsTerrainSeamFinder = sRsTerrainSeamFinder()
-- TEST BITS:
if False do
(
ClearListener()
--GC()
undo "Test Seam Fixer" on
(
for obj in GetCurrentSelection() do
(
local timeClass = (DotnetClass "System.DateTime")
local timeStart = (timeClass.now)
--local probVerts = gRsTerrainSeamFinder.FindProblemVerts obj; print probVerts
--SubObjectLevel = 1; polyop.SetVertSelection obj probVerts
--local vals = gRsTerrainSeamFinder.FindProblems obj getFixData:True
--gRsTerrainSeamFinder.FixObjProblems obj fixNames:#(#blendSeam)
gRsTerrainSeamFinder.FixObjProblems obj
print vals
local timeTaken = ((timeClass.now.Subtract timeStart).totalSeconds as Float)
Format "%\n\tTime taken: %s\n" obj timeTaken
)
)
)