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