Wire Chaos


A 3dsMax-Tool written in MaxScript


April 24, 2017 - Tommy Dräger


Wire Chaos is a script that lets you distribute a point cloud along the surface of any given mesh. Afterwards the points gets randomly connect to bezier-splines. Those splines are manipulated by the gravity parameter (also compatible for inverse gravity). Have fun with the script.

Download:

simply extract it inside the scripts folder of 3dsMax

WireChaos.zip

increase & decrease gravity

Adjust gravity settings

increase & decrease count of wires

Adjust wire count

distribute the points on the surface of any object

Distribute points on surface

WireChaos GUI in 3dsMax

WireChaos GUI

WireChaosGUI.ms

rollout WireChaos "WireChaos" width:184 height:248
(
    groupBox    SimulationGroup     "Simulation"    pos:[8,152]     width:168 height:64
    button      solve               "Solve"         pos:[16,168]    width:152 height:40

    spinner     gravity_spinner     ""              pos:[88,120]    width:75 height:16 type:#integer range: [-1000,1000000,10]
    spinner     count_spinner       ""              pos:[88,96]     width:75 height:16 type:#integer range: [0,100000,100]
    groupBox    ParameterGroup      "Parameter"     pos:[8,80]      width:168 height:64

    label       lbl1                "Count"         pos:[24,96]     width:56 height:16
    label       lbl2                "Gravity"       pos:[24,120]    width:56 height:16

    pickButton  ctm_btn             "Pick Object"   pos:[16,24]     width:152 height:40 enabled:true
    groupBox    customMeshGroup     "Custom Mesh"   pos:[8,8]       width:168 height:64
    label footer "(c) FenixFox®Studios 2017"        pos:[8,224]     width:168 height:24

    fn GenerateRandomPointsOnSurface obj point_count =
    (   
        local custom_mesh       = snapshotasMesh obj
        local mesh_face_count   = custom_mesh.numfaces
        local point_array       = #()
        local area_size         = 0

        -- calculate the size from the area of faces
        for i = 1 to mesh_face_count do
        (
            -- getFaceArea <Mesh mesh> <facelist>
            area_size           += meshop.getFaceArea custom_mesh i
        )

        -- for an even distibution of the points on the surface
        local point_density     = point_count / area_size

        -- prepare an array for a random face selection
        -- simple array shuffle routine here
        local face_indicies     = for i = 1 to mesh_face_count collect i
        local random_indicies   = #()
        -- randomly transfer the elements from array A to B
        while face_indicies.count > 0 do
        (
            random_element      = random 1 face_indicies.count
            -- now transfer by cutting from A to B
            append      random_indicies face_indicies[random_element]
            deleteItem  face_indicies   random_element  
        )

        -- initialize a counter for the created points
        local current_point     = 0
        -- itterate trough the random faces -->(r_face) and create all points
        for r_face in random_indicies while current_point < point_count do 
        (       
            -- calculate a limit of points for each face
            local max_dense         = ceil((meshop.getFaceArea custom_mesh r_face) * point_density)

            -- getFace <mesh> <face_index_integer>
            -- returns vert_index as integer[3]
            local face              = getFace custom_mesh r_face

            -- getVert <mesh> <vert_index_integer>
            -- returns vertex as pos3
            -- those verticies are going to use later as "barycentric coords" [use wiki]
            local vert1             = (getVert custom_mesh face.x)
            local vert2             = (getVert custom_mesh face.y)
            local vert3             = (getVert custom_mesh face.z)

            --loop from 1 to the number of max allowed points on the face
            for i = 1 to max_dense while current_point < point_count do 
            (
                -- save it later to an array
                local n_obj         = point wirecolor:green 
                -- the normalvector information stored in his matrix are assign to the TM
                n_obj.transform     = matrixFromNormal (getFaceNormal custom_mesh r_face)

                -- craete a randomized X and Y
                local x             = random 0.0 1.0
                local y             = random 0.0 1.0

                --if the sum is greater than 1, subtract them from 1.0
                if x + y > 1.0 do  
                (
                    x               = 1.0 - x
                    y               = 1.0 - y                       
                )
                --the third bary coord is 1.0 minus the other two
                local z             = 1.0 - x - y 
                --set position using barycentric coords.
                -- add all 3 stretched/squeezed verts together
                n_obj.pos           = (vert1*x + vert2*y + vert3*z) 
                current_point       += 1
                append point_array n_obj.pos    
                delete n_obj
            ) -- for i loop end
        ) -- for r_face loop end
        return point_array
    ) -- function end

    -- shuffle array function
    fn shuffle arr =
    (
        for counter = arr.count to 1 by -1 collect (swap arr[random 1 counter] arr[counter])
    )

    fn GenerateSplines p_array =
    (
        local points    = shuffle p_array
        local grav      = gravity_spinner.value
        local limit     = points.count - (mod points.count 2)
        local chroma    = #(    [0,104,55],
                                [48,163,85],
                                [120,198,122],
                                [193,230,153],
                                [255,253,206])

        for i = 1 to limit by 2 do
        (
            local p1 = points[i]
            local p2 = points[i+1]

            s = splineShape()
            addnewspline s

            addKnot s 1 #corner #curve p1 
            addKnot s 1 #corner #curve p2 

            updateShape s

            setKnotType s 1 1 #bezier
            setKnotType s 1 2 #bezier 

            in_vec          = p1 - [0,0,grav]
            out_vec         = p2 - [0,0,grav]

            setOutVec s 1 1 in_vec
            setInVec s 1 2 out_vec      

            s.adaptive  = true
            s.wirecolor     = chroma[random 1 5]
            --s.wirecolor   = color 255 240 0
            s.pivot         = s.center

            updateShape s
        )-- for i end
    )-- function end

    on solve pressed  do
    (
        if getnodebyname(ctm_btn.text) != undefined do
        (
            obj             = getnodebyname(ctm_btn.text)
            p               = GenerateRandomPointsOnSurface obj count_spinner.value
            GenerateSplines p
        )
    )

    on ctm_btn picked obj do
    (
        fn shape_filt obj   = isKindOf obj Geometry
        if obj != undefined do
        (
            ctm_btn.text    = obj.name
        )   
    )

    --  dotnet hack to change the icon of the rollout
    on WireChaos open do
    (
        d           = (windows.getChildHWND 0 WireChaos.title)[1]
        WM_SETICON  = 0x0080
        ICON_SMALL  = 0
        icon        = dotnetobject "System.Drawing.Icon" (getdir #scripts + 
                                            "\WireChaos\icon\favicon.ico")
        windows.SendMessage d WM_SETICON ICON_SMALL icon.handle
    )   
)
createdialog WireChaos 184 248