-- terrain_helperfunctions.ms -- created by Gunnar Droege -- written by Luke Openshaw -- ripped out of the collision set tool for general use -- 10/06/2010 -- -- Neal D Corbett 05/2014 - Rewritten as generalised methods for processing terrain-shaders fileIn (RsConfigGetWildWestDir() + "script/3dsMax/_common_functions/FN_RSTA_string.ms") global gRsTerrainHelpers -- Returns value used to match two colours together; the higher the value, the better the match: -- (ClrA/B should be normalised point3 values) fn RsGetAlphaVal ClrA ClrB = ( -- Alpha corresponds to r/g/b matches: local Alph = (ClrA * ClrB) + (1.0 - ClrA) * (1.0 - ClrB) -- Return combined alpha-value: return (Alph.X * Alph.Y * Alph.Z) ) -- Descriptor for a terrain-material shader-type: -- (Default settings are for GTA5-style 'Terrain_Cb_*' blended shaders) struct RsTerrainShaderType ( -- Flags for quickly checking shader-type: isUberTerrain = False, isStandard2lyr = False, -- The behaviour of a terrain-material depends on which type of terrain-shader it's using: Type = #Terrain_Cb, -- String-pattern for distiguishing shader-types via their names: pattern = "Terrain_Cb_*", -- Colours used to denote which diffuse-texture should be used where: LookupColours = #(Black, Blue, Green, (color 0 255 255), Red, (color 255 0 255), Yellow, White), LookupColourNames = #("black", "blue", "green", "turquoise", "red", "purple", "yellow", "white"), NormLookupColours, -- Colour-indices that are safe to blend to from the colour with a given index: LookupSafeCombos = #(#{1,2,3,5},#{1,2,4,6},#{1,3,4,7},#{2,3,4,8},#{1,5,6,7},#{2,5,6,8},#{3,5,7,8},#{4,6,7,8}), -- UV-channels used to place Texmaps/Lookup-maps/Tint-maps: -- (default Texmap/LookupUVchans can be changed per-material for some shaders) texmapUVchan = 1, lookupUVchan = 2, tintUVchan = 3, -- Will be set to list of channels that LookupUVchan can be set to: LookupUVchans = #{}, -- Lookup/Tint vertex-colour channels: LookupClrChan = 9, TintClrChan = 0, -- Channels used to blend between Texture/Vertex-Colour versions of Lookup/Tint: -- Lookup is masked on vertex-alpha channel by default maskChan = -2, lookupMaskSubChan, tintMaskSubChan, -- Channels for masking displacement/curvature effects: effectsMaskChan, displaceMaskSubChan, curvatureMaskSubChan, -- Default mask-values, to be used on slider-controls: DefLookupMaskVal = 1.0, DefTintMaskVal = 0.0, DefDisplaceMaskVal = 0.0, -- Text description of mask-channels: lookupMaskChanText = "Vertex Alpha", tintMaskChanText = "", displaceMaskChanText = "", -- Text description of mask min/max values: LookupMaskRangeText = DataPair Min:"Texture" Max:"Vertex", TintMaskRangeText = DataPair Min:"Texture" Max:"Vertex", DisplaceMaskRangeText = DataPair Min:"Show" Max:"Hide", -- Function for deconstructing a given normalised colour-value to texmap-blend weights: (this alias is set by 'On Create') GetWeightsFromClr, -- Returns value used to match two colours together; the higher the value, the better the match: -- (ClrA/B should be normalised point3 values) fn GetAlphaVal clrA clrB = ( -- Alpha corresponds to r/g/b matches: local alph = (clrA * clrB) + (1.0 - clrA) * (1.0 - clrB) -- Return combined alpha-value: return (alph.x * alph.y * alph.z) ), -- (converted from 'BlendTerrainColor' in 'terrain_cb_common.fxh') fn GetWeightsFromClr_Terrain_Cb clrVal texCount: = ( if (texCount == unsupplied) do ( texCount = normLookupColours.count ) for idx = 1 to texCount collect ( GetAlphaVal clrVal normLookupColours[idx] ) ), -- (converted from 'cpvBlendWeights' in 'terrain_uber_common.fxh') fn GetWeightsFromClr_Terrain_Uber clrVal = ( local rVal = clrVal[1] local gVal = clrVal[2] local bVal = clrVal[3] local blackVal = (1.0 - (rVal + gVal + bVal)) if (blackVal < 0) do (blackVal = 0) -- Return blend-weights calculated for clrVal. -- Uber lookup-colours are red/green/blue/black: return #(rVal, gVal, bVal, blackVal) ), -- Greyscale values: fn GetWeightsFromClr_Standard2Lyr clrVal = ( local whiteVal = clrVal[1] local blackVal = (1.0 - whiteVal) return #(blackVal, whiteVal) ), -- Function for constructing colour-value from given blend-weights: -- (inverse function to 'GetWeightsFromClr') GetClrFromWeights, fn GetClrFromWeights_Terrain_Cb blends = ( -- Construct blend-colour colour up by adding weighted lookup-colours: local OutClr = [0,0,0] for Idx = 1 to 4 do ( OutClr += (Blends[Idx] * NormLookupColours[Idx]) ) return OutClr ), fn GetClrFromWeights_Terrain_Uber blends = ( -- Just return first three blend-weights as rgb values: return [Blends[1],Blends[2],Blends[3]] ), fn GetClrFromWeights_Standard2Lyr blends = ( -- Just return white-value: local whiteVal = blends[2] return [whiteVal, whiteVal, whiteVal] ), on Create do ( -- Convert lookup-colours to normalised point3 values: this.normLookupColours = for clr in this.lookupColours collect ((clr as Point3) / 255.0) -- Choose appropriate blend-weight function for shader: this.GetWeightsFromClr = case type of ( #Terrain_Uber:(this.GetWeightsFromClr_Terrain_Uber) #Standard_2lyr:(this.GetWeightsFromClr_Standard2Lyr) Default:(this.GetWeightsFromClr_Terrain_Cb) ) this.GetClrFromWeights = case type of ( #Terrain_Uber:(this.GetClrFromWeights_Terrain_Uber) #Standard_2lyr:(this.GetClrFromWeights_Standard2Lyr) Default:(this.GetClrFromWeights_Terrain_Cb) ) -- Get list of Lookup UV channels this shadertype might use: this.lookupUVchans = case type of ( #Terrain_Uber:(#{1..3}) #Standard_2lyr:(#{1..2}) Default:(#{this.lookupUVchan}) ) ) ) -- TTN shaders are like default Terrain_Cb, but with Tint-texmap, -- and mask-channel to blend between using Lookup/Tint texmaps or vertex-colours: global RsTerrainShaderType_TTN = ( RsTerrainShaderType Type:#Terrain_Cb_Ttn \ Pattern:"Terrain_Cb_*_Ttn" ) -- Default: GTA5-style terrain-shaders: -- (only 'cm' versions have the 'Lookup texture' slot) global RsTerrainShaderType_CB = ( RsTerrainShaderType() ) -- RDR3/Americas-style terrain-shaders: global RsTerrainShaderType_Uber = ( RsTerrainShaderType Type:#Terrain_Uber \ Pattern:"Terrain_Uber_*" \ LookupColours:#(Red, Green, Blue, Black) \ LookupColourNames:#("red", "green", "blue", "black") \ LookupSafeCombos:#() \ -- (Uber doesn't appear to show blending-artifacts) LookupUVchan:1 TintUVchan:1 \ -- Lookup/Tint textures use same UVs as diffuse-maps (diffuse-tiling is changed via shader values) MaskChan:5 \ -- Lookup/Tint are both masked by this vert-channel TintMaskSubChan:1 TintMaskChanText:"Vert-Channel 5: Red" \ -- Tint is masked on red sub-channel LookupMaskSubChan:2 LookupMaskChanText:"Vert-Channel 5: Green" \ -- Lookup is masked on green sub-channel DisplaceMaskSubChan:3 DisplaceMaskChanText:"Vert-Channel 5: Blue" -- Displacement is masked on blue sub-channel ) -- List of differences between terrain-shader types: global RsTerrainShaderTypes = #( RsTerrainShaderType_TTN, RsTerrainShaderType_CB, RsTerrainShaderType_Uber ) -- Descriptor for a terrain-material. -- Should be initialised with 'TerrainMat' argument. struct RsTerrainMatInfo ( Private -- Faces this material and its diffuse-texmaps are used on: -- 'MatFaces' is set up by 'GetObjTerrainFaceData', 'DiffuseFaces' by 'FindDominantFaceTextures' MatFaces = #{}, DiffuseFaces = #(), Public TerrainMat, -- 'PresetName' is the value in material's 'Preset' slot, which will be the shader-name or a Material Preset. -- If this is a Material Preset, the name of the actual shader will be saved to ShaderName. -- 'ShaderName' is matched against RsTerrainShaderTypes: PresetName, ShaderName, TypeDef, DiffuseTexPaths = #(), BumpTexPaths = #(), TexCount = 0, LookupTexPath, TintTexPath, -- The material-slots containing this material's diffuse/bump texmaps: DiffSlotNums = #(), BumpSlotNums = #(), -- Object and MatId this material is used on: Obj, MatId = undefined, -- Material's shader-values, updated by 'UpdateVals': VarNames = #(), VarTypes = #(), VarNums = #(), VarVals = #(), -- Track tool-selection of texmap-indices: Selected = #{}, ------------------------------------------------------------------------------------------------------ -- Accessor-functions for private arrays: ------------------------------------------------------------------------------------------------------ fn GetMatFaces = (return MatFaces), fn GetDiffuseFaces = (return DiffuseFaces), ------------------------------------------------------------------------------------------------------ -- GetLookupUVchan: -- Returns the Lookup UV channel for this descriptor's material. -- This can currently only be changed on a per-material basis for Terrain_Uber shaders. ------------------------------------------------------------------------------------------------------ fn GetLookupUVchan = ( -- Default value local uvChan = typeDef.lookupUVchan -- Get channel-value from material, where supported: local matChan = case of ( (typeDef.isUberTerrain):(RstGetVariableByName this.terrainMat "Lookup UV Set") (typeDef.isStandard2lyr):(RstGetVariableByName this.terrainMat "Control UV Set") Default:Undefined ) -- Use channel from material: if (matChan != undefined) do ( uvChan = (Integer matChan) ) return uvChan ), ------------------------------------------------------------------------------------------------------ -- GetTexmapUVchan: -- Returns the UV channel used to map a materials texmap. -- This can currently only be changed on a per-texmap basis for Terrain_Uber shaders. ------------------------------------------------------------------------------------------------------ fn GetTexmapUVchan texNum = ( -- Default mapping-channel: local uvChan = typeDef.texmapUVchan case of ( (typeDef.isUberTerrain): ( -- PXM shaders will use the default UV channel (1) if (not isPxmShader) do ( -- Use material's 'UV Set' variable, if supported by shader: local texChans = (RstGetVariableByName this.terrainMat "UV Set") if (texChans != undefined) do ( uvChan = (Integer texChans[texNum]) ) ) ) (typeDef.isStandard2lyr): ( local varName = case texNum of ( 1:"Lyr1 UV Set" 2:"Lyr2 UV Set" ) uvChan = Integer (RstGetVariableByName this.terrainMat varName) ) ) return uvChan ), ------------------------------------------------------------------------------------------------------ -- UpdateVals: -- Get/update Material info: ------------------------------------------------------------------------------------------------------ fn UpdateVals = ( -- Get material's current preset/shader-name: local NewPresetName = (RstGetShaderName TerrainMat) -- Clear path-arrays/values: ( for ThisArray in #(DiffuseTexPaths, BumpTexPaths) do ( ThisArray.Count = 0 ) LookupTexPath = undefined TintTexPath = undefined ) -- Update details if shadername has changed since: if (NewPresetName != PresetName) do ( -- Clear various values/arrays: ( for ThisArray in #(VarNames, VarTypes, VarNums, VarVals, Selected, DiffuseFaces) do ( ThisArray.Count = 0 ) TexCount = 0 ) -- Update shader-name stuff: ( PresetName = NewPresetName -- Get shadername for preset,if used: -- ('RstGetMaterialPresetShaderName' isn't currently defined in GTA5 tools) if (RstGetMaterialPresetShaderName != undefined) do ( ShaderName = (RstGetMaterialPresetShaderName TerrainMat) ) -- If material isn't using a Preset, use 'PresetName' as 'ShaderName': if (ShaderName == undefined) do ( ShaderName = PresetName ) -- Strip extension from ShaderName: ShaderName = (GetFilenameFile ShaderName) -- Find matching terrain-type for material: TypeDef = undefined for ThisType in RsTerrainShaderTypes while (TypeDef == undefined) do ( if (MatchPattern ShaderName Pattern:ThisType.Pattern) do ( TypeDef = ThisType ) ) ) -- Update attribute-names/types if this is a valid terrain-shader: if (TypeDef != undefined) do ( local NumVars = (RstGetVariableCount TerrainMat) -- Find shader's texmap-slots, used for texmap-swapping: local TexSlotNum = 0 for VarNum = 1 to NumVars do ( local VarType = (RstGetVariableType TerrainMat VarNum) -- We're only interested in Texmap and Vector4 attributes: if (varType == "texmap") or (varType == "vector4") or (varType == "float") do ( local VarName = (RstGetVariableName TerrainMat VarNum) append VarNames VarName append VarNums VarNum append VarTypes VarType if (VarType == "texmap") do ( TexSlotNum += 1 case of ( -- Get diffuse-maps: ((matchpattern VarName pattern:"Diffuse*") or (matchpattern VarName pattern:"Color Texture*")): ( append DiffuseFaces #{} append DiffSlotNums TexSlotNum ) -- Remember bump slot-numbers: (matchPattern VarName pattern:"Bump *"): ( append BumpSlotNums TexSlotNum ) ) ) ) ) -- Get diffusemap-slot count: TexCount = DiffSlotNums.Count ) ) -- Stop processing if this isn't a valid terrain-shader: if (TypeDef == undefined) do return OK -- Collect updated attribute-values: for VarIdx = 1 to VarNums.Count do ( local VarNum = VarNums[VarIdx] local ThisVal = (RstGetVariable TerrainMat VarNum) VarVals[VarIdx] = ThisVal -- Collect updated texturemap-name data: if (VarTypes[VarIdx] == "texmap") do ( local VarName = VarNames[VarIdx] case of ( -- Get diffuse-maps: ((matchpattern VarName pattern:"Diffuse*") or (matchpattern VarName pattern:"Color Texture*")): ( append DiffuseTexPaths ThisVal ) -- Remember bump slot-numbers: (matchPattern VarName pattern:"Bump *"): ( append BumpTexPaths ThisVal ) -- Get lookup-texture path, if used: (matchpattern VarName pattern:"Lookup texture"): ( LookupTexPath = ThisVal ) -- Get tint-texture path, if used: (matchpattern VarName pattern:"Tint"): ( TintTexPath = ThisVal ) ) ) ) return OK ), ------------------------------------------------------------------------------------------------------ -- GetTexPath: -- Returns diffuse/bump-map path with specific index: ------------------------------------------------------------------------------------------------------ fn GetTexPath TexNum Bump:False = ( local TexArray = if Bump then BumpTexPaths else DiffuseTexPaths local ThisPath = TexArray[TexNum] if (ThisPath == undefined) do ( ThisPath = "" ) return ThisPath ), ------------------------------------------------------------------------------------------- -- GetFaceVertTexWeights: -- Returns lists of blend-weights for each vert on each face in 'MatFaces', -- Lookup-bitmaps and masks are ignored if 'IgnoreBitmaps' is true, ------------------------------------------------------------------------------------------- fn GetFaceVertTexWeights faceLookupClrVerts: ignoreBitmaps:False quiet:False escToAbort:False perFace:False = ( -- Abort if material has no faces, or is an invalid object: if (this.matFaces.numberSet == 0) or not ((IsValidNode obj) or (IsKindOf obj TriMesh)) do return OK local faceLookupClrVertsUnsupplied = (faceLookupClrVerts == Unsupplied) if faceLookupClrVertsUnsupplied do ( faceLookupClrVerts = #() ) local promptMsg = ("Collecting texmap blend-weights... [matId: " + (matId as String) + "]") if escToAbort do ( Append promptMsg " [Esc to Cancel]" ) PushPrompt (RsProgressString PromptMsg 0) -- Set up aliases to appropriate functions for this object: local ObjOp = RsMeshPolyOp Obj local isMeshObj = (ObjOp == MeshOp) local ObjGetFace = RsGetFaceFunc obj local ObjGetMapFace = RsGetMapFaceFunc obj local ObjGetMapVert = ObjOp.GetMapVert -- Get appropriate vert-channels from the TypeDef: local LookupClrChan = TypeDef.LookupClrChan local LookupMapChan = This.GetLookupUVchan() local MaskChan = TypeDef.MaskChan local LookupMaskSubChan = TypeDef.LookupMaskSubChan -- Are we processing Standard_2lyr shader? local isStandard2lyr = typeDef.isStandard2lyr -- Do we need to get pixels and mask for Lookup texture? -- (non-vertcolour channels are ignored for 'IgnoreBitmaps' mode) local HasLookupTex = (not IgnoreBitmaps) and (LookupTexPath != undefined) local UsesMask = (not isStandard2lyr) and (not IgnoreBitmaps) and (HasLookupTex) and (TypeDef.MaskChan != undefined) local UsesLookupClr = (LookupClrChan != undefined) local UsesLookupMap = (not IgnoreBitmaps) and (LookupMapChan != undefined) -- Get typedef's function for finding a colour's texmap-blend weights: local GetWeightsFromClr = TypeDef.GetWeightsFromClr -- Are required channels actually available? local LookupClrChanAvailable = (UsesLookupClr and (ObjOp.GetMapSupport Obj LookupClrChan)) local LookupMapChanAvailable = (UsesLookupMap and (ObjOp.GetMapSupport Obj LookupMapChan)) local MaskChanAvailable = (UsesMask and (ObjOp.GetMapSupport Obj MaskChan)) -- Get blend-value stuff for Standard_2lyr shader: if isStandard2lyr do ( local blendSoftness = (RstGetVariableByName this.terrainMat "Blend Softness") local controlFilter = (RstGetVariableByName this.terrainMat "Control Filter") local invertControl = ((RstGetVariableByName this.terrainMat "Invert Control") == 1.0) -- This needs to be a Point3 to be used with Point3 colours if (IsKindOf controlFilter Point4) do controlFilter = controlFilter as Point3 -- Find blend-exponent: local blendExp = (blendSoftness ^ 2) if (blendExp < 0.00001) do ( blendExp = 0.00001 ) blendExp = (1.0 / blendExp) ) -- Standard_2lyr needs to load alpha from Control Texture: local texClrClass = if isStandard2lyr then Point4 else Point3 -- Warn about missing channels, where required: if (not Quiet) do ( for Item in #( DataPair Text:("Lookup-colour (channel " + (LookupClrChan as string) + ")") Error:(UsesLookupClr and not LookupClrChanAvailable), DataPair Text:("Lookup-texture UVs (channel " + (LookupMapChan as string) + ")") Error:(UsesLookupMap and not LookupMapChanAvailable), DataPair Text:("Mask-colour (channel " + (MaskChan as string) + ")") Error:(UsesMask and not MaskChanAvailable) ) where Item.Error do ( format "WARNING: % is missing on object" Item.Text if (isValidNode Obj) do (format " '%'" Obj.Name) -- Don't print name for TriMesh Obj-values format " - this is required by material ID % (we'll assume channel is black)\n" matId ) ) -- Lookup-bitmap data will be loaded to here, if required: local LookupBmp, BmpMaxX, BmpMaxY local BmpLoadFailed = False -- Don't bother attempting to load lookup-bitmap if the relevant uv-channel is missing: -- (lookup-bitmap will be assumed to be all black) if (not LookupMapChanAvailable) do ( BmpLoadFailed = True ) -- Use arbitrary rgb subchannel for lookup-mask if subchannel was unspecified... local UseLookupMaskSubChan = LookupMaskSubChan if (UseLookupMaskSubChan == undefined) do ( UseLookupMaskSubChan = 1 ) -- UV/colour vert-data for this material, so we'll only need to probe each vert once: local LookupMapVertClrs = #() if LookupMapChanAvailable do (LookupMapVertClrs.Count = (ObjOp.GetNumMapVerts Obj LookupMapChan)) -- Collect UV face-vert lists - these are used by other functions too: if LookupClrChanAvailable do ( join FaceLookupClrVerts (for FaceNum in MatFaces collect (ObjGetMapFace Obj LookupClrChan FaceNum)) ) -- Get number of faces used by material: local MatFacesCount = (this.matFaces.numberSet) -- We expect this operation will require about this much memory: local blendsCount = this.texCount local FaceVertsEstimate = if isMeshObj then 3 else 4 local MemReq = (Integer64 MatFacesCount * FaceVertsEstimate * BlendsCount * 32) local MemDiff = (HeapFree - MemReq) --format "MemReq:% HeapFree:% MemDiff: %\n" MemReq HeapFree MemDiff -- Increase heapsize if we think it's going to be too small: -- (otherwise Max may auto-increase it thousands of times during loop, very slow) if (MemDiff < 0) do ( HeapSize += (abs MemDiff) ) -- Predefine array: local FaceBlendWeights = for n = 1 to MatFacesCount collect #() -- Update prompt: (overriding any heap-resize messages) ReplacePrompt (RsProgressString PromptMsg 0) -- We'll update the status-prompt every so often: local PromptUpdateInterval = (MatFacesCount / 21) -- Find dominant lookup-weightings for each material's faces' verts: local Success = True local FaceIdx = 0 for FaceNum in This.MatFaces while (Success = not (EscToAbort and Keyboard.EscPressed)) do ( FaceIdx += 1 -- Update statusprompt progressbar every so often: if ((Mod FaceIdx PromptUpdateInterval) == 0) do ( ReplacePrompt (RsProgressString PromptMsg (1.0 * FaceIdx / MatFacesCount)) ) -- This empty blendweights-array will be initially filled with lookup-colours... local FaceLookupClrs = FaceBlendWeights[FaceIdx] local FaceLookupMask = #() -- Set to False if all vert-colours are to be taken from lookup-bitmap: local HasLookupVertClrs = True -- Get mask-values for face's verts, if shader uses Lookup-mask: if HasLookupTex do ( -- Default this to false for Terrain (for shaders that have Lookup-texture but no masking feature) -- Standard_2lyr uses both texture and vertex values, and has no masking hasLookupVertClrs = if isStandard2lyr then True else False local hasLookupTexClrs = True -- Get data from mask-channel, if used by this terrain-shader type: if usesMask do ( -- Only process mask-channel if it's actually active - otherwise, we'll default to assuming it is white if MaskChanAvailable then ( -- Get verts used on mask-channel face: local FaceMaskVerts = (ObjGetMapFace Obj MaskChan FaceNum) -- Get mask-values for each of mask-face's verts: faceLookupMask = for mapVertNum in faceMaskVerts collect ( -- Get colour-value from mask-channel: local maskClr = (ObjGetMapVert obj maskChan mapVertNum) -- Get mask-values for Lookup-texture: maskClr[useLookupMaskSubChan] ) faceMaskVerts.count = 0 -- Do we need to load colours from texture or vertcolours or both for this face? HasLookupVertClrs = False HasLookupTexClrs = False for VertMask in FaceLookupMask do ( if (VertMask > 0) do ( -- This face takes some colours from Lookup vertex-channel: HasLookupVertClrs = True ) if (VertMask < 1) do ( -- This face takes some colours from Lookup texture: HasLookupTexClrs = True ) ) ) else ( -- If mask-channel is missing, we'll assume it's default white: HasLookupTexClrs = False HasLookupVertClrs = True ) ) -- Collect unmasked colours, from lookup-bitmap: if hasLookupTexClrs do ( -- Load bitmap, it will be needed: -- (this will happen once per material) if (not BmpLoadFailed) and (LookupBmp == undefined) do ( LookupBmp = OpenBitmap LookupTexPath -- Check to see if bitmap failed to load: if (LookupBmp == undefined) then ( BmpLoadFailed = True ) else ( BmpMaxX = (LookupBmp.Width - 1) BmpMaxY = (LookupBmp.Height - 1) ) ) -- Collect lookup-colours for this face's verts: -- Use black as default if lookup-bitmap failed to load: local LookupClrs = if (BmpLoadFailed) then ( -- This works even if the mapping-channel is inactive: for ThisVert in (ObjGetFace Obj FaceNum) collect [0,0,0] ) else ( -- Get verts used on lookup-texture's map-face: local FaceMapVerts = ObjGetMapFace Obj LookupMapChan FaceNum -- Collect bitmap-colours for each unmasked vert: for VertIdx = 1 to FaceMapVerts.Count collect ( -- Get mask-value for vert (zero (black) if shader doesn't do masking, or 1 (white) if mask-channel is missing) local VertMask = case of ( (UsesMask and MaskChanAvailable):FaceLookupMask[VertIdx] UsesMask:1 Default:0 ) -- Collect undefined if bitmap is masked for this vert... local LookupClr = undefined -- Get bitmap-colour from texturemap if vert is unmasked: if (vertMask < 1) do ( -- Get vertex-number from mapping-face vert-list: local MapVertNum = FaceMapVerts[VertIdx] -- Get cached colour for vert: LookupClr = LookupMapVertClrs[MapVertNum] -- If not cached, get UVs from, mesh, and colour from bitmap: if (LookupClr == undefined) do ( local UvPos = ObjGetMapVert Obj LookupMapChan MapVertNum -- Convert UV-coords to texture-coords: local BmpPos = [integer (abs (mod UvPos.x 1.0) * BmpMaxX), integer ((1 - (abs (mod UvPos.y 1.0))) * BmpMaxY)] -- Get pixel-colour: LookupClr = (GetPixels LookupBmp BmpPos 1)[1] -- Convert to nomalised Point3/Point4: LookupClr = (LookupClr as texClrClass) / 255 -- Cache pixel-colour: LookupMapVertClrs[MapVertNum] = LookupClr ) ) -- Collect colour from bitmap: LookupClr ) ) -- Join colours-array to this empty array (this maintains array-references) join FaceLookupClrs LookupClrs ) -- (Finished loading face's colours from Lookup-texture) ) -- Get colours from Lookup vert-colours, if face's mask allows this: if hasLookupVertClrs do ( local ThisFaceLookupClrVerts if LookupClrChanAvailable do ( -- Get vert-indices for face's vert-colour Lookup-colours: ThisFaceLookupClrVerts = FaceLookupClrVerts[FaceIdx] -- Expand face's colours-array if it is still empty: FaceLookupClrs.Count = ThisFaceLookupClrVerts.Count ) -- Get vert-colours for verts that aren't masked to use Lookup texture: for VertIdx = 1 to FaceLookupClrs.Count do ( local VertMask = if (UsesMask and MaskChanAvailable) then FaceLookupMask[VertIdx] else 1 -- Only bother getting vert-colour if this vert was masked at all - otherwise we just use colour from Lookup texture. if (VertMask > 0) do ( local VertClr if (LookupClrChanAvailable) then ( local MapVertNum = ThisFaceLookupClrVerts[VertIdx] -- Get normalised Lookup vertex-colour: (on 0-1 scale) vertClr = ObjGetMapVert obj lookupClrChan mapVertNum case of ( -- Apply Control Texture values to Standard_2lyr vertex-blend: isStandard2lyr: ( -- Get 'Control Amount' from texmap: local bmpVertClr = faceLookupClrs[vertIdx] local controlAmount = (Dot controlFilter bmpVertClr) if invertControl do ( controlAmount = (1.0 / controlAmount) ) -- Find blend-value: local vertClrVal = vertClr[1] local blendBase = (vertClrVal * controlAmount) + vertClrVal if (blendBase > 1.0) do ( blendBase = 1.0 ) local blendVal = (blendBase ^ blendExp) vertClr = [blendVal,blendVal,blendVal] ) -- If vert isn't fully-masked, mix it with the colour taken from the lookup-texture: (vertMask < 1): ( local bmpVertClr = faceLookupClrs[vertIdx] -- Combine texmap/vertex lookup-colours using mask-value as alpha: vertClr = (vertMask * vertClr) + ((1 - vertMask) * bmpVertClr) ) ) ) else ( -- Default to black if lookup-channel is missing: VertClr = [0,0,0] ) FaceLookupClrs[VertIdx] = VertClr ) ) ) -- This array is no longer needed: FaceLookupMask.Count = 0 -- Now we have the colours for each of face's verts (blended between Lookup-vertcolours and texturemap as required) -- Extract texmap blend-weights for each vert: for VertIdx = 1 to FaceLookupClrs.Count do ( local vertClr = FaceLookupClrs[VertIdx] -- Replace array-colour with its corresponding blend-weights: FaceLookupClrs[VertIdx] = (GetWeightsFromClr vertClr texCount:texCount) ) ) -- All done! Now close lookup-bitmap, if one has been opened: if (LookupBmp != undefined) do ( Close LookupBmp ) -- Clear finished arrays: lookupMapVertClrs.count = 0 -- Only clear this array if it was created inside this function: if (faceLookupClrVertsUnsupplied) do ( for item in faceLookupClrVerts do ( item.count = 0 ) faceLookupClrVerts.count = 0 ) PopPrompt() if Success then (return FaceBlendWeights) else (return False) ), ------------------------------------------------------------------------------------------- -- GetFaceTexWeights: -- Returns lists of blend-weights for each face in 'MatFaces', -- Lookup-bitmaps and masks are ignored if 'IgnoreBitmaps' is true, ------------------------------------------------------------------------------------------- fn GetFaceTexWeights ignoreBitmaps:False quiet:False escToAbort:False = ( -- Get per-vertex weights, or face-weights from lookup-render: local faceVertWeights = this.GetFaceVertTexWeights perFace:True ignoreBitmaps:ignoreBitmaps quiet:quiet escToAbort:escToAbort -- Return non-array error-values: if (not isKindOf faceVertWeights Array) do return faceVertWeights if (faceVertWeights.count == 0) do return #() -- If this is a list of weights per vertex per face, convert to list of weights per face: if (IsKindOf faceVertWeights[1][1] Array) do ( local blendsCount = this.texCount for faceIdx = 1 to faceVertWeights.count do ( local vertWeights = faceVertWeights[faceIdx] if (vertWeights.count != 0) do ( local avgWeights = vertWeights[1] for vertIdx = 2 to vertWeights.count do ( local thisVertWeights = vertWeights[vertIdx] for texIdx = 1 to blendsCount do ( avgWeights[texIdx] += thisVertWeights[texIdx] ) ) avgWeights = for thisWeight in avgWeights collect (thisWeight / blendsCount) faceVertWeights[faceIdx] = avgWeights ) ) ) return faceVertWeights ), ------------------------------------------------------------------------------------------- -- HasMatchingTexIdx: -- Returns idx of highest BlendWeight ------------------------------------------------------------------------------------------- fn GetDominantTexIdx BlendWeights = ( FindItem BlendWeights (Amax BlendWeights) ), ------------------------------------------------------------------------------------------- -- HasMatchingTexIdx: -- Used to decide whether a subobject uses a given texmap or not. -- [Default] True if TexIdx has non-zero weighting -- [Dominant:True] True if TexIdx has the highest weighting ------------------------------------------------------------------------------------------- fn HasMatchingTexIdx BlendWeights TexIdx Dominant:False = ( if Dominant then ( return ((GetDominantTexIdx BlendWeights) == TexIdx) ) else ( return (BlendWeights[TexIdx] != 0.0) ) ), -- Returns list of verts displaying texmap with index 'TexIdx'; fn GetVertsUsingTexmap TexIdx IgnoreBitmaps:False Dominant:False = ( -- Return verts for all matfaces if no specific TexIdx was requested: if (TexIdx == 0) then ( -- Get list of verts used by matfaces: local MatVerts = #() local ObjGetFace = (RsGetFaceFunc Obj) for FaceNum in MatFaces do ( join MatVerts (ObjGetFace Obj FaceNum) ) -- Return verts for faces: return (MatVerts as BitArray) ) else ( local FaceGeomVerts = #() -- Get vertex texture-blends data: local FaceBlendWeights = GetFaceTexWeights FaceGeomVerts:FaceGeomVerts IgnoreBitmaps:IgnoreBitmaps -- Abort if that failed: if (not isKindOf FaceBlendWeights Array) do (return OK) local TexVerts = #{} -- Examine blend-weights on verts used by each face: for FaceIdx = 1 to FaceBlendWeights.Count do ( local FaceBlends = FaceBlendWeights[FaceIdx] local FaceVertNums = FaceGeomVerts[FaceIdx] for VertIdx = 1 to FaceBlends.Count do ( -- Set bit for geometry-verts if weighting matches TexIdx: if (HasMatchingTexIdx FaceBlends[VertIdx] TexIdx Dominant:Dominant) do ( local VertNum = FaceVertNums[VertIdx] TexVerts[VertNum] = True ) ) ) return TexVerts ) ), -- Returns list of faces displaying texmap with index 'TexIdx'; fn GetFacesUsingTexmap TexIdx IgnoreBitmaps:False Dominant:False = ( -- Return all matfaces if no specific TexIdx was requested: if (TexIdx == 0) do return MatFaces -- Get vertex texture-blends data: local FaceBlendWeights = GetFaceTexWeights IgnoreBitmaps:IgnoreBitmaps -- Abort if that failed: if (not isKindOf FaceBlendWeights Array) do (return OK) local TexFaces = #{} ( -- Initialise face-array sizes: local MatFacesArray = (MatFaces as Array) local MaxFaceNum = MatFacesArray[MatFacesArray.Count] MatFacesArray.Count = 0 TexFaces.Count = MaxFaceNum ) local FaceNums = (MatFaces as Array) -- Examine blend-weights on verts used by each face: for FaceIdx = 1 to FaceBlendWeights.Count do ( local FaceHasTex = False local FaceBlends = FaceBlendWeights[FaceIdx] if Dominant then ( -- Add faces' vert-weights together: local CombinedBlends = for TexIdx = 1 to TexCount collect ( local Val = 0 for VertBlends in FaceBlends do ( Val += VertBlends[TexIdx] ) Val ) -- Find dominant texmap for combined blend-weights, and see if it matches TexIdx: FaceHasTex = ((GetDominantTexIdx CombinedBlends) == TexIdx) ) else ( -- Does this face include a vert with non-zero blend for this texmap? for VertBlends in FaceBlends while (not FaceHasTex) do ( FaceHasTex = (HasMatchingTexIdx VertBlends TexIdx Dominant:False) ) ) if FaceHasTex do ( local FaceNum = FaceNums[FaceIdx] TexFaces[FaceNum] = True ) ) return TexFaces ), ------------------------------------------------------------------------------------------ -- SwapTexmapLookups: -- Swaps lookup-colours for two texmap-ids. ------------------------------------------------------------------------------------------ fn SwapTexmapLookups TexIdxA TexIdxB = ( -- Abort if both idxs are the same for some reason... if (TexIdxA == TexIdxB) do return OK -- Get vertex texture-blends data for material's faces: local FaceLookupClrVerts = #() local FaceBlendWeights = GetFaceTexWeights FaceLookupClrVerts:FaceLookupClrVerts IgnoreBitmaps:True -- Abort if that failed: if (not isKindOf FaceBlendWeights Array) or (FaceLookupClrVerts.Count != FaceBlendWeights.Count) do (return FaceBlendWeights) -- Set up aliases to appropriate functions for this object: local ObjOp = RsMeshPolyOp Obj local ObjSetMapVert = ObjOp.SetMapVert local ObjGetMapFace = RsGetMapFaceFunc Obj -- We'll be processing the Lookup vertexcolour-channel: local Chan = TypeDef.LookupClrChan -- Get typedef's function for converting texmap-blend weights to colour-values: local GetClrFromWeights = TypeDef.GetClrFromWeights -- Get map-verts per lookup-channel face, to compare against 'FaceBlendWeights': local LookupClrChan = TypeDef.LookupClrChan -- Bitarray to keep track of which mapverts have already been processed: local VertsDone = #{} VertsDone.Count = (ObjOp.GetNumMapVerts Obj LookupClrChan) -- Process all mapverts that have weights available: for FaceIdx = 1 to FaceBlendWeights.Count do ( -- Get blends and UV-indices for this face: local FaceBlends = FaceBlendWeights[FaceIdx] local FaceVerts = FaceLookupClrVerts[FaceIdx] -- Process face's verts in turn: for VertIdx = 1 to FaceVerts.Count do ( local VertNum = FaceVerts[VertIdx] -- Don't process mapverts more than once each: if (not VertsDone[VertNum]) do ( VertsDone[VertNum] = True local VertWeights = FaceBlends[VertIdx] -- Create new weights-array, where TexIdxA/B are swapped: local NewWeights = for TexIdx = 1 to TexCount collect ( case TexIdx of ( TexIdxA:VertWeights[TexIdxB] TexIdxB:VertWeights[TexIdxA] Default:VertWeights[TexIdx] ) ) -- Generate colour from swapped texmap-weights, and edit that lookup-vert: local NewClr = GetClrFromWeights NewWeights ObjSetMapVert Obj Chan VertNum NewClr ) ) ) -- Swap the values for this struct's path/face arrays: for ThisArray in #(DiffuseTexPaths, DiffuseFaces) do ( local OldValA = ThisArray[TexIdxA] ThisArray[TexIdxA] = ThisArray[TexIdxB] ThisArray[TexIdxB] = OldValA ) -- Update onscreen colours: case ObjOp of ( polyOp:(polyOp.collapseDeadStructs Obj) meshOp:(update Obj) ) CompleteRedraw() return OK ), ------------------------------------------------------------------------------------------ -- SwapTexmapMatSlots, SwapTexmapSlotsByTexIdx: -- Swaps material's texmap-slots for two texmap-ids. ------------------------------------------------------------------------------------------ fn SwapTexmapMatSlots SlotNumA SlotNumB = ( --format "Swapping texmap slots: %/%\n" SlotNumA SlotNumB local SlotATexmap = GetSubTexmap TerrainMat SlotNumA local SlotBTexmap = GetSubTexmap TerrainMat SlotNumB SetSubTexmap TerrainMat SlotNumA SlotBTexmap SetSubTexmap TerrainMat SlotNumB SlotATexmap SlotBTexmap.Filename = SlotBTexmap.Filename ), fn SwapTexmapSlotsByTexIdx TexIdxA TexIdxB = ( -- Swap diffuse and bump slots: for ThisArray in #(DiffSlotNums, BumpSlotNums) where (ThisArray.Count != 0) do ( SwapTexmapMatSlots ThisArray[TexIdxA] ThisArray[TexIdxB] ) -- Update shader after swaps are completed: RstRefreshMtl TerrainMat ), ------------------------------------------------------------------------------------------ -- SwapTexmaps: -- Swaps lookup-colours or material texmap-slots for two texmap-ids. ------------------------------------------------------------------------------------------ fn SwapTexmaps TexIdxA TexIdxB SwapMatSlots:True SwapLookups:True = ( if (TexIdxA == TexIdxB) do return False if SwapMatSlots do ( SwapTexmapSlotsByTexIdx TexIdxA TexIdxB ) if SwapLookups do ( SwapTexmapLookups TexIdxA TexIdxB ) ), ------------------------------------------------------------------------------------------- -- FindDominantFaceTextures: -- Works out which diffusemap is most prominent on each of material's faces -- (This function takes lookup-texture/mask into account) ------------------------------------------------------------------------------------------- fn FindDominantFaceTextures Quiet:False IgnoreBitmaps:False EscToAbort:False = ( -- Get texture-blends data for this material-definition: local FaceBlendWeights = (GetFaceTexWeights IgnoreBitmaps:IgnoreBitmaps EscToAbort:EscToAbort) -- Abort if that failed: if (not isKindOf FaceBlendWeights Array) do (return False) -- Get number of faces used by material: local MatFacesCount = 0 local MaxMatFaceNum = 0 for n in This.MatFaces do ( MatFacesCount += 1 MaxMatFaceNum = n ) -- Initialise face-array sizes: for TexFaces in DiffuseFaces do ( TexFaces.Count = MaxMatFaceNum ) local FacesCount = (FaceBlendWeights.Count) local PromptMsg = ("Finding dominant texmap index... [matId: " + (matId as String) + "]") if EscToAbort do ( append PromptMsg " [Esc to Cancel]" ) PushPrompt (RsProgressString PromptMsg 0) -- We'll update the status-prompt every so often: local PromptUpdateInterval = (FacesCount / 21) -- Examine blend-weights on verts used by each face: local faceIdx = 0 local multiVertWeights = False for FaceNum in This.MatFaces do ( faceIdx += 1 -- Update statusprompt's progressbar every so often: if ((Mod FaceIdx PromptUpdateInterval) == 0) do ( ReplacePrompt (RsProgressString PromptMsg (1.0 * FaceIdx / FacesCount)) ) local faceBlends = faceBlendWeights[FaceIdx] -- Is 'faceBlends' a single array of texmap-weights, or an array of per-vert weight-arrays? if (faceIdx == 1) and (IsKindOf faceBlends[1] Array) do ( multiVertWeights = True ) local combinedBlends = if (not multiVertWeights) then faceBlends else ( -- Add face's vert-weights together: for texIdx = 1 to texCount collect ( local val = 0 for vertBlends in faceBlends do ( val += vertBlends[texIdx] ) val ) ) -- Find dominant texmap for combined blend-weights: local texIdx = GetDominantTexIdx CombinedBlends -- We can now assign this face to the matching diffuse-path's face-list: DiffuseFaces[TexIdx][FaceNum] = True ) PopPrompt() return True ), ------------------------------------------------------------------------------------------- -- GetNameString: -- Generate name-string to show in tools ------------------------------------------------------------------------------------------- fn GetNameString = ( local MatText = StringStream "" if (MatId != undefined) and (MatId != -1) do ( format "%: " MatId To:MatText ) format "% [" TerrainMat.Name To:MatText -- Show shadername value if material is using a preset: if ((GetFilenameFile PresetName) != ShaderName) do ( format "% | " ShaderName To:MatText ) format "%]" PresetName To:MatText return (MatText as String) ), ------------------------------------------------------------------------------------------- -- Create: -- Initialises struct to match describe TerrainMat -- Works out flavour of Terrain shader is in use (if any) and collects textures ------------------------------------------------------------------------------------------- on Create do ( -- Abort if 'TerrainMat' is undefined or invalid: if (not isKindOf TerrainMat Rage_Shader) do return False -- Get material shader-data: UpdateVals() return OK ) ) struct RsTerrainHelpers ( ------------------------------------------------------------------------------------------------- -- GetObjTerrainFaceData: -- Returns list of 'RsTerrainMatInfo' descriptors of materials with -- compatible terrain-shaders (these are defined by array 'RsTerrainShaderTypes') -- If 'MatchMat' is supplied, function only returns descriptor for that submaterial. -- (if submaterial was found on object, and uses compatible shader) ------------------------------------------------------------------------------------------------- fn GetObjTerrainFaceData Obj Material: MatchMat: = ( if (not isValidObj Obj) do return #() -- Materials used on object, and their respective faces: local ObjMats = #() local FaceLists = #() local MtlIdList = #() -- Get material-info for object: PushPrompt "Collecting material-info for object..." RsGetMaterialsOnObjFaces Obj Material:Material Materials:ObjMats FaceLists:FaceLists MtlIdList:MtlIdList PopPrompt() -- Generate and collect terrain-material info-structs for each material found: PushPrompt "Processing materials..." local MatInfoList = for MatIdx = 1 to ObjMats.Count collect ( local SubMat = ObjMats[MatIdx] -- Skip this submaterial if if doesn't match the supplied 'MatchMat': if (MatchMat != unsupplied) and (MatchMat != SubMat) then DontCollect else ( local NewMatInfo = RsTerrainMatInfo TerrainMat:ObjMats[MatIdx] Obj:Obj MatId:MtlIdList[MatIdx] -- Set up private array - set up list of faces used by material: local MatFaces = NewMatInfo.GetMatFaces() join MatFaces FaceLists[MatIdx] -- Collect MatInfo struct: NewMatInfo ) ) PopPrompt() -- Filter out materials without valid terrain-shaders: MatInfoList = for Item in MatInfoList where (Item.TypeDef != undefined) collect Item -- Sort materials by matid: qsort MatInfoList (fn SortByMatId v1 v2 = (v1.MatId - v2.MatId)) return MatInfoList ), ------------------------------------------------------------------------------------------------- -- GetDominantFaceTexInfo: -- Collects compatible-shader descriptors for 'Obj', -- and triggers collection of per-face dominant-texture data. ------------------------------------------------------------------------------------------------- fn GetDominantFaceTexInfo Obj IgnoreBitmaps:False EscToAbort:False Material: MatchMat: = ( local TimeStart = TimeStamp() local MatInfoList = GetObjTerrainFaceData Obj Material:Material MatchMat:MatchMat -- Collect dominant-textures face-data for listed material-structs: PushPrompt "Finding dominant textures for terrain-faces..." local Success = True for Item in MatInfoList while Success and (Success = not (EscToAbort and Keyboard.EscPressed)) do ( Success = Item.FindDominantFaceTextures IgnoreBitmaps:IgnoreBitmaps EscToAbort:EscToAbort ) PopPrompt() format "Dominant-texmap search took % seconds\n" ((TimeStamp() - TimeStart) / 1000.0) return MatInfoList ), ------------------------------------------------------------------------------------------ -- BalanceTexWeights: -- Balance texmap-colour weights - they should add up to 1.0 ------------------------------------------------------------------------------------------ fn BalanceTexWeights texWeights = ( local totalWeight = 0 for texWeight in texWeights do ( totalWeight += texWeight ) if (totalWeight != 1) do ( local mult = (1.0 / totalWeight) texWeights = for texWeight in texWeights collect (texWeight * mult) ) return texWeights ) ) gRsTerrainHelpers = RsTerrainHelpers() -- Legacy function: fn GetDominantTextureFromTerrainFace obj mat matID faceId = ( -- Get submaterial: local MatchMat = unsupplied if (isKindOf Mat MultiMaterial) and (isKindOf MatId Integer) do ( local MatIdx = FindItem Mat.MaterialIDList MatID if (MatIdx != 0) do ( MatchMat = Mat.MaterialList[MatIdx] ) ) -- Collect dominant-texture terrain-material data from object: local MatInfoList = gRsTerrainHelpers.GetDominantFaceTexInfo Obj Material:Mat MatchMat:MatchMat -- Search collected data for material/texmap that uses 'FaceId': local MatFound = False local DiffusePath = undefined for MatItem in MatInfoList while (not MatFound) do ( if (MatItem.GetMatFaces())[FaceId] do ( MatFound = True local TexFound = False for TexNum = 1 to MatItem.DiffuseTexPaths.Count while (not TexFound) do ( if MatItem.DiffuseFaces[TexNum][FaceId] do ( TexFound = True DiffusePath = MatItem.DiffuseTexPaths[TexNum] ) ) ) ) return DiffusePath ) -- TEST -- IF FALSE DO ( clearlistener() print "IF YOU CAN SEE THIS, SOMEBODY LEFT THE TEST-CODE IN" gc() local TimeStart = TimeStamp() local obj = $ --local stuff = (gRsTerrainHelpers.GetObjTerrainFaceData Obj EscToAbort:True) local stuff = (gRsTerrainHelpers.GetDominantFaceTexInfo Obj EscToAbort:True) --IgnoreBitmaps:False) --local stuff = (gRsTerrainHelpers.GetDominantFaceTexInfo (copy Obj.Mesh) Material:Obj.Material EscToAbort:True) --IgnoreBitmaps:False) --local stuff = (gRsTerrainHelpers.GetObjTerrainFaceData (copy Obj.Mesh) Material:Obj.Material EscToAbort:True) --IgnoreBitmaps:False) --local Verts = stuff[2].GetVertsUsingTexmap 0 format "Took % seconds\n" ((timeStamp() - timeStart) / 1000.0) --stuff[1].GetFaceTexWeights() print stuff local MatThing = stuff[1] print (MatThing.GetMatFaces()).numberset for n = 1 to 4 do ( print (MatThing.GetDiffuseFaces())[n].numberset ) print (MatThing.GetFacesUsingTexmap 1) print (MatThing.GetFacesUsingTexmap 2) print (MatThing.GetFacesUsingTexmap 3) print (MatThing.GetFacesUsingTexmap 4) --MatThing.SwapTexmaps 1 2 SwapMatSlots:False SwapLookups:True RsTerrainShaderTypes[3].GetClrFromWeights (RsTerrainShaderTypes[3].GetWeightsFromClr [0.1,0.5,0.1]) --ok )