--	-----------------------------------------------------------------------------------------------------------------------------
--	C4 v0.4 by Light @ 21.09.2006 00:16:46															www.orionflame.com
--	-----------------------------------------------------------------------------------------------------------------------------
global C4

struct C4
(
	-------------------------------------
	--// Editable Poly Related Methods //--
	-------------------------------------
	
	fn convertSOs sel elements arr =
	(
		local geuv = polyOp.getEdgesUsingVert
		local gfuv = polyOp.getFacesUsingVert
		local gvue = polyOp.getVertsUsingEdge
		local gfue = polyOp.getFacesUsingEdge
		local gvue = polyOp.getVertsUsingEdge
		local gvuf = polyOp.getVertsUsingFace
		local geuf = polyOp.getEdgesUsingFace
		local gluf = polyOp.getElementsUsingFace
		local fns = #(#(undefined, geuv, geuv, gfuv, gfuv), #(gvue, undefined, undefined, gfue, gfue), #(gvue, undefined, undefined, gfue, gfue), #(gvuf, geuf, geuf, undefined, gluf), #(gvuf, geuf, geuf, undefined, undefined))
		local theNum = arr[1]; for i = 2 to arr.count do ( elements = if fns[theNum][arr[i]] != undefined then fns[theNum][arr[i]] sel elements else elements; theNum = arr[i] )
		elements
	),
	
	fn getAngleBetweenFaces sel faceA faceB = acos (dot (normalize (polyOp.getFaceNormal sel faceA)) (normalize (polyOp.getFaceNormal sel faceB))),
	
	fn getEdgeCenter sel theEdge =
	(
		local edgeVerts = polyOp.getEdgeVerts sel theEdge
		((polyOp.getVert sel edgeVerts[1]) + (polyOp.getVert sel edgeVerts[2])) / 2
	),
	
	fn getEdgeLength sel theEdge =
	(
		local edgeVerts = polyOp.getEdgeVerts sel theEdge
		distance (polyOp.getVert sel edgeVerts[1]) (polyOp.getVert sel edgeVerts[2])
	),
	
	fn getFacePerimeter sel theFace =
	(
		local faceEdges = polyOp.getEdgesUsingFace sel theFace
		local tPerimeter = 0; for i in faceEdges do tPerimeter += C4.getEdgeLength sel i
		tPerimeter
	),
	
	fn getFaceUnderCursor sel =
	(
		local obj = snapshot sel; hide obj
		local arr = intersectRayEx obj (mapScreenToWorldRay mouse.pos)
		if arr != undefined then
		(
			local faceVerts = getFace obj arr[2]
			local theFace = (((polyOp.getFacesUsingVert sel.baseObject faceVerts[1]) * (polyOp.getFacesUsingVert sel.baseObject faceVerts[2]) * (polyOp.getFacesUsingVert sel.baseObject faceVerts[3])) as array)[1]
			delete obj
			theFace
		)
		else undefined
	),
	
	fn getUnconnectedEdges sel theEdges thisForm =
	(	--	04.08.2004 23:11:39
		with undo off with redraw off
		(
			local edgeArr = theEdges as array, edgeBA = theEdges as bitArray
			local edgeSel = polyOp.getEdgeSelection sel
			polyOp.setEdgeSelection sel edgeArr
			local unconnectedEdges = #()
			while edgeArr.count != 0 do
			(
				local vertEdges = C4.convertSOs sel edgeArr[1] #(2, 1, 2)
				local done = #{edgeArr[1]}, neighbourEdges = vertEdges * edgeBA
				while not neighbourEdges.isEmpty do
				(
					join done neighbourEdges
					neighbourEdges = ((C4.convertSOs sel neighbourEdges #(2, 1, 2)) * edgeBA) - done
				)
				append unconnectedEdges (done as thisForm)
				edgeArr = ((edgeArr as bitArray) - done) as array
			)
			polyOp.setEdgeSelection sel edgeSel
			unconnectedEdges
		)
	),
	
	fn getUnconnectedPolys sel faces includeCrossPolys thisForm =
	(
		fn getPolyNeighboursOf sel thePolys includeCrossPolys =
		(
			local edgeFaces = C4.convertSOs sel thePolys #(4, 2, 4)
			if includeCrossPolys then (C4.convertSOs sel thePolys #(4, 1, 4)) + edgeFaces else edgeFaces
		)
		
		with undo off with redraw off
		(
			local faceArr = faces as array, faceBA = faces as bitArray
			local faceSel = polyOp.getFaceSelection sel
			polyOp.setFaceSelection sel faceArr
			local unconnectedPolys = #()
			while faceArr.count != 0 do
			(
				local done = #{faceArr[1]}, neighbourPolys = (getPolyNeighboursOf sel faceArr[1] includeCrossPolys) * faceBA
				while not neighbourPolys.isEmpty do
				(
					join done neighbourPolys
					neighbourPolys = ((getPolyNeighboursOf sel neighbourPolys includeCrossPolys) * faceBA) - done
				)
				append unconnectedPolys (done as thisForm)
				faceArr = ((faceArr as bitArray) - done) as array
			)
			polyOp.setFaceSelection sel faceSel
			unconnectedPolys
		)
	),
	
	fn getVertCenter sel theVerts = ( local tPos = [0,0,0]; for i in theVerts do tPos += polyOp.getVert sel i; tPos / (if classOf theVerts == Array then theVerts.count else theVerts.numberSet) ),
	
	fn getVertNormal sel theVert =
	(
		local vertFaces = polyOp.getFacesUsingVert sel theVert
		local tNormal = [0,0,0]; for i in vertFaces do tNormal += polyOp.getFaceNormal sel i
		normalize (tNormal / vertFaces.numberSet)
	),
	
	------------------------------
	--// Array Related Methods //--
	------------------------------
	
	fn appendIfNew &arr item = if findItem arr item == 0 then (append arr item; true) else false,
	fn compactArray arr = ( for i = 1 to arr.count do (for f = arr.count to i + 1 by -1 where arr[i] == arr[f] do deleteItem arr f); arr ),
	fn compactArrays arr = ( for i = 1 to arr.count do (for f = arr.count to i + 1 by -1 where (arr[i] as string) == (arr[f] as string) do deleteItem arr f); arr ),
	fn copyArr arr = for i in arr collect i,
	
	fn getNextAvailableName arr searchStr =
	(
		local newArr = for i in arr where findString i searchStr != undefined collect i
		local arr = for i in newArr collect C4.getNumberFromName i searchStr as integer
		local theNum = 1, found = 1
		while found != 0 do
		(
			found = findItem arr theNum
			if found != 0 do theNum += 1
		)
		searchStr + " " + (if theNum < 10 then "0" else "") + theNum as string
	),
	
	fn reorderArr original unsorted sorted unique:false =
	(
		for i = 1 to original.count collect
		(
			local index = findItem unsorted sorted[i]
			if not unique do unsorted[index] = ""
			original[index]
		)
	),
	
	fn reverseArr arr = for i = arr.count to 1 by -1 collect arr[i],
	
	fn sort2 arr =
	(
		sorted = for i in arr collect C4.lowercase i
		local unsorted = C4.copyArr sorted
		sort sorted
		C4.reorderArr arr unsorted sorted
	),
	
	fn stringArr arr = for i in arr collect i as string,
	
	------------------------------
	--// Color Related Methods //--
	------------------------------
	
	fn getAverageColor colorArr =
	(
		local clr = black; for i in colorArr do clr += i
		clr = clr / colorArr.count
		color clr.r clr.g clr.b
	),
	
	fn hex2RGB theColor =
	(
		local arr = "ABCDEF"
		local rgb = #()
		for i = 3 to 7 by 2 do
		(
			local c = if (local index = findString arr theColor[i]) != undefined then (index + 9) * 16 else theColor[i]
			c += if (local index = findString arr theColor[i + 1]) != 0 then index + 9 else theColor[i + 1]
			append rgb c
		)
		color rgb[1] rgb[2] rgb[3]
	),
	
	fn hsv2RGB theColor =
	(
		local h = theColor.x / 255, s = theColor.y / 255, v = theColor.z / 255
		local r = g = b = v * 255
		if s != 0 then
		(
			local var_h = h * 6
			local var_i = floor var_h
			local var_1 = v * (1 - s)
			local var_2 = v * (1 - s * (var_h - var_i))
			local var_3 = v * (1 - s * (1 - (var_h - var_i)))
			local var_r, var_g, var_b
			if var_i == 0 then (r = v; g = var_3; b = var_1)
				else if var_i == 1 then (r = var_2; g = v; b = var_1)
					else if var_i == 2 then (r = var_1; g = v; b = var_3)
						else if var_i == 3 then (r = var_1; g = var_2; b = v)
							else if var_i == 4 then (r = var_3; g = var_1; b = v)
								else (r = v; g = var_1; b = var_2)
			r *= 255; g *= 255; b *= 255
		)
		color (C4.round r) (C4.round g) (C4.round b)
	),
	
	fn point3toColor p3 = color p3.x p3.y p3.z,
	
	fn rgb2HEX theColor convert:true =
	(
		local str = "ABCDEF"
		local components = if classOf theColor == color then #("r", "g", "b") else #("x", "y", "z")
		local hex = "0x"
		for i in components do
		(
			for f = 1 to 2 do
			(
				local c = (if f == 1 then (getProperty theColor i) / 16 else mod (getProperty theColor i) 16) as integer
				if c >= 10 then hex += str[c - 9] else hex += c as string
			)
		)
		if convert then hex as integer else hex
	),
	
	fn rgb2HSV theColor =
	(
		local r = theColor.r / 255, g = theColor.g / 255, b = theColor.b / 255
		local minVal = amin #(r, g, b)
		local maxVal = amax #(r, g, b)
		local delta = maxVal - minVal
		local h = 0, s = 0, v = maxVal
		if delta != 0 then
		(
			local s = delta / maxVal
			local del_R = (((maxVal - r) / 6) + (delta / 2.0)) / delta
			local del_G = (((maxVal - g) / 6) + (delta / 2.0)) / delta
			local del_B = (((maxVal - b) / 6) + (delta / 2.0)) / delta
			if r == maxVal then h = del_B - del_G
				else if g == maxVal then h = (1 / 3.0) + del_R - del_B
					else if b == maxVal do h = (2 / 3.0) + del_G - del_R
			if h < 0 do h += 1
			if h > 1 do h -= 1
			h *= 255; s *= 255; v *= 255
		)
		point3 (C4.round h) (C4.round s) (C4.round v)
	),
	
	--------------------------------------
	--// Integer/Float Related Methods //--
	--------------------------------------
	
	fn convertValue2Integers val = for i = 24 to 8 by -8 collect (local a = bit.shift val -i; val -= a * (2 ^ i); a),
	
	fn convertIntegers2Value intArr =
	(
		if classOf intArr == color do intArr = #(intArr.r, intArr.g, intArr.b)
		(bit.shift intArr[1] 24) + (bit.shift intArr[2] 16) + (bit.shift intArr[3] 8)
	),
	
	fn round val = (val + 0.5) as integer,
	
	-------------------------------
	--// String Related Methods //--
	-------------------------------
	
	fn addCount str count chars addedBefore = (if addedBefore do str = C4.removeCount str chars[1]; str += " " + chars[1] + count as string + chars[2]),
	
	fn capitalize str =
	(
		str = copy str
		local index = #(1) + (for i = 1 to str.count where str[i] == " " or str[i] == "-" collect (i + 1))
		for i in index do str = C4.upper str i
		str
	),
	
	fn findLast str char =
	(
		local found = 0, index = 0
		while found != undefined do
		(
			found = findString str char
			if found != undefined do (str = replace str found 1 " "; index = found)
		)
		index
	),
	
	fn getCharCount str char = (filterString str char).count,
	
	fn getNumberFromName str searchStr =
	(
		local theNum = ""
		if findString str (searchStr + " ") == 1 do
		(
			local integers = "0123456789", finished = false
			for i = searchStr.count + 2 to str.count while not finished do (if findString integers str[i] != undefined then theNum += str[i] else finished = true)
		)
		theNum
	),
	
	fn lower str index =
	(
		local arr = #("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
		if (local f = findString arr[2] str[index]) != undefined do str[index] = arr[1][f]
		str
	),
	
	fn lowercase str = (local newstr = copy str; for i = 1 to newstr.count do C4.lower newstr i; newstr),
	fn removeCount str char = substring str 1 ((C4.findLast str char) - 2),
	
	fn removeInvalidChars str =
	(
		local arr = #(" ",",",".","'","é",">","<","!","#","$","&","/","\\","*","?","-","+","½","%","\"",":",";","^")
		for i in arr do (while (findString str i) != undefined do str = replace str (findString str i) 1 ""); str
	),
	
	fn replaceString str source target = (local found = 0; while found != undefined do (found = findString str source; if found != undefined do str = replace str found 1 target); str),
	
	fn searchString theFile str =
	(
		local f = openFile theFile mode:"r"
		local theNum = 0; while not (eof f) do (if skipToString f str != undefined do theNum += 1); close f; theNum
	),
	
	fn upper str index =
	(
		local arr = #("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
		if (local f = findString arr[1] str[index]) != undefined do str[index] = arr[2][f]
		str
	),
	
	fn uppercase str =
	(
		local newstr = copy str
		for i = 1 to newstr.count do C4.upper newstr i; newstr
	),
	
	----------------------------------
	--// TreeView Related Methods //--
	----------------------------------
	
	fn getChildrenBackward tvNode =
	(
		local firstChild = tvNode.child.index
		local arr = #(tvNode.child.lastSibling); while arr[arr.count].index != firstChild do append arr arr[arr.count].previous; arr
	),
	
	fn getChildrenForward tvNode =
	(
		local lastChild = tvNode.child.lastSibling.index
		local arr = #(tvNode.child); while arr[arr.count].index != lastChild do append arr arr[arr.count].next; arr
	),
	
	fn getNumChildren tvNode =
	(
		local arr = C4.getChildrenForward tvNode
		local directories = for i in arr where i.children != 0 collect (join arr (C4.getChildrenForward i); 0)
		arr.count - directories.count
	),
	
	fn getParentNodes tvNode = (local arr = #(); while tvNode.parent != undefined do (append arr (tvNode = tvNode.parent)); arr),
	
	fn addNode tv str index: bold:false expand:false checked:false color:black backColor:black sorted:false tag: key: edit:false dragNDrop:false =
	(
		local theNode = if index == unsupplied then tv.nodes.add() else (if dragNDrop then tv.nodes.add index else tv.nodes.add index 4 "" str 0)
		theNode.text = str
		if bold do theNode.bold = true
		if expand do theNode.expanded = true
		theNode.checked = checked
		if color != black do theNode.foreColor = color
		if backColor != black do theNode.backColor = backColor
		if sorted do theNode.sorted = true
		if tag != unsupplied do theNode.tag = tag
		if key != unsupplied do theNode.key = key
		if edit do (enableAccelerators = false; tv.nodes[theNode.index].selected = true; tv.startLabelEdit())
		theNode
	),
	
	fn setLayout tv =
	(
		tv.borderStyle = #ccFixedSingle
		tv.lineStyle = #tvwRootLines
		tv.appearance = #ccFlat
		tv.indentation = 28 * 15
		tv.hotTracking = true
		tv.labelEdit = #tvwManual
		tv.font = "Verdana"
		tv.pathSeparator = "/"
	),
	
	fn stripColors parentNodes singleNodes colorArr =
	(
		local nodesArr = #()
		for f in parentNodes do
		(
			if f.expanded then
			(
				local arr = C4.getChildrenBackward f
				for i in arr where i.children != 0 and i.expanded do join arr (C4.getChildrenBackward i)
				local indexArr = #(f.index) + (for i in arr collect i.index)
				insertItem undefined arr 1
				local sorted = #(f)
				for i = 2 to arr.count do
				(
					local index = (findItem indexArr arr[i].parent.index) + 1
					insertItem arr[i] sorted index
					indexArr[i] = 0
					insertItem arr[i].index indexArr index
					deleteItem indexArr (findItem indexArr 0)
				)
				join nodesArr sorted
			)
			else append nodesArr f
		)
		nodesArr += singleNodes
		for i = 1 to nodesArr.count do nodesArr[i].backColor = colorArr[if (local m = mod i colorArr.count) != 0 then m else colorArr.count]
		nodesArr
	),
	
	--------------------------------
	--// Rollout Related Methods //--
	--------------------------------
	
	fn dockDialog theDialog &dockState index subtract =
	(
		if dockState != index do
		(
			if not theDialog.dialogBar do (for i in theDialog.controls do i.pos.y -= 3; cui.registerDialogBar theDialog maxSize:[theDialog.width, systemTools.getScreenHeight()] style:#(#cui_dock_left, #cui_dock_right))
			dockState = index; theDialog.tv.size.y = (systemTools.getScreenHeight()) - 74 - subtract
			cui.dockDialogBar theDialog #(#cui_dock_left, #cui_dock_right)[index]
			theDialog.ops[4] -= [2,6]
		)
	),
	
	fn getDialogCenter theDialog w h = (getDialogPos theDialog) + [(theDialog.width - w) / 2, (theDialog.height - h) / 2],
	
	fn getOptionsMask =
	(
		local ops = bitmap 14 14 color:white
		local arr = #([5,3], [5,4], [5,5], [5,6], [5,7], [5,8], [5,9], [6,4], [6,5], [6,6], [6,7], [6,8], [7,5], [7,6], [7,7], [8,6])
		for i in arr do setPixels ops i #(black); ops
	),
	
	fn isMouseInside theRollout area offset = (local dPos = getDialogPos theRollout, m = mouse.screenPos; m.x >= dPos.x + offset[1] and m.x <= dPos.x + area.x + offset[2] and m.y >= dPos.y + offset[3] and m.y <= dPos.y + area.y + offset[4]),
	fn newDialog theRollout pos: width: height: = if pos == unsupplied then createDialog theRollout style:#(#style_toolWindow, #style_sysMenu, #style_resizing) bmpStyle:#bmp_stretch else createDialog theRollout style:#(#style_toolWindow, #style_sysMenu, #style_resizing) bmpStyle:#bmp_stretch pos:pos width:width height:height,
	
	fn notify title txt modal: pos: center: backColor: closeAfter: =
	(
		local lines = filterString txt "\n"
		local maxWidth = amax (for i in lines collect (getTextExtent i).x)
		local size = [maxWidth + 20, 33 + 16 * (lines.count - 1)]
		local str = "rollout notification \"" + title + "\" width:" + size.x as string + " height:" + size.y as string + " ( \n"
		for i = 1 to lines.count do
		(
			if findString lines[i] "www" == 1 then str += "hyperlink info" + i as string + " \"" + lines[i] + "\" align:#center offset:[0,-5] height:17 color:(color 200 0 0) address:\"http://" + lines[i] + "\" \n"
				else str += "label info" + i as string + " \"" + lines[i] + "\" pos:[10," + (10 + 16 * (i - 1)) as string + "] width:" + maxWidth as string + " height:17 \n"
		)
		if closeAfter != unsupplied do str += "timer timeX interval:" + (closeAfter * 1000) as string + " \n on timeX tick do destroyDialog notification \n"
		str += " ) \n"
		if center != unsupplied do pos = C4.getDialogCenter center size.x size.y
		str += "createDialog notification " + (if modal == unsupplied then "modal:false " else "modal:" + modal as string + " ") + (if pos != unsupplied then "pos:" + pos as string else "") + " style:#(#style_toolWindow, #style_sysMenu) " + (if backColor != unsupplied then "bgColor:" + backColor as string else "") + " \n"
		execute str
	),
	
	fn toggleDialog theRollout theRCMenu override =
	(
		if C4 != undefined and classOf C4 == structDef do
		(
			local theDialog = execute theRollout
			if getDialogSize theDialog != [0,0] then theRCMenu.quit.picked()
			else
			(
				local ops = execute (getINISetting (getMAXINIFile()) "Light" theRollout)
				if ops != OK then
				(
					local pos = ops[4]
					if override or ops[1] do C4.newDialog theDialog pos:[-1000,-1000] width:ops[5].x height:ops[5].y
					theDialog.ops.dialogPos = pos
					if not theDialog.dialogBar do setDialogPos theDialog ops[4]
				)
				else C4.newDialog theDialog
			)
		)
	),
	
	fn undockDialog theDialog &dockState pos size subtract:0 save:true =
	(
		if theDialog.dialogBar do
		(
			for i in theDialog.controls do i.pos.y += 3; cui.unRegisterDialogBar theDialog
			theDialog.width = size.x; if save do dockState = 0; theDialog.tv.size.y = size.y - subtract
			setDialogPos theDialog pos
		)
	),
	
	------------------------------
	--// Node Related Methods //--
	------------------------------
	
	fn setColor n =
	(
		local index = if keyboard.shiftPressed then 2 else 1
		n.material = meditMaterials[index]
		try(n.smooth = 0) catch()
		try(n.smooth = false) catch()
	),
	
	fn setMaterial obj =
	(
		if findItem #(GeometryClass, shape) (superClassOf obj) != 0 do
		(
			obj.material = meditMaterials[if keyboard.shiftPressed then 2 else 1]
			if hasProperty obj #smooth do (if classOf (getProperty obj #smooth) == integer then setProperty obj #smooth 0 else setProperty obj #smooth false)
		)
	),
	
	------------------------------
	--// Viewport Related Methods //--
	------------------------------
	
	fn onViewport str color:white after: =
	(
		if after != unsupplied then
		(
			local txt = "rollout delayer \"\" (\n"
			txt += "timer timeX interval:" + (after * 1000) as string + "\n"
			txt += "on timeX tick do ( C4.onViewport \"" + str + "\" color:" + color as string + "; print 5555; destroyDialog delayer ) )\n"
			txt += "createDialog delayer 1 1 pos:[-1000, -1000]"
			execute txt
		)
		else
		(
			completeRedraw()
			gw.wtext [((getViewSize()).x - (gw.getTextExtent str).x) / 2, 17, 0] str color:color
			gw.enlargeUpdateRect #whole
			gw.updateScreen()
		)
	),
	
	-----------------------------
	--// File Related Methods //--
	-----------------------------
	
	fn getAllFiles path pattern =
	(
		local arr = getDirectories (path + "\\*"); for i in arr do join arr (getDirectories (i + "*"))
		local allFiles = #(); for f in arr do join allFiles (getFiles (f + pattern))
		join allFiles (getFiles (path + "\\" + pattern)); allFiles
	),
	
	fn macroExists category macro =
	(
		local macroArr = #(), categoryArr = #()
		local str = stringStream ""; macros.list to:str
		local mcr = for i in (filterString (str as string) "\n") collect filterString i "\"\""
		for i in mcr do (local index = findItem categoryArr i[4]; if index == 0 then (append categoryArr i[4]; append macroArr #(i[2])) else append macroArr[index] i[2])
		local index = findItem categoryArr category
		index != 0 and findItem macroArr[index] macro != 0
	)
)