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