Wire Chaos


A 3dsMax-Tool written in MaxScript


2017 April 24 - 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


increase & decrease count of wires


distribute the points on the surface of any object


WireChaos GUI in 3dsMax


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