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

1872 lines
46 KiB
Plaintext
Executable File

/*
Go with the flow
Flow map generator
*/
filein (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms")
RsCollectToolUsageData (getThisScriptFilename())
escapeEnable = true
options.printAllElements = false
--helpers
--filein ( theWildWest + "script/max/rockstar_north/maps/FlowVortex.ms" )
/*
struct CPV
(
index, colour
)
*/
struct FlowMapperConfig
(
theWildWest = RsConfigGetWildWestDir(),
configPath = theWildWest + "etc/config/general/flowmapper.dat",
centreFlowBias = 1.0,
flowWidthMin = 10.0,
flowWidthMax = 50.0,
shoreDrag = 0.15,
slopeScale = 1.0,
quality = 1,
flowMapPath = (RsConfigGetArtDir() + "textures/"),
--------------------------------------
--CONFIGURATION HANDLING
--------------------------------------
fn createConfig =
(
fs = createFile configPath
--write defaults
format "--FlowMapper user config file\n\n" to:fs
format "cfg_centreFlowBias = %\n" centreFlowBias to:fs
format "cfg_flowWidthMin = %\n" flowWidthMin to:fs
format "cfg_flowWidthMax = %\n" flowWidthMax to:fs
format "cfg_slopeScale = %\n" slopeScale to:fs
format "cfg_quality = %\n" quality to:fs
format "cfg_flowMapPath = %\n" ("\""+flowMapPath+"\"") to:fs
--close filestream
close fs
),
fn loadConfig =
(
centreFlowBias = (getINISetting configPath "baseFlow" "cfg_centreFlowBias") as Float
flowWidthMin = (getINISetting configPath "baseFlow" "cfg_flowWidthMin") as Float
flowWidthMax = (getINISetting configPath "baseFlow" "cfg_flowWidthMax") as Float
shoreDrag = (getINISetting configPath "baseFlow" "cfg_shoreDrag") as Float
slopeScale = (getINISetting configPath "baseFlow" "cfg_slopeScale") as Float
quality = (getINISetting configPath "output" "cfg_quality") as Integer
flowMapPath = getINISetting configPath "output" "cfg_flowMapPath"
),
fn saveConfig =
(
--print configPath
if doesFileExist configPath == true then
(
--delete the old version
try
(
deleteFile configPath
)
catch
(
messagebox "Couldnt delete config file"
)
)
setINISetting configPath "baseFlow" "cfg_centreFlowBias" (centreFlowBias as string)
setINISetting configPath "baseFlow" "cfg_flowWidthMin" (flowWidthMin as string)
setINISetting configPath "baseFlow" "cfg_flowWidthMax" (flowWidthMax as string)
setINISetting configPath "baseFlow" "cfg_shoreDrag" (shoreDrag as string)
setINISetting configPath "baseFlow" "cfg_slopeScale" (slopeScale as string)
setINISetting configPath "output" "cfg_quality" (quality as string)
setINISetting configPath "output" "cfg_flowMapPath" ("\""+flowMapPath+"\"")
),
on create do
(
--print "\n\nconfig constructor called!!!\n\n"
--Check config exists and update UI values accordingly
--otherwise create a config file with defaults
if doesFileExist configPath == true then
(
--Load config
loadConfig()
--break()
)
else --create config
(
--createConfig()
saveConfig()
)
)
)
struct FlowSpline
(
name,
uid,
fn getUID name =
(
return uid
),
fn getName uid =
(
return name
)
)
struct FlowMapper
(
flowSplineItems = #(),
config = FlowMapperConfig(),
--UI
rolloutUI,
flowSplineListUI,
flowHelperID = dotNetObject "RSG.MaxUtils.MaxDictionary",
--Meshes
baseObject,
flowObject,
--Flow splines
flowSplines = #(),
flowSplineID = dotnetobject "RSG.MaxUtils.MaxDictionary", --key=node.handle value=node.name
--Flow Helper types
flowHelperTypes = #("Blocker", "Faster", "Slower"),
--Flowmap bake
bakeSize = 512,
bakeLength = bakeSize,
bakeWidth = bakeSize,
artFolderRoot = RsConfigGetArtDir(),
flowMapPath = config.flowMapPath,
flowMapTexturePath,
flowMapBmp,
quality = config.quality,
--vert positions
aVPos = #(),
--flow colours
aCPV = #(),
--Generation parameters
centreFlowBias = config.centreFlowBias,
slopeScale = config.slopeScale,
shoreDrag = 0.15,
obj,
--callbacks
cb_nodeSelChange = undefined,
cb_nodeRename = undefined,
cb_nodeDelete = undefined,
cb_undo = undefined,
--///////////////////////////////////////////////////////////////
--STRUCT FUNCTIONS
--//////////////////////////////////////////////////////////////
fn addToHelperDict helperObj =
(
if flowHelperID.ContainsKey(helperObj.handle) == true then
(
messageBox "This object is already a flow helper" title:"Error"
return false
)
else if flowHelperID.ContainsValue(helperObj.name) == true then --check for name in dict
(
messageBox "There is already a FlowHelper with the same name, please rename it" title:"Error"
return false
)
else
(
flowHelperID.add helperObj.handle helperObj.name
return true
)
return false
),
fn removeFromHelperDict helperObj =
(
if flowHelperID.ContainsKey(helperObj.handle) == true then
(
flowHelperID.remove(helperObj.handle)
)
),
--Returns a list of Flowhelpers that have been defined and stored in flowHelperID dict
fn getFlowHelperList =
(
helperList = #()
helperHnd = #()
it = flowHelperID.GetEnumerator()
nextIt = true
while nextIt do
(
item = it.current
append helperHnd item.key
nextIt = it.movenext()
)
format "helperHnd: %\n" helperHnd
if helperHnd.count > 0 then
(
for h in helperHnd do
(
if h != undefined then
(
append helperList (maxOps.getNodeByHandle h)
)
)
--helperList = for h in helperHnd collect (maxOps.getNodeByHandle h) where h != undefined
)
--return
helperList
),
fn getFlowSplinesFromObject =
(
--get the current selected object retreive flowsplines from it
obj = getCurrentSelection()
obj = obj[1]
--get the splines from the object
ss = StringStream ""
objHasAppData = getAppData obj 1
appData = #()
if objHasAppData != undefined then
(
ss = StringStream ( objHasAppData )
while not eof ss do
(
append appData (readValue ss)
)
)
--return
appData
),
--find the nearest point on a spline to a given position
fn nearestPointOnSpline pos spline splineIndex:1 =
(
pathParam = nearestPathParam spline splineIndex pos
uniformParam = pathToLengthParam spline splineIndex pathParam steps:50
closestPointOnSpline = lengthInterp spline splineIndex uniformParam steps:50
--return
closestPointOnSpline
),
fn normalizeValue vMin vMax val =
(
newVal = vMin + (1.0 / vMax) * (val - vMin)
newVal = vMax - vMin
--return
newVal
),
--find the closest point on a given spline from a given position of interest
fn distanceToSpline pos spline =
(
d = undefined
splinePos = FlowMapper.nearestPointOnSpline pos spline
d = distance splinePos pos
--return
d
),
--find the sortest distance to a spline for an array of splines
fn closestSpline pos aSplines =
(
if aSplines == undefined then throw("Missing Array")
--assert (aSplines != undefined)
/*
closest = 99999
for spline in aSplines do
(
d = FlowMapper.distanceToSpline pos spline
--break()
if d < closest then
(
closest = d
)
)
format "closest: %\n" closest
--return
closest
*/
closest = #()
for spline in aSplines do
(
if spline != undefined do (
d = FlowMapper.distanceToSpline pos spline
splineDist = #(spline, d)
append closest splineDist
)
)
--return
closest
),
--From a given spline and index on that spline,
--return the vector for the spline at that point
fn getFlowDirection pos spline splineIndex:1 =
(
pathParam = nearestPathParam spline splineIndex pos
uniformParam = pathToLengthParam spline splineIndex pathParam steps:50
tangent = lengthTangent spline splineIndex uniformParam steps:50
--resetLengthInterp()
--return
tangent
),
--Average normalized vector from an array of splines
fn weightedAvgFlowDirection pos aSplines =
(
vec = [0, 0, 0]
for spline in aSplines do
(
--format "spline: %\n" spline
tempVec = FlowMapper.getFlowDirection pos spline[1]
--distance falloff
distFalloff = 1.0 / spline[2]^2
--distFalloff = amax distFalloff 2.0
vec += tempVec * distFalloff
)
vec = normalize(vec)
--return
vec
),
--weighted average of distances from splines
--takes in the two elemement array pairs of spline and distance, so we want element [2]
fn weightedAvgFlowDistance aDistances =
(
avg = 0
combined = 0
for d in aDistances do
(
combined += d[2]
)
avg = combined / aDistances.count
--return
avg
),
fn createFlowHelper helperType =
(
--spline selected?
obj = getcurrentselection()
obj = obj[1]
objClass = classOf obj
print objClass
if isKindOf obj shape != true then
(
messageBox "Not a spline!" title:"Selection invalid:"
return undefined
)
--not a flowspline?
if MatchPattern obj.name pattern:"flowSpline_*" then
(
messageBox "Spline is a flowspline_!" title:"Selection invalid:"
return undefined
)
--closed?
if not isClosed $ 1 then
(
messageBox "Spline isn't closed!" title:"Selection invalid:"
return undefined
)
--all good then prefix spline with name of type
obj.name = ("Flow_" + helperType + "_" + obj.name)
--setAppData channel 2
setAppData obj 2 ("Flow_" + helperType)
--add to FlowHelperID dict
addToHelperDict obj
--add watermesh handle to node appdata
return obj
),
------------------------------------------
--Convert the flow vector to a colour
------------------------------------------
fn getFlowColour vec =
(
redXCol = ( vec.x * 127) + 128
greenYCol = ( vec.y * 127) + 128
flowColour = color redXCol greenYCol 127
--return
flowColour
),
--Take the accumulated generated CPV and apply to the mesh
fn setFlowColours obj =
(
aCPVCount = aCPV.count
rolloutUI.prgProcess.color = red
for v = 1 to aCPVCount do
(
--convert aCPV value to a colour
colour = getFlowColour aCPV[v]
--set flow vec colour
polyOp.setVertColor obj 0 v colour
rolloutUI.prgProcess.value = 100 * v / aCPVCount
)
--reset progress
rolloutUI.prgProcess.value = 0
),
fn createFlowObject obj =
(
-----------------------------------------
--get object selected, create a copy
-----------------------------------------
if classOf obj != Editable_Poly then
(
messagebox "Select an Editable Poly object"
return false
)
--store a referenc to the original object in the struct
baseObject = obj
--create a copy fo the obj to make a flow object that can be uprezzed to the quality we want
flowObject = copy obj
flowObject.name = obj.name + "FLOW"
--subdivide for detail
quality = rolloutUI.spnQuality.value
smoothMod = TurboSmooth name:"Detail" iterations:quality
addModifier flowObject smoothMod
addModifier flowObject (Turn_To_Poly())
collapseStack flowObject
--add vertex color material
flowObject.material = standardMaterial()
flowObject.material.diffuseMap = Vertex_Color()
flowObject.material.name ="FLOW_VERT_COLOR"
--show vertex colours
flowObject.showVertexColors = on
flowObject
),
fn defaultBakePath =
(
imagesDir = getDir #image
flowMapPath = imagesDir
rolloutUI.txtBakePath.text = flowMapPath
flowMapPath
),
fn getBakePath =
(
--bakePath = artFolderRoot+"Textures/_AREAS/Countryside/CS3_05/"+flowObject.name+"_bake.bmp"
if flowMapPath == undefined then
(
defaultBakePath()
return flowMapPath
)
bakeRoot = artFolderRoot+"Textures"
grabPath = getOpenFileName caption:"FlowMap Bake Location:" filename:bakeRoot
--handle user cancel dialog
if grabPath == undefined then
(
return false
)
grabPath = substituteString grabPath "\\" "/"
--fo'git the file, we jus' want the path
flowMapPath = getFileNamePath grabPath
flowMapPath = substitutestring flowMapPath "\\" "/"
--bakePath += "/"+flowObject.name+"_bake.bmp"
--print bakePath
rolloutUI.txtBakePath.text = flowMapPath
--return
flowMapPath
),
fn generateFlowTextures =
(
--reset the bake dimensions
bakeWidth = bakeSize
bakeLength = bakeSize
--make sure the vfb is closed
if flowMapBmp != undefined then
(
undisplay flowMapBmp
)
--create uvwmap
uvmap = UVWMap()
addModifier baseObject uvmap
addModifier flowObject uvmap
ratio = 1
if uvmap.width > uvmap.length then
(
ratio = (uvmap.width / uvmap.length) as integer
if ratio < 1 then ratio = 1
for i = 1 to ratio by 2 do
(
bakeLength /= 2
)
)
else if uvmap.length > uvmap.width then
(
ratio = (uvmap.length / uvmap.width) as integer
if ratio < 1 then ratio = 1
for i = 1 to ratio by 2 do
(
bakeWidth /= 2
)
)
--format "bakeWidth: % bakeLength:% \n" bakeWidth bakeLength
--collapse stack
--render to texture
--create bakepath
flowMapTexturePath = flowMapPath+flowObject.name+".bmp"
--remove old bake texture
try
(
if doesFileExist (flowMapTexturePath) == true then
(
deleteFile flowMapTexturePath
)
)
catch
(
messagebox "couldnt delete old bake texture"
)
--Clear all render elements
flowObject.iNodeBakeProperties.removeAllBakeElements()
diffuseBake = diffusemap()
--background colour
diffuseBake.backgroundColor = ( color 127 127 127 0 )
--set size
diffuseBake.outputSzX = bakeWidth
diffuseBake.outputSzY = bakeLength
--file location
diffuseBake.fileName = flowMapTexturePath
diffuseBake.fileType = flowMapTexturePath
diffuseBake.filterOn = true --enable filtering
diffuseBake.shadowsOn = false --disable shadows
diffuseBake.lightingOn = false --disable lighting
diffuseBake.enabled = true --enable baking
--Preparing the object for baking:
flowObject.INodeBakeProperties.addBakeElement diffuseBake --add first element
flowObject.INodeBakeProperties.bakeEnabled = true --enabling baking
flowObject.INodeBakeProperties.bakeChannel = 1 --channel to bake
flowObject.INodeBakeProperties.nDilations = 5 --expand the texture a bit
select flowObject --we are baking the selection, so we select the object
--Call the renderer to bake both elements:
render rendertype:#bakeSelected vfb:off progressBar:true outputSize:[bakeSize, bakeSize]
--display the result
flowMapBmp = openBitMap flowMapTexturePath
if flowMapBmp != undefined then
(
display flowMapBmp
)
),
----------------------------------
--NEW VERSION
----------------------------------
fn lowFreqFlow =
(
--interface lookup
polyop_getvert = polyop.getvert
--get the splines from UI selection
--flowSplines = rolloutUI.lstFlowSplines.items
--get the flowsplines from the object AppData
flowSplines = getFlowSplinesFromObject()
--Check for any flow splines
if flowSplines.count == 0 then
(
messagebox "No flow splines selected"
return false
)
--get flowsplines
aFlows = #()
for splineHnd in flowSplines do
(
--get the obj from handle
itemName = maxOps.getNodeByHandle splineHnd
append aFlows itemName
)
--openEdges on baseObject mesh that will be enough detail to test against and faster than the flowObject
aBorderVertIdx = (polyop.getVertsUsingEdge flowObject (polyop.getOpenEdges flowObject)) as array
aBorderVerts = #()
for v in aBorderVertIdx do
(
--pos = baseObject.GetVertex v
pos = polyop_getvert flowObject v node:flowObject
append aBorderVerts pos
)
--Width flow scaling from UI vars
wMin = rolloutUI.spnFlowWidthMin.value
wMax = rolloutUI.spnFlowWidthMax.value
--wDiff = wMax - wMin
wDiff = 1.0 / (wMax - wMin)
wMinBase = wMin * wDiff
format "wMin: % wMax: % wDIff: % wMinBase: %\n" wMin wMax wDiff wMinBase
--iterate verts
numVerts = flowObject.vertices.count
for i=1 to numVerts do
(
--vPos
vPos = polyop_getvert flowObject i node:flowObject
--lets stuff this into a struct array for use in later passes
append aVPos vPos
--spline distances
flowDistance = FlowMapper.closestSpline vPos aFlows
--centre flow vec
flowVec = FlowMapper.weightedAvgFlowDirection vPos flowDistance
flowVec = normalize(flowVec)
--init flowScale
--flowScale = 1.0
------------------------------------------------------------------
--WIDTH SCALING
------------------------------------------------------------------
--Now find the closest border egdes verts for width scaling
aBVDistance = #()
for v = 1 to aBorderVerts.count do
(
d = distance aBorderVerts[v] vPos
append aBVDistance d
)
sort aBVDistance
--format "aBVDistance: %\n" aBVDistance
flowWidth = aBVDistance[1] + aBVDistance[2]
--flowClose = amin aBVDistance[1] aBVDistance[2]
--format "flowWidth: %" flowWidth
--flowWidth = amin flowWidth wMin
--flowWidth = amax flowWidth wMax
--if flowWidth > wMin and flowWidth < wMax then
--(
--flowScale = abs(0.0 - wMinBase + (flowWidth^2 * wDiff))
--flowScale = 1.0 - (flowWidth * wDiff)
--flowScale = 1.0 - (0.0 - wMinBase + (flowWidth * wDiff))
flowScale = flowWidth * (1.0 / wMax)
--flowScale = amin flowScale 1.0
--)
--format "flowscale: %\n" flowScale
---------------------------------------------------------------
--BORDER PROXIMITY
---------------------------------------------------------------
--are we a border vert?
if finditem aBorderVertIdx i != 0 then
(
--print "Border\n"
flowScale *= shoreDrag
)
--format "before: %" flowVec
flowVec.x *= flowScale
flowVec.y *= flowScale
flowVec.z = 0.5
--format " after: %\n" flowVec
append aCPV flowVec
--update progress
rolloutUI.prgProcess.value = 100 * i / numVerts
)
rolloutUI.prgProcess.value = 0
),
--------------------------------------------------------------------------------------------------------------------
--FLOW SLOPE VECTOR
-- For each vertex point in the mesh work out the slope vector using the connected edges
-- and colour it according to how fast the mesh is rising or falling at that point and in which direction
--------------------------------------------------------------------------------------------------------------------
fn flowSlopeVector =
(
--add a new vertexpaint modifier to hold the new data
--flowSlopeColour = VertexPaint name:"SlopeFlow" mapChannel:0 colorBy:2 layerMode:"Multiply"
--addModifier flowObject flowSlopeColour
numVerts = flowObject.vertices.count
--Iterate obj verts
for v = 1 to numVerts do
(
--get face normals from surrounding faces and compute average vector weighted by face area
--For each vertex pos
--vPos = polyop.getvert flowObject v
vPos = aVPos[v]
--get a normal using the surrounding faces
--get faces for vert
vFaces = polyop.getFacesUsingVert flowObject v
vFaces = vFaces as array
--get faceNormals
vNormal = [0, 0, 0]
for f =1 to vFaces.count do
(
fNormal = polyop.getFaceNormal flowObject vFaces[f]
vNormal += fNormal
)
vNormal = normalize vNormal
--colour vertex
flowColour = [0, 0, 0]
dotZ = dot [0, 0, 1] vNormal
--create a rotation matrix
matrix = matrix3 1
-- angle of rotation:
ang = acos (dot vNormal matrix.row3)
-- axis of rotation:
axis = normalize (cross vNormal matrix.row3)
-- rotate about pos of tm:
rot_tm = preRotate matrix (quat ang axis)
rot_tm.row4 = vPos
/* DEBUG
--create point help
--viz = point pos:vPos size:4 isSelected:on
--viz.transform = rot_tm
DEBUG */
-- new X and Y axis:
xVec = rot_tm.row1
yVec = rot_tm.row2
--dot the xVec and yVec with the world Z axis to get gradient
dotX = dot [0, 0, 1] xVec
dotY = dot [0, 0, 1] yVec
--format "v: % dotX: % dotY: % \n" v dotX dotY
--Create the colour
--slopeScale up the dot prodcut value as we are considering a hemisphere rather than a full sphere direction
gradientScale = 1.0 - (slopeScale * dotZ)
flowColour.x = gradientScale * ( slopeScale * dotY * 0.5 ) + 0.5
flowColour.y = gradientScale * ( slopeScale * dotX * 0.5 ) + 0.5
flowColour.z = 0.5
--format "slopeCol: %\n" flowColour
--get current colour Point3
--colourNow = polyOp.getMapVert flowObject 0 v
colourNow = aCPV[v]
--format "colourNow: %\n" colourNow.colour
--create the combined color
newColour = [0.5, 0.5, 0.5]
newColour.x = colourNow.x + ( 0.5 - flowColour.x )
newColour.y = colourNow.y + ( 0.5 - flowColour.y )
newColour = normalize(newColour)
--format "newColour: %\n" newColour
--update array vert colour
aCPV[v] = newColour
--update progress
rolloutUI.prgProcess.value = 100 * v / numVerts
)
--reset progress
rolloutUI.prgProcess.value = 0
--Show the map colours
--show vertex colours
flowObject.showVertexColors = on
flowObject.vertexColorType = 5
flowObject.vertexColorMapChannel = 0
--------------------------------------------------------------------------------------------------------------------
-- EVALUATE USER FEATURE SPLINES
-- if the artists have created feature splines to influence direction of the flow then evaluate these
--and adjust the flow accordingly dependent on distance from the feature spline
--------------------------------------------------------------------------------------------------------------------
),
--///////////////////////////////////////////////////////////////////////////////////////////////
--FLOW HELPERS
--Determine the contribution that user placed flow helper objects make to the flowmap
--//////////////////////////////////////////////////////////////////////////////////////////////
fn evalHelpers obj =
(
--get bounds of the selected river mesh
objBB = nodeLocalBoundingBox obj
objBB_xDiff = objBB[1][1] - objBB[2][1]
objBB_yDiff = objBB[1][2] - objBB[2][2]
objBB_Radius = sqrt( objBB_xDiff^2 + objBB_yDiff^2 )
objBB_Centre = [ 0.5 * objBB_xDiff, 0.5 * objBB_yDiff, 0 ]
--for each helper see if its bounds intersect the selected mesh bounds
--flowHelpers = rolloutUI.lstFlowHelpers.items
aFlowHelpers = getFlowHelperList()
--format "aFlowHelpers: % \n" aFlowHelpers
--up vector for perpendicular vec calculation
upVec = [0, 0, 1]
--Get a list of flow helpers that lie within the bounds of our object so we cut down some work
aFHelpers = #()
for fh in aFlowHelpers do
(
--test intersection between flowhelper and river mesh
if (intersects obj fh) == true then
(
append aFHelpers fh
)
)
--format "aFHelpers % \n" aFHelpers
--with the helpers identified that are significant. Build capped versions for testing.
aCappedHelpers = #()
for fh in aFHelpers do
(
solidHelper = copy fh
solidHelper.name = (fh.name + "_CAP")
addModifier solidHelper (Turn_to_Mesh())
nm = NormalModifier()
nm.flip = true
addModifier solidHelper nm
append aCappedHelpers solidHelper
)
--format "aCappedHelpers % \n" aCappedHelpers
--Now we got our list of helpers, lets figure out what effect they will have
numVerts = obj.vertices.count
--Iterate obj verts
for v = 1 to numVerts do
(
--vtx Position
vPos = aVPos[v]
vRay = ray vPos upVec
--iterate helpers
for fh = 1 to aFHelpers.count do
(
--get type and properties
fhNode = aFHelpers[fh]
fhType = classof fhNode
--break()
--need to match name prefix for type
--format "fhNode.name % \n" fhNode.name
if MatchPattern fhNode.name pattern:"Flow_Vortex*" then fhType = "FlowVortex"
else if MatchPattern fhNode.name pattern:"Flow_Blocker*" then fhType = "FlowBlocker"
else if MatchPattern fhNode.name pattern:"Flow_Slower*" then fhType = "FlowSlower"
else if MatchPattern fhNode.name pattern:"Flow_Faster*" then fhType = "FlowFaster"
case fhType of
(
"FlowBlocker":
(
--print "WE GOT A FLOWBLOCKEEEEER!!!"
--test if inside shape
hit = intersectRay aCappedHelpers[fh] vRay
if hit != undefined then --we inside the shape outline, so full stop on flow vector
(
aCPV[v] = [0.0, 0.0, 0.0]
)
else --block proportionate to distance from closest point to edge
(
--distance
splinePos = nearestPointOnSpline vPos aFHelpers[fh]
d = distance vPos splinePos
d = 1.0 / (d*d)
--tangent
fhTangent = getFlowDirection vPos aFHelpers[fh]
fhTangent = normalize fhTangent
--normal
fhNormal = [fhTangent[2], -fhTangent[1], fhTangent[3]]
fhNormal = normalize fhNormal
--current flow
flowNow = aCPV[v]
flowNow = normalize flowNow
blockerDir = dot fhNormal flowNow
newFlow = [flowNow[1]*blockerDir*d, flowNow[2]*blockerDir*d, 0.0]
--format "newFlow: %\n" newFlow
aCPV[v] = flowNow - newFlow
)
)
"FlowFaster":
(
--print "WE GOT A FLOWFASTEEEEER!!!"
--test if inside shape
hit = intersectRay aCappedHelpers[fh] vRay
if hit != undefined then --we inside the shape outline, so increase flow vector
(
--print "hit!"
--current flow
flowNow = aCPV[v]
flowNow.x = flowNow.x * 2.0
flowNow.y = flowNow.y * 2.0
if flowNow.x > 1.0 or flowNow.y > 1.0 then
(
f = amax flowNow.x flowNow.y
f = 1.0 / f
flowNow.x = f * flowNow.x
flowNow.y = f * flowNow.y
)
aCPV[v] = [flowNow.x, flowNow.y, 0.5]
)
else --block proportionate to distance from closest point to edge
(
--distance
splinePos = nearestPointOnSpline vPos aFHelpers[fh]
d = distance vPos splinePos
d = 1.0 / d
--current flow
flowNow = aCPV[v]
--flowNow = normalize flowNow
--blockerDir = dot fhNormal flowNow
newFlow = [flowNow.x + d, flowNow.y + d, 0.0]
--format "newFlow: %\n" newFlow
aCPV[v] = flowNow + newFlow
)
)
"FlowSlower":
(
--print "WE GOT A FLOWSLOWEEEEER!!!"
--test if inside shape
hit = intersectRay aCappedHelpers[fh] vRay
if hit != undefined then --we inside the shape outline, so increase flow vector
(
--print "hit!"
--current flow
flowNow = aCPV[v]
flowNow.x = flowNow.x * 0.5
flowNow.y = flowNow.y * 0.5
aCPV[v] = [flowNow.x, flowNow.y, 0.5]
)
else --block proportionate to distance from closest point to edge
(
--distance
splinePos = nearestPointOnSpline vPos aFHelpers[fh]
d = distance vPos splinePos
d = 1.0 / d
--current flow
flowNow = aCPV[v]
--flowNow = normalize flowNow
--blockerDir = dot fhNormal flowNow
newFlow = [flowNow.x - d, flowNow.y - d, 0.0]
--format "newFlow: %\n" newFlow
aCPV[v] = newFlow
)
)
)
)
--progress
rolloutUI.prgProcess.value = 100 * v / numVerts
)--vert iterator
--DEBUG
--hObj = copy flowObject
--setFlowColours hObj
--/DEBUG
--reset progress
rolloutUI.prgProcess.value = 0
--delete capped helpers
for n in aCappedHelpers do delete n
),
--//////////////////////////
--main process control func
--main process control func
--main process control func
--//////////////////////////
fn doFlows =
(
windows.processPostedMessages ()
--print self.flowSplineListUI
--set coordinate system to world
setRefCoordSys #world
--clear the arrays
aCPV = #()
aVPos = #()
--get current object selection
obj = getcurrentselection()
obj = obj[1]
--persist object
persistObj = obj
collapseStack obj
--set struct var for the flowObject that other functions will use
createFlowObject(obj)
--call low freq flow generation
success = lowFreqFlow()
if success == false then
(
delete flowObject
return false
)
success = flowSlopeVector()
--if not success then return false
--call high freq flow generation
evalHelpers(flowObject)
--set the flow colour from CPV array
setFlowColours(flowObject)
--generate textures from cpv layers
generateFlowTextures()
--apply flow map to base mesh
RstSetVariable obj.material 3 flowMapTexturePath
--clean up
delete flowObject
collapseStack persistObj
--select obj
--trash compacter
gc
),
--/////////////////////////////////////////////
--Callbacks ///////////////////////////////////
--/////////////////////////////////////////////
fn onNodeSelChange =
(
--query the object for appData to see if it has any splines associated with it or conversely any object
obj = getCurrentSelection()
obj = obj[1]
if obj != undefined then
(
--so we have an object at least
--format "obj: % \n" obj.name
appData = #()
objHasAppData = getAppData obj 1
--format "objHasAppData: %\n" objHasAppData
if objHasAppData != undefined then
(
--print "has appData\n"
--check its an editable poly
if isKindOf obj.baseobject Editable_Poly == true then
(
ss = StringStream ( objHasAppData )
while not eof ss do
(
append appData (readValue ss)
)
splines = #()
--check first value in appData to see if its a node handle
--test = (appData[1] as IntegerPtr)
--format "test % \n" test
--print appData.count
if appData.count != 0 then
(
if (appData[1] as IntegerPtr) != undefined then
(
--Now deal with based on type
for item in appData do
(
--get name from node
spline = maxOps.getNodeByHandle (item as Integer)
--format "spline: %\n" spline
if spline != undefined then
(
append splines spline.name
)
)
)
)
--format "splines: %\n" splines
--update struct var holding flowSplines
flowSplines = splines
--finally update the UI with the splines associated with this object
rolloutUI.lstFlowSplines.items = splines
)
)
else
(
--Update the UI with an empty array
rolloutUI.lstFlowSplines.items = #()
)
)
else
(
--Update the UI with an empty array
rolloutUI.lstFlowSplines.items = #()
)
),
fn onNodeDelete =
(
--------------------------------------
--First case deal with flowsplines
--------------------------------------
--get its appdata
obj = callbacks.notificationParam()
--print obj
objHasAppData = getAppData obj 1
if objHasAppData != undefined then
(
--get the splines it holds from its appData
appData = #()
--check its an editable poly
if isKindOf obj.baseobject Editable_Poly == true then
(
ss = StringStream ( objHasAppData )
while not eof ss do
(
append appData (readValue ss)
)
)
--go through each spline and remove its appData
for item in appData do
(
--get the spline from handle
spline = maxOps.getNodeByHandle (item as Integer)
deleteAppData spline 1
)
--Update the UI with an empty array
rolloutUI.lstFlowSplines.items = #()
)
--------------------------------------------------
--Second case deal with deleting flowObjects
--------------------------------------------------
if classOf obj == FlowVortex then
(
format "FlowVortex: % \n" obj.name
--get Flow helper list
helperList = rolloutUI.lstFlowHelpers.items
format "helperList: % \n" helperList
found = findItem helperList obj.name
format "found: %\n" found
if found != 0 then
(
newList = deleteItem helperList found
rolloutUI.lstFlowHelpers.items = newList
)
)
),
fn onNodeRename =
(
return false
),
---------------------------
--If we have a scene undo re-check the ui lists incase somethign was deleted and then undone.
---------------------------
fn onSceneUndo =
(
--Check flow helpers
--get current UI list
helperList = getFlowHelperList()
for o in $helpers do
(
if isKindOf o FlowVortex == true then
(
--check if its in the list
found = finditem o.name helperList
if found != 0 then
(
)
)
)
)
)
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--Destroy an open dialog if exists
--if flowMapperTool != undefined then
--(
-- destroydialog flowMapperTool.rolloutUI
--)
--create tool
fn pickFilter obj = classOf obj == SplineShape and matchpattern obj.name pattern:"flowspline_*"
if FlowMapperUI != undefined then
(
destroydialog FlowMapperUI
)
--CREATE TOOL STRUCT INSTANCE
--flowMapperConfig = FlowMapperConfig()
flowMapperTool = FlowMapper()
rollout FlowMapperUI "FlowMapper"
(
group "FlowSplines"
(
listbox lstFlowSplines
pickbutton btnPickSplines "Pick" filter:pickFilter across:3
button btnRemoveSpline "Remove"
button btnClearPicked "Clear"
)
group "Base Flow"
(
--spinner spnCentreFlowBias "Centre Flow Bias:" range:[0.1, 4.0, 1.0] type:#float
spinner spnFlowWidthMin "Flow Width Min:" range:[1, 100, 5] type:#float
spinner spnFlowWidthMax "Flow Width Max:" range:[1, 100, 40] type:#float
spinner spnShoreDrag "Shore Drag:" range:[0.001, 1.0, 0.15] type:#float
)
group "Slope"
(
spinner spnSlopeScale "Slope Scale:" range:[1.0, 4.0, 1.0] type:#float
)
group "Flow helpers"
(
listbox lstFlowHelpers
dropdownlist ddlFlowHelpers items:#("Blocker", "Slower", "Faster") across:4
button btnCreateFlowHelper "Create!"
button btnRemoveFlowHelper "Remove"
button btnDestroyFlowHelper "Destroy!"
)
group "FlowMap"
(
spinner spnQuality "Quality: " range:[1,8,1] type:#integer
edittext txtBakePath "Path" style_sunkenedge:true width:185 height:20 labelontop:true readonly:true across:2
button btnBakePath "..." align:#right offset:[0, 18]
button btnViewLast "View Last" align:#left
)
button btnDo "Go With The Flow!" width:200
progressbar prgProcess color:orange
---------------------------
--EVENTS dear boy
---------------------------
on FlowMapperUI open do
(
--Set UI values to saved config values
flowMapperTool.config.loadConfig()
--set spinner value struct siblings
spnSlopeScale.value = flowMapperTool.config.slopeScale
--spnCentreFlowBias.value = flowMapperTool.config.centreFlowBias
spnFlowWidthMin.value = flowMapperTool.config.flowWidthMin
spnFlowWidthMax.value = flowMapperTool.config.flowWidthMax
--mesh quality
spnQuality.value = flowMapperTool.config.quality
--set the flowmap bake path to the config setting
txtBakePath.text = flowMapperTool.config.flowMapPath
--populate the flow helpers with any found in the current scene
flowHelpers = #()
for o in $shapes do
(
--check appdata
appData = getAppData o 2
if appData != undefined and matchPattern appData pattern:"Flow_*" then
(
append flowHelpers o.name
success = flowMapperTool.addToHelperDict o
format "Added to FlowHelper dict?: %\n" success
)
)
lstFlowHelpers.items = flowHelpers
--clear out callbacks incase they are present from before
--flowMapperTool.cb_nodeSelChange = undefined
--flowMapperTool.cb_nodeDelete = undefined
--flowMapperTool.cb_nodeRename = undefined
callbacks.removeScripts id:#FlowMapper
--gc light:true
--register callbacks
--flowMapperTool.cb_nodeSelChange = NodeEventCallback mouseUp:true selectionChanged:flowMapperTool.onNodeSelChange()
flowMapperTool.cb_nodeSelChange = callbacks.addScript #selectionSetChanged "flowMapperTool.onNodeSelChange()" id:#FlowMapper
flowMapperTool.cb_nodeDelete = callbacks.addScript #nodePreDelete "flowMapperTool.onNodeDelete()" id:#FlowMapper
flowMapperTool.cb_nodeRename = callbacks.addScript #nodeNameSet "flowMapperTool.onNodeRename()" id:#FlowMapper
flowMapperTool.cb_undo = callbacks.addScript #sceneUndo "flowMappertool.onSceneUndo()" id:#FlowMapper
)
on FlowMapperUI close do
(
--remove callbacks
callbacks.removeScripts id:#FlowMapper
)
on btnPickSplines picked spline do
(
--get whats there
--items = lstFlowSplines.items
items = for i in flowMapperTool.flowSplines collect i
--this.flowSplineItems = items
--add pick to it
--check for dupes
for item in items do
(
if spline.name == item then
(
return false
)
)
--create a new FlowSpline
--newFlowSpline = FlowSpline()
--get objects unique handle
hnd = spline.handle
--add to dict
--flowMapperTool.flowSplineID.add spline.name hnd
--newFlowSpline.name = obj.name
--newFlowSpline.uid = hnd
append flowMapperTool.flowSplines spline.name
--append items obj.name
lstFlowSplines.items = for i in flowMapperTool.flowSplines collect (i as string)
--this.flowSplineItems = items
--add the spline to the object app data for later retreival
--add object handle to spline app data to make it exclusive
selObj = $selection[1]
selObjHnd = selObj.handle
deleteAppData spline 1
setAppData spline 1 (selObjHnd as string)
--get the appData for the object as an array, then add the newly picked spline to
--it and re-add the appData to the object
ss = StringStream ""
objHasAppData = getAppData selObj 1
appData = #()
if objHasAppData != undefined then
(
ss = StringStream ( objHasAppData )
while not eof ss do
(
append appData (readValue ss)
)
)
--Now append the new spline to the appData
appendIfUnique appData hnd
--Bung it back into the obejct
ss = StringStream ""
for d in appData do print d to:ss
setAppData selObj 1 ss
)
--remove spline from object
on btnRemoveSpline pressed do
(
--check an object is selected with flowspline data
selObj = $selection[1]
objHasAppData = getAppData selObj 1
--break()
if objHasAppData == undefined then
(
return false
)
--check a flowspline is selected in the ui list
items = lstFlowSplines.items
selectedItem = lstFlowSplines.selected
selectedItemIdx = lstFlowSplines.selection
--find the flowspline handle and remove it from the object data
appData = #()
ss = StringStream ( objHasAppData )
while not eof ss do
(
append appData (readValue ss)
)
--check for empty appData
if appData.count == 0 then
(
return false
)
aNewSplines = #()
removeSplineHnd = undefined
for splineHnd in appData do
(
--splineHnd = flowMapperTool.flowSplineID.Item spline.name
spline = maxOps.getNodeByHandle (splineHnd as Integer)
splineName = spline.name
if spline.name != selectedItem then
(
append aNewSplines splineHnd
)
else
(
removeSplineHnd = splineHnd
)
)
--add the new list to the object
ss = StringStream ""
for d in aNewSplines do print d to:ss
setAppData selObj 1 ss
--remove the object handle from the spline data
killSpline = maxOps.getNodeByHandle (removeSplineHnd as Integer)
deleteAppData killSpline 1
--remove the spline from flowsplines struct array
format "flowSplines: %\n" flowMapperTool.flowSplines
flowMapperTool.flowSplines = deleteItem flowMapperTool.flowSplines selectedItemIdx
--remove the spline from the ui list
aNewList = deleteItem items selectedItemIdx
lstFlowSplines.items = aNewList
--done
)
--clear the listbox of items
on btnClearPicked pressed do
(
--get the current selected object retreive flowsplines from it
obj = getCurrentSelection()
obj = obj[1]
--get the splines from the object
ss = StringStream ""
objHasAppData = getAppData obj 1
appData = #()
if objHasAppData != undefined then
(
ss = StringStream ( objHasAppData )
while not eof ss do
(
append appData (readValue ss)
)
)
--erase spline appData
for splineHnd in appData do
(
--splineHnd = flowMapperTool.flowSplineID.Item spline.name
spline = maxOps.getNodeByHandle (splineHnd as Integer)
if spline != undefined then
(
deleteAppData spline 1
)
)
deleteAppData obj 1
--clear UI values
lstFlowSplines.items = #()
flowMapperTool.flowSplines = #()
)
--flowMapPath
on btnBakePath pressed do
(
flowMapperTool.getBakePath()
if flowMapperTool.flowMapPath != undefined then
(
txtBakePath.text = flowMapperTool.flowMapPath
)
else
(
txtBakePath.text = flowMapperTool.config.flowMapPath
)
)
--CentreFlowBias
on spnCentreFlowBias changed arg do
(
flowMapperTool.centreFlowBias = spnCentreFlowBias.value
)
--shore drag
on spnShoreDrag changed arg do
(
flowMapperTool.shoreDrag = spnShoreDrag.value
)
--SlopeScale
on spnSlopeScale changed arg do
(
flowMapperTool.slopeScale = spnSlopeScale.value
)
--quality
on spnQuality changed arg do
(
flowMapperTool.quality = spnQuality.value
)
--FlowMap Helper Create
on btnCreateFlowHelper pressed do
(
--get dropdown selection
aItems = ddlFlowHelpers.items
sel = ddlFlowHelpers.selection
opt = aItems[sel]
case opt of
(
"Vortex":
(
flowVortex = flowMapperTool.createFlowHelper "Vortex"
if flowVortex != undefined then
(
--update UI list
curList = for i in lstFlowHelpers.items collect i
append curList flowVortex.name
lstFlowHelpers.items = curList
)
)
"Blocker":
(
blocker = flowMapperTool.createFlowHelper "Blocker"
if blocker != undefined then
(
--add to the helper list
curList = for i in lstFlowHelpers.items collect i
append curList blocker.name
lstFlowHelpers.items = curList
)
)
"Slower":
(
slower = flowMapperTool.createFlowHelper "Slower"
if slower != undefined then
(
--add to the helper list
curList = for i in lstFlowHelpers.items collect i
append curList slower.name
lstFlowHelpers.items = curList
)
)
"Faster":
(
faster = flowMapperTool.createFlowHelper "Faster"
if faster != undefined then
(
--add to the helper list
curList = for i in lstFlowHelpers.items collect i
append curList faster.name
lstFlowHelpers.items = curList
)
)
)
)
fn getHandleByName sel =
(
handle = undefined
it = flowMapperTool.flowHelperID.GetEnumerator()
nextIt = true
while nextIt do
(
dict = it.current
if sel == dict.value then
(
handle = dict.key
--format "handle: %\n" handle
return handle
)
nextIt = it.movenext()
)
)
on btnRemoveFlowHelper pressed do
(
--get dropdown selection
aItems = ddlFlowHelpers.items
sel = ddlFlowHelpers.selection
helperItem = aItems[sel]
--get the curent flowhelper from the list
sel = lstFlowHelpers.selected
--get the object using the flowhelper dict
handle = getHandleByName sel
obj = maxOps.getNodeByHandle handle
--remove the appdata defining the flowhelper type
deleteAppData obj 2
--remove the name prefix
for n in flowMapperTool.flowHelperTypes do
(
if matchPattern obj.name pattern:("*"+ n + "*") then
(
--rename
)
)
--remove it from the flowhelper dict
flowMapperTool.removeFromHelperDict helperItem
--remove it from the ui list
aItems = deleteItem aItems sel
ddlFlowHelpers.items = aItems
)
on lstFlowHelpers doubleClicked item do
(
--focus on selected item
aItems = lstFlowHelpers.items
sel = aItems[item]
try
(
handle = getHandleByName sel
fhNode = maxOps.getNodeByHandle handle
select ( fhNode )
max zoomext sel
)
catch
(
print "FlowHelper dictionary fetch error"
)
)
--DO
on btnDo pressed do
(
--check for anything valid selected to work on
obj = getCurrentSelection()
if obj.count != 0 then
(
flowSplines = flowMapperTool.getFlowSplinesFromObject()
if flowSplines.count != 0 then
(
--Save config settings
--update config
--flowMapperTool.config.centreFlowBias = spnCentreFlowBias.value
flowMapperTool.config.slopeScale = spnSlopeScale.value
flowMapperTool.config.flowWidthMin = spnFlowWidthMin.value
flowMapperTool.config.flowWidthMax = spnFlowWidthMax.value
flowMapperTool.config.quality = spnQuality.value
--flowMapperTool.config.flowMapPath = substitutestring ("\""+flowMapperTool.flowMapPath+"\"") "\\" "/"
flowMapperTool.config.flowMapPath = txtBakePath.text
--save config
flowMapperTool.config.saveConfig()
--Do that thang
flowMapperTool.doFlows()
)
else
(
messagebox "Are you sure you have a river mesh selected? I find no flowsplines assigned"
return false
)
)
else
(
messagebox "Nothing selected, I'll just have a rest till you sort yourself out :)"
return false
)
)
--View last
on btnViewLast pressed do
(
format "flowMapBmp: %\n" flowMapperTool.flowMapBmp
if flowMapperTool.flowMapBmp != undefined then
(
display flowMapperTool.flowMapBmp
)
)
)
flowMapperTool.rolloutUI = FlowMapperUI
createDialog FlowMapperUI width:250 style:#(#style_titlebar, #style_minimizebox, #style_sysmenu)