--***************************************************************************
--*						GBXModel Impoter by TheGhost						*
--*					   for 3ds Max v5+ and gmax v1.2						*
--***************************************************************************
--*		Features:															*
--*			- Import any geometry from the tag with many import options		*
--*			- Import perfect UVW coordinates and material ID's				*
--*			- Setup the max material library for the model's shaders		*
--*			- Import model's nodes and/or markers with many options			*
--*				(nodes/markers are perfectly placed and rotated)			*
--*			- Can also import biped nodes as bone objects					*
--*			- Imports vertex weights for a fully rigged model				*
--*-------------------------------------------------------------------------*
--*	Instructions: Open up a .gbxmodel tag in Guerilla and export the tag as	*
--*	a .txt file (File > Export Tag). Run this script and load the file.		*
--*	Select the region, permutation, and LOD to import and choose from the	*
--*	import options. If importing nodes/markers, choose from those options.	*
--*-------------------------------------------------------------------------*
--*	Submit bugs to TheGhost on the Gearbox Software forums. Visit the		*
--*	forums for additional help at http://gbxforums.gearboxsoftware.com.		*
--*-------------------------------------------------------------------------*
--*	Copyright (C) 2006 Adam Papamarcos (mailto:papamarcos@gmail.com)		*
--*	This program is free software; you can redistribute it and/or modify it	*
--*	under the terms of the GNU General Public License as published by the	*
--*	Free Software Foundation; either version 2 of the License, or (at your	*
--*	option) any later version. This program is distributed in the hope that	*
--*	it will be useful, but WITHOUT ANY WARRANTY; without even the implied	*
--*	warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See	*
--*	the GNU General Public License for more details. A full copy of this	*
--*	license is available at http://www.gnu.org/licenses/gpl.txt.			*
--*-------------------------------------------------------------------------*

global reg_list = #()  -- Holds the *name* of each region in the GBXModel file.
global perm_list = #()  -- (multi-dimensional, 2D) Holds the *name* of each permutation for each region in the GBXModel file.
global lod_list = #("super high", "high", "medium", "low", "super low")  -- (static) A simple array for referencing LOD cutoffs.

global geom_block_index = #()  -- (multi-dimensional, 3D) Holds the information about which regions, permutations, and LOD are defined by which geometry block.
global geom_block_offsets = #()  -- Hold the file positions (offset) of each geometry block.
global geom_block_verts = #()  -- Holds the number of uncompressed vertices in each geometry block.
global shader_index = #()  -- An array to hold the *names* of the model's shaders (the material ID's).

global node_array = #()  -- An array which holds the node objects.
global marker_array = #()  -- An array which holds the marker objects.

global in_file  -- This is the file loaded from which information is read. It is global so it may be accessed anywhere in this script.
global file_loaded = false  -- A global boolean which contains the state of whether or not a file is loaded.
global local_nodes = false -- A global boolean which describes whether the parts of the geometry blocks use local nodes.
global u_scale  -- The base map u-scale for the model.
global v_scale  -- the base map v-scale for the model.
global maxver = maxVersion()

rollout roll "GBXModel Importer by TheGhost" width:468 height:478
(
	GroupBox grp_load "Load Exported GBXModel Text File" pos:[8,5] width:452 height:65
	button load_button "Load File" pos:[41,28] width:116 height:30
	edittext txt_model_name "Model:" pos:[172,34] width:257 height:17
	
	GroupBox grp_select "LOD / Region / Permutation Selection" pos:[8,76] width:452 height:119
	radiobuttons radio_LOD "Select LOD" pos:[28,94] width:72 height:94 labels:#("super high", "high", "medium", "low", "super low") default:1 columns:1
	listbox listbox_perm "Select Model Permutation" pos:[264,94] width:172 height:5
	listbox listbox_reg "Select Model Region" pos:[121,94] width:124 height:5
	
	GroupBox grp_geomops "Import Geometry Options" pos:[8,200] width:452 height:107
	button geom_button "Import Model" pos:[268,269] width:141 height:29
	checkbox chk_uv "Import UVW Coordinates and MatID's" pos:[243,211] width:197 height:17 checked:true
	checkbox chk_matlib "Setup 3ds Material Library Shaders" pos:[243,229] width:188 height:17 checked:true
	checkbox chk_weight "Import Vertex Weights (Rigged Model)" pos:[243,247] width:202 height:17 enabled:false checked:false
	radiobuttons radio_geom "Select Import Geometry" pos:[23,217] width:206 height:62 labels:#("Selected Permutation from All Regions", "All Permutations of Selected Region", "Selected Region and Permutation Only")
	checkbox chk_attach "Attach Parts of Mesh Within Region" pos:[34,282] width:195 height:17 checked:true
	
	GroupBox grp_nodeops "Import Nodes / Markers Options" pos:[7,311] width:452 height:104
	checkbox chk_n "Import All Nodes" pos:[197,322] width:103 height:17 checked:true
	checkbox chk_m "Import Markers" pos:[197,357] width:94 height:17 checked:true
	label lbl_n "Node Radius" pos:[338,325] width:64 height:13
	label lbl_b "Bone Width" pos:[343,342] width:59 height:13
	label lbl_m "Marker Radius" pos:[331,359] width:71 height:13
	spinner spn_n "" pos:[405,323] width:42 height:16 range:[0.01,30,2]
	spinner spn_b "" pos:[405,342] width:42 height:16 range:[0.01,30,2.5]
	spinner spn_m "" pos:[405,361] width:42 height:16 range:[0.01,30,1.5]
	button nm_button "Import Nodes / Markers" pos:[229,378] width:134 height:28
	radiobuttons radio_markers "Import Markers from" pos:[24,335] width:166 height:62 labels:#("All Reg. of Selected Perm.", "All Perm. of Selected Reg.", "Selected Reg. and Perm. Only")
	checkbox chk_bones "Biped Nodes as Bones" pos:[197,339] width:133 height:17 checked:true
	
	GroupBox grp_status "Status" pos:[6,418] width:452 height:51
	label lbl_scriptby "Script by TheGhost" pos:[356,431] width:94 height:14 enabled:false
	progressBar pbar "" pos:[25,435] width:320 height:15 color:(color 30 10 190)
	progressBar pbar2 "" pos:[25,447] width:320 height:14 color:(color 255 0 0)
	label lbl_version "v1.0.1" pos:[415,447] width:32 height:15 enabled:false
	
	on load_button pressed do
	(
		ClearListener()
		format "-- GBXModel Importer by TheGhost --\n"
		format "Using max version: %\n" (maxver[1] / 1000.0)
		
		-- Bring up the open file dialog and prompt for a file to open.
		in_name = getOpenFileName \
			filename: "C:\\Program Files\\Microsoft Games\\Halo Custom Edition\\" \
			types: "GBXModel Tag Export (*.txt)|*.txt|All Files (*.*)|*.*"
		
		if in_name != undefined then
	 	(
			in_file = openFile in_name 
			
		  	if in_file == undefined then
			(
				messageBox "Error in opening file!" title:"File opening error"
			)
			else
			(
				seek in_file 0
				
				-- Check the first word of the open file. If it's not "gbxmodel" then it's not the correct file type.
				if readDelimitedString in_file "\t" != "gbxmodel" then
				(
					messageBox "Error: Incorrect file type!\n\nPlease select an exported .gbxmodel tag.  " \
						title:"Incorrect file type error"
				)
				else
				(
					-- If the file about to be opened is the same as the one already open, don't bother to index everything again!
					if txt_model_name.text != readDelimitedString in_file "\n" then
					(
						start1 = timeStamp()
						seek in_file 0
						file_loaded = true
						local_nodes = false
						chk_weight.checked = false
						chk_weight.enabled = false
						
						reg_list = #()
						perm_list = #()
						geom_block_index = #()
						geom_block_offsets = #()
						shader_index = #()
											
						-- Get the local file path of the .gbxmodel in the tags directory.
						readDelimitedString in_file "\t"
						model_name = readDelimitedString in_file "\n"
						txt_model_name.text = model_name
						
						format "Model loaded: %\n" model_name
						
						-- Find out whether the parts of this model use local nodes.
						skipToString in_file "flags\tlong flags\t"
						flag_state = readValue in_file
						if flag_state == 2 or flag_state == 3 or flag_state == 6 or flag_state == 7 then
						(
							local_nodes = true
							format "Warning: Parts use local nodes. The model may not be rigged correctly.\n"
						)
						
						-- Gather the base map U and V scales. If the scales are set to 0, make them 1.
						skipToString in_file "base map u-scale\treal\t"
						u_scale = readValue in_file
						if u_scale == 0 do u_scale = 1
						
						skipToString in_file "base map v-scale\treal\t"
						v_scale = readValue in_file
						if v_scale == 0 do v_scale = 1
						
						-- Loop through the regions, permutations, and LODs to build the geometry block index.
						skipToString in_file "regions\tblock\t"
						region_blocks = readValue in_file
						for r = 1 to region_blocks do
						(
							skipToString in_file "name\tstring\t"
							reg_name = readDelimitedString in_file "\n"
							reg_list[r] = reg_name
							
							temp_perm_list = #()
							perm_geomblock_index = #()
							
							skipToString in_file "permutations\tblock\t"
							permutation_blocks = readValue in_file
							for p = 1 to permutation_blocks do
							(
								skipToString in_file "name\tstring\t"
								perm_name = readDelimitedString in_file "\n"
								append temp_perm_list perm_name
								
								LOD_geomblock_index = #()
								
								for i = 5 to 1 by -1 do
								(
									skipToString in_file "short block index\t"
									geom_block = readValue in_file + 1
									LOD_geomblock_index[i] = geom_block
								)
								
								append perm_geomblock_index LOD_geomblock_index
								skipToString in_file ("\n     end element " + (p - 1) as string)
							)
							
							geom_block_index[r] = perm_geomblock_index
							perm_list[r] = temp_perm_list
						)
						
						-- Set the listboxes to hold the items of the regions and the permutations of the first region.
						listbox_reg.items = reg_list
						listbox_perm.items = perm_list[1]
						
						listbox_reg.selection = 1
						listbox_perm.selection = 1
						
						skipToString in_file "geometries\tblock\t"
						geometry_blocks = readValue in_file
						
						seek in_file ((filePos in_file) - 10)
						
						-- Build an array which holds the offsets of each geometry block so they may be accessed quickly later.
						for g = 1 to geometry_blocks do
						(
							skipToString in_file ("\n   element " + (g - 1) as string)
							geom_block_offsets[g] = filePos in_file
							geom_block_verts[g] = 0
							
							skipToString in_file "parts\tblock\t"
							part_blocks = readValue in_file
							for p = 1 to part_blocks do
							(
								-- Since the geometry blocks are huge, this code is used to skip over the geometry information
								-- by predicting how big each block is and skipping forward in the file accordingly.
								
								skipToString in_file "uncompressed vertices\tblock\t"
								uncompressed_vert_count = readValue in_file
								geom_block_verts[g] += uncompressed_vert_count
								-- Each uncompressed vertex block takes up at least 478 characters, average for a large file is about 489.8 characters.
								if (uncompressed_vert_count < 100) do offset_magic = 478
								if (uncompressed_vert_count >= 100 and uncompressed_vert_count <= 500) do offset_magic = 481
								if (uncompressed_vert_count > 500) do offset_magic = 484
								seek in_file (filePos in_file + (offset_magic * uncompressed_vert_count))
								
								-- Use this to skip over the compressed vertices block.
								skipToString in_file "compressed vertices\tblock\t"
								compressed_vert_count = readValue in_file
								if compressed_vert_count > 0 then
								(
									-- Each compressed vertex block takes up at least 470 characters, likely minimum is 492 characters, average for a large file is about 516 characters.
									if (compressed_vert_count < 20) do offset_magic = 470
									if (compressed_vert_count >= 20 and compressed_vert_count <= 100) do offset_magic = 492
									if (compressed_vert_count >= 100 and compressed_vert_count <= 500) do offset_magic = 502
									if (compressed_vert_count > 500) do offset_magic = 508
									seek in_file (filePos in_file + (offset_magic * compressed_vert_count))
								)
								
								skipToString in_file "triangles\tblock\t"
								triangles_count = readValue in_file
								-- Each triangle block takes up at least 155 characters, average for a large file is about 160 characters.
								seek in_file (filePos in_file + (155 * triangles_count))
								
								-- Update the progress bar to let the user know that this code is working!
								pbar.value = ((g + (p as float / part_blocks)) / geometry_blocks) * 100.0
							)
						)
						
						-- Parse the names of the shader from the bottom of the GBXModel file.
						skipToString in_file "shaders\tblock\t"
						shader_blocks = readValue in_file
						for s = 1 to shader_blocks do
						(
							skipToString in_file "shader\ttag reference\t"
							
							-- Since the shaders are stored as their directory (i.e. characters\cyborg\shaders\armor),
							-- read only the text after the last slash.
							d2 = 1
							d1 = 0
							while d1 < d2 do  -- Loop through until the closest tab is closer than the closest slash.
							(
								temp_pos = filePos in_file
								skipToString in_file "\t"
								d2 = filePos in_file - temp_pos  -- Distance 2: The distance to the nearest tab.
								seek in_file temp_pos
								skipToString in_file "\\"
								d1 = filePos in_file - temp_pos  -- Distance 1: The distance to the nearest \.
							)
							
							-- Go back to the last slash and read the name of shader.
							seek in_file temp_pos
							shader_index[s] = readDelimitedString in_file "\t"
						)
						
						-- Set the progress bar back to zero.
						pbar.value = 0
						
						end1 = timeStamp()
						time1 = (end1 - start1) / 1000.0
						format "Model indexing took % seconds.\n" time1
					)
				)
			)
		)
	)
	
	-- When the region listbox selection is changed, the permutations listbox
	-- must be updated to reflect permutations of the selected region.
	on listbox_reg selected i do
	(
		listbox_perm.items = perm_list[i]
	)

	on geom_button pressed do
	(
		if file_loaded == false then
		(
			messageBox "No file selected!\n\nPlease load an exported .gbxmodel tag.  " title:"File load error"
		)
		else
		(
			start2 = timeStamp()
	
			import_geometry_block = #() -- An array which holds the index of the geometry blocks to import.
			geometry_block_region = #() -- An array which holds the name of the import geometry block's region.
			all_imported_geometries = #() -- An array which holds all of the imported objects (for setting up material library).
			
			-- Make sure the user did not delete any nodes. If so, uncheck the import weight checkbox and display an error.
			allValid = true
			for n = 1 to node_array.count do
			(
				if allValid == true then
				(
					if isValidNode node_array[n] == false then
					(
						allValid = false
					)
				)
			)
			if allValid == false then
			(
				chk_weight.checked = false
				chk_weight.enabled = false
				messageBox "Error: Cannot import vertex weights.  \n\nOne or more of the imported nodes has been deleted. Please re-import.  " \
					title: "Vertex weight error"
			)
			
			-- Find out which LOD, region, and permutation are selected, and, based on the import geometry radio button,
			-- decide which geometry blocks to import. Place these blocks' indices in the "import_geometry_block" array.
			reg_index = listbox_reg.selection
			perm_index = listbox_perm.selection
			LOD_index = radio_LOD.state
			
			-- To import "All Regions from Selected Permutation", cycle through each region to check for a matching permutation.
			if radio_geom.state == 1 then
			(
				perm_name = perm_list[reg_index][perm_index]
				for r = 1 to geom_block_index.count do
				(
					add_region = false
					perm_from_region = 0
					for p = 1 to geom_block_index[r].count do
					(
						if perm_list[r][p] == perm_name then
						(
							add_region = true
							perm_from_region = p
						)
					)
					if add_region == true then
					(
						append import_geometry_block geom_block_index[r][perm_from_region][LOD_index]
						append geometry_block_region reg_list[r]
					)
				)
			)
			-- Add all of the current permutations from the selected region.
			if radio_geom.state == 2 then
			(
				for p = 1 to geom_block_index[reg_index].count do
				(
					import_geometry_block[p] = geom_block_index[reg_index][p][LOD_index]
				)
			)
			-- Add only the selected permutation from the selected region.
			if radio_geom.state == 3 then
			(
				import_geometry_block[1] = geom_block_index[reg_index][perm_index][LOD_index]
			)
			
			geom_blocks = import_geometry_block.count
			
			total_verts = 0
			for b = 1 to geom_blocks do
			(
				total_verts += geom_block_verts[import_geometry_block[b]]
			)
			vertnum = 0
			
			for c = 1 to geom_blocks do  -- Iterate for the number of geometries to import.
			(
				-- Skip to the offset in the file where the geometry block is located.
				geom_index_num = import_geometry_block[c]
				seek in_file geom_block_offsets[geom_index_num]
				
				parts_meshes = #()  -- Holds all of meshes of the parts that are imported.
				vert_weight_bones = #()  --  Holds the indicies of the bones that the verts are weighted to (node0 index, node1 index).
				vert_weights = #()  -- Holds the weights of the vertices (node0 weight, node1 weight). Should add up to 1.0000.
				
				-- Each geometry block is broken into parts. The parts are consistent in that they each have the same
				-- material / shader applied to them; each face in a particular part block has the same material ID.
				
				skipToString in_file "parts\tblock\t"
				part_blocks = readValue in_file
				
				for p = 1 to part_blocks do
				(
					-- Clear the arrays for each part of the geometry block.
					vertex_xyz = #()  -- A Point3 array which holds the translation [x,y,z] of each vertex in the part.
					vertex_uvw = #()  -- A Point3 array which holds the UVW coordinates [u,v,w] of each vertex in the part. The w coordinate is always 0.
					vertex_order = #()  -- (multi-dimensional, 2D) An array which describes the order in which the triangles will be made. Serves as a buffer before triangles are made.
					triangles = #()  -- A Point3 array which holds the triangles after the vertex_order has been processed.
					faces_matID = #()  -- An array which holds the material ID number (shader index) for the faces. Each value in the array is the same. The size is equal to the number of faces.
					
					-- Find out which shader is applied to this part block.
					skipToString in_file "shader index\tshort block index\t"
					shader_name = readDelimitedString in_file "\t" -- This is the shader's name.
					material_ID = readValue in_file + 1  -- This is the shader's index.
					
					-- For each vertex in the parts block, gather the xyz coordinates, uv coordinates, and weight data.
					skipToString in_file "uncompressed vertices\tblock\t"
					uncompressed_vert_blocks = readValue in_file
					for v = 1 to uncompressed_vert_blocks do
					(
						-- Pre-initialize the arrays to save a bit of time.
						vertex_xyz[uncompressed_vert_blocks] = 0
						vertex_uvw[uncompressed_vert_blocks] = 0
						
						-- Gather the [x, y, z] position of each vertex.
						skipToString in_file "real point 3d\t"
						x = readValue in_file * 100
						y = readValue in_file * 100  -- 3ds max coordinates are 100 times greater the Halo's "world units"
						z = readValue in_file * 100  -- so multiply each coordinate by 100.
						vertex_xyz[v] = [x,y,z]
						
						-- Gather the UV coordinate data if the checkbox to do so is checked.
						if chk_uv.checked == true then
						(
							skipToString in_file "real point 2d\t"
							u_coord = (readValue in_file * u_scale)
							v_coord = 1 - (readValue in_file * v_scale)
							vertex_uvw[v] = [u_coord,v_coord,0]
						)
						
						-- Gather the weight and weight index data if the checkbox to do so is checked.
						if chk_weight.checked == true then
						(
							skipToString in_file "node0 index\tshort integer\t"
							node0_index = readValue in_file + 1
							skipToString in_file "node1 index\tshort integer\t"
							node1_index = readValue in_file + 1
							append vert_weight_bones #(node0_index, node1_index)
							
							skipToString in_file "node0 weight\treal\t"
							node0_weight = readValue in_file
							skipToString in_file "node1 weight\treal\t"
							node1_weight = readValue in_file
							append vert_weights #(node0_weight, node1_weight)
						)
						
						vertnum += 1
						pbar.value = (vertnum as float / total_verts) * 100.0
					)
					
					-- Use this to skip over the compressed vertices block.
					skipToString in_file "compressed vertices\tblock\t"
					compressed_vert_count = readValue in_file
					if compressed_vert_count > 0 then
					(
						-- Each compressed vertex block takes up at least 470 characters, likely minimum is 492, average for a large file is about 516.
						if (compressed_vert_count < 20) do offset_magic = 470
						if (compressed_vert_count >= 20 and compressed_vert_count <= 100) do offset_magic = 492
						if (compressed_vert_count >= 100 and compressed_vert_count <= 500) do offset_magic = 502
						if (compressed_vert_count > 500) do offset_magic = 508
						seek in_file (filePos in_file + (offset_magic * compressed_vert_count))
					)
					
					-- Gather the information about the triangles.
					skipToString in_file "triangles\tblock\t"
					triangle_blocks = readValue in_file
					for t = 1 to triangle_blocks do
					(
						-- Keep adding the vertices read in the tag in sequential order to the vertex_order array.
						skipToString in_file "vertex0 index\tshort integer\t"
						append vertex_order (readValue in_file + 1)
						skipToString in_file "vertex1 index\tshort integer\t"
						append vertex_order (readValue in_file + 1)
						skipToString in_file "vertex2 index\tshort integer\t"
						append vertex_order (readValue in_file + 1)
					)
					
					-- Check the last two vertex indices to see if they hold actual index data.
					vo_count = vertex_order.count
					if vertex_order[vo_count] == 0 do deleteItem vertex_order (vo_count)
					if vertex_order[vo_count-1] == 0 do deleteItem vertex_order (vo_count-1)
					
					-- Take the vertex order and make triangles from it. For example:
					-- If vertex_order = #(1, 2, 3, 4, 5), then triangles = #([1, 2, 3], [2, 3, 4], [3, 4, 5])
					for w = 1 to (vertex_order.count - 2) do
					(
						triangles[w] = [vertex_order[w],vertex_order[w+1],vertex_order[w+2]]
					)
					
					-- Reverse the vertex order of every other triangle because of how 3ds max creates triangles.
					for r = 1 to triangles.count by 2 do
					(
						a = triangles[r][1]
						triangles[r][1] = triangles[r][3]
						triangles[r][3] = a
					)
					
					-- Check each triangle for duplicate vertices (degenerate triangles), and delete if found.
					for d = triangles.count to 1 by -1 do
					(
						-- If the index of any of the vertices match, delete the entire triangle.
						if (triangles[d][1] == triangles[d][2]) or (triangles[d][2] == triangles[d][3]) or (triangles[d][1] == triangles[d][3]) then
						(
							deleteItem triangles d
						)
					)
					
					-- Create the mesh based on the position of the vertices and the triangles defined by the vertex indices.
					-- Also provide the vertex UVW coordinates and material IDs for the faces if checkbox to do so is checked.
					if chk_uv.checked != true then
					(
						-- Create the mesh of the object with just the vertex positions and triangle information.
						m = mesh vertices:vertex_xyz faces:triangles
					)
					else
					(
						-- Set all faces to the shader index (material ID number).					
						for t = 1 to triangles.count do
						(
							faces_matID[t] = material_ID
						)
						-- Create the mesh of the object with vertex positions, UVW coordinates, triangle information, and material ID's.
						m = mesh vertices:vertex_xyz tverts:vertex_uvw faces:triangles materialIDs:faces_matID
						
						-- Because the texture verts thing in 3ds max is broken, do this to restore texture coordinates:
						mmesh = m.mesh
						buildTVFaces mmesh
						for i = 1 to mmesh.numfaces do
						(
							setTVFace mmesh i (getFace mmesh i)
							setFaceSmoothGroup mmesh i 1
						)
					)
					
					-- Name the mesh based on its shader name for organization's sake.
					m.name = shader_name
					
					-- Put the mesh parts objects in an array.
					append parts_meshes m
					
										
				) -- End of Parts Loop --
				
				-- If the "Attach Parts of Mesh Within Region" checkbox is checked, attach the parts just created.
				if chk_attach.checked == true then
				(
					for w = 2 to parts_meshes.count do
					(
						attach parts_meshes[1] parts_meshes[w]
					)
					
					if radio_geom.state == 1 then
					(
						parts_meshes[1].name = geometry_block_region[c]
					)
					else
					(
						parts_meshes[1].name = listbox_reg.selected
					)
					
					append all_imported_geometries parts_meshes[1]
				)
				else
				(
					meshcount = parts_meshes.count
					for imc = 1 to meshcount do
					(
						append all_imported_geometries parts_meshes[imc]
					)
				)
				
				-- If the model's weights were imported, apply the skin modifier and weight the verts based on the
				-- bone index array and the bone weight array.
				if chk_weight.checked == true then
				(
					theSkin = Skin()
					max modify mode
					select parts_meshes[1]
					modPanel.addModToSelection theSkin
					
					if maxver[1] > 4200 then
					(
						theSkin.bone_Limit = 2
					)
					
					for node = 1 to node_array.count do
					(
						skinOps.addbone theSkin node_array[node] 1
					)
					
					subobjectLevel = 1
					
					skinVerts = skinOps.getNumberVertices theSkin
					for vt = 1 to skinVerts do
					(
						if vert_weight_bones[vt][2] == 0 then
						(
							skinOps.ReplaceVertexWeights theSkin vt vert_weight_bones[vt][1] vert_weights[vt][1]
						)
						else
						(
							if vert_weight_bones[vt][1] == 0 then
							(
								skinOps.ReplaceVertexWeights theSkin vt vert_weight_bones[vt][2] vert_weights[vt][2]
							)
							else
							(
								if vert_weight_bones[vt][1] == vert_weight_bones[vt][2] then
								(
									skinOps.ReplaceVertexWeights theSkin vt vert_weight_bones[vt][1] 1.000
								)
								else
								(
									skinOps.ReplaceVertexWeights theSkin vt vert_weight_bones[vt] vert_weights[vt]
								)
							)
						)
						pbar2.value = (vt as float / skinVerts) * 100.0
					)
					max create mode
				)
				
			) -- End of Geometries Loop --
			
			-- Setup 3ds Material Library if the checkbox is checked.
			if chk_matlib.checked == true then
			(
				meditMaterials[1] = multimaterial()
				meditMaterials[1].materialList.count = shader_index.count
				for sh = 1 to shader_index.count do
				(
					meditMaterials[1].names[sh] = shader_index[sh]
					meditMaterials[1].materialList[sh].name = shader_index[sh]
					meditMaterials[1].materialIDList[sh] = sh
				)
				for g = 1 to all_imported_geometries.count do
				(
					all_imported_geometries[g].material = meditMaterials[1]
				)
			)
			
			pbar.value = 0
			pbar2.value = 0
			
			end2 = timeStamp()
			time2 = (end2 - start2) / 1000.0
			format "Model importing took % seconds.\n" time2
		)
	)
	
	-- In order to be as user friendly as possible, a number of changes are made when the states of some
	-- of the checkboxes and radio buttons are changed. This also insures correct/compatible import options.
	
	on chk_uv changed theState do
	(
		if theState == true then
		(
			chk_matlib.checked = true
		)
		else
		(
			chk_matlib.checked = false
		)
	)
	
	on chk_weight changed theState do
	(
		if theState == true then
		(
			chk_attach.checked = true
			chk_n.checked = true
			if radio_geom.state == 2 then
			(
				radio_geom.state = 3
			)
			allValid = true
			for n = 1 to node_array.count do
			(
				if allValid == true then
				(
					if isValidNode node_array[n] == false then
					(
						allValid = false
					)
				)
			)
			if allValid == false then
			(
				chk_weight.checked = false
				chk_weight.enabled = false
				messageBox "Error: Cannot import vertex weights.  \n\nOne or more of the imported nodes has been deleted. Please re-import.  " \
					title: "Vertex weight error"
			)
			else
			(
				if local_nodes == true then
				(
					messageBox "Warning: This model's parts use local nodes. The model may not be rigged correctly." \
						title: "Import weights warning"
				)
			)
		)
	)
	
	on radio_geom changed i do
	(
		if i == 1 then
		(
			chk_attach.checked = true
		)
		if i == 2 then
		(
			chk_attach.checked = true
			chk_weight.checked = false
		)
		if i == 3 then
		(
			chk_attach.checked = true
		)
	)
	
	on chk_attach changed theState do
	(
		if theState == false then
		(
			if chk_weight.checked == true then
			(
				chk_attach.checked = true
				messageBox "Parts of mesh must be attached in order to import vertex weights." \
					title: "Checkbox selection warning"
			)
		)
	)
	
	on chk_n changed theState do
	(
		if theState == true then
		(
			spn_n.enabled = true
			lbl_n.enabled = true
			spn_b.enabled = true
			lbl_b.enabled = true
			chk_bones.enabled = true
			chk_bones.checked = true
		)
		else
		(
			spn_n.enabled = false
			lbl_n.enabled = false
			spn_b.enabled = false
			lbl_b.enabled = false
			chk_bones.checked = false
			chk_bones.enabled = false
		)
	)
	
	on chk_m changed theState do
	(
		if theState == true then
		(
			radio_markers.enabled = true
			spn_m.enabled = true
			lbl_m.enabled = true
		)
		else
		(
			radio_markers.enabled = false
			spn_m.enabled = false
			lbl_m.enabled = false
		)
	)
	
	on nm_button pressed do
	(
		if file_loaded == false then
		(
			messageBox "No file selected!\n\nPlease load an exported .gbxmodel tag.  " title:"File load error"
		)
		else
		(
			node_array = #()  -- Clear the node array
			node_parent = #()  -- An array which holds the index of each node's parent.
			node_child = #()  -- An array which holds the index of each node's immediate child.
			node_translation = #()  -- A Point3 array which holds the [x, y, z] translation of each node.
			node_rotation = #()  -- An array which holds the quaternion rotation (i, j, k, w) of each node.
			
			marker_array = #()  -- Clear the marker array
			marker_parent = #()  -- An array which holds the index of each marker's parent.
			marker_translation = #()  -- A Point3 array which holds the [x, y, z] translation of each marker.
			marker_rotation = #()  -- An array which holds the quaternion rotation (i, j, k, w) of each marker.
			marker_region = #()  -- An array which holds the name of each marker's region.
			marker_permutation = #()  -- (multi-dimensional, 2D) An array which holds the name of each of the marker's permutations.
			
			if chk_n.checked == true then
			(
				chk_weight.enabled = true
			)
			
			seek in_file 0
			
			skipToString in_file "nodes\tblock\t"
			node_blocks = readValue in_file
			
			-- All of the node information is located in one place. Parse the name, child/parent indices, translation, and rotation of each.
			for n = 1 to node_blocks do
			(
				node_array[n] = sphere radius:spn_n.value
				node_array[n].wirecolor = color 6 134 6
				
				skipToString in_file "name\tstring\t"
				nodename = readDelimitedString in_file "\n"
				node_array[n].name = nodename
				
				skipToString in_file "first child node index\tshort block index\t"
				skipToString in_file "\t"
				node_child[n] = readValue in_file + 1
				
				skipToString in_file "parent node index\tshort block index\t"
				skipToString in_file "\t"
				node_parent[n] = readValue in_file + 1
				
				skipToString in_file "real point 3d\t"
				x = readValue in_file * 100
				y = readValue in_file * 100
				z = readValue in_file * 100
				node_translation[n] = [x,y,z]
				
				skipToString in_file "real quaternion\t"
				i = -readValue in_file
				j = -readValue in_file
				k = -readValue in_file
				w = (readValue in_file)
				node_rotation[n] = #(i,j,k,w)
			)
			
			-- The markers are located in one of two places. Check both and gather all marker information.
			if chk_m.checked == true then
			(
				seek in_file 0
	
				skipToString in_file "markers\tblock\t"
				marker_blocks = readValue in_file
	
				for m = 1 to marker_blocks do
				(
					-- Create a sphere to represent the marker and name it according to the marker's name.
					marker_array[m] = sphere radius:spn_m.value
					marker_array[m].wirecolor = color 28 89 177
					skipToString in_file "name\tstring\t"
					marker_array[m].name = readDelimitedString in_file "\n"
					marker_array[m].name = "#" + marker_array[m].name
					
					-- Each marker in this area has a number of "instances" of itself in different permutations.
					-- Gather the information about which regions and permutations the marker belongs to.
					skipToString in_file "instances\tblock\t"
					instnum = readValue in_file
					
					skipToString in_file "region index\tchar integer\t"
					regindex = readValue in_file + 1
					marker_region[m] = reg_list[regindex]
					
					temp_perm_array = #()
					
					for i = 1 to instnum do
					(
						skipToString in_file "permutation index\tchar integer\t"
						permindex = readValue in_file + 1
						temp_perm_array[i] = perm_list[regindex][permindex]
					)
					
					marker_permutation[m] = temp_perm_array
					
					-- Gather the parent index, translation, and rotation of each marker. These values should be the same for each instance.
					skipToString in_file "node index\tchar integer\t"
					marker_parent[m] = readValue in_file + 1
					
					skipToString in_file "real point 3d\t"
					x = readValue in_file * 100
					y = readValue in_file * 100
				   	z = readValue in_file * 100
					marker_translation[m] = [x,y,z]
						
					skipToString in_file "real quaternion\t"
					i = -readValue in_file
					j = -readValue in_file
				   	k = -readValue in_file
					w = readValue in_file
					marker_rotation[m] = #(i,j,k,w)
					
				)
							
				skipToString in_file "regions\tblock\t"
				region_blocks = readValue in_file
				
				-- Loop over each region and permutation block to gather information about the markers.
				-- The marker belongs to the designated region and permutation based on the current block.
				for r = 1 to region_blocks do
				(
					skipToString in_file "permutations\tblock\t"
					permutation_blocks = readValue in_file
								
					for p = 1 to permutation_blocks do
					(
						skipToString in_file "markers\tblock\t"
						marker_blocks = readValue in_file
						
						for m = 1 to marker_blocks do
						(
							-- Define the array index to add the marker information to the end of the current marker array.
							c = marker_array.count + 1
							
							-- Define a sphere to represent the marker and name it according to the marker's name.
							marker_array[c] = sphere radius:spn_m.value
							marker_array[c].wirecolor = color 28 89 177
							skipToString in_file "name\tstring\t"
							marker_array[c].name = readDelimitedString in_file "\n"
							marker_array[c].name = "#" + marker_array[c].name
							
							-- Gather the information about the marker's parent index, translation, and rotation.
							skipToString in_file "node index\tshort block index\t"
							skipToString in_file "\t"
							marker_parent[c] = readValue in_file + 1
							
							skipToString in_file "real quaternion\t"
							i = -readValue in_file
							j = -readValue in_file
				   			k = -readValue in_file
							w = readValue in_file
							marker_rotation[c] = #(i,j,k,w)
							
							skipToString in_file "real point 3d\t"
							x = readValue in_file * 100
							y = readValue in_file * 100
				   			z = readValue in_file * 100
							marker_translation[c] = [x,y,z]
							
							-- Use the region and permutation block with the region and permutation lists to find out which region and permutation
							-- this marker belongs to. Permutation must be stored as an array because that's how it was done before with the instances.
							marker_region[c] = reg_list[r]
							marker_permutation[c] = #(perm_list[r][p])
						)
					)
				)
			)
						
			-- Link each node to its parent.
			for n = 2 to node_array.count do
			(
			  	node_array[n].parent = node_array[node_parent[n]]
			)
			
			-- Link each marker to its parent, only if "Import Markers" is checked.
			if chk_m.checked == true then
			(
				for n = 1 to marker_array.count do
			 	(
			  		marker_array[n].parent = node_array[marker_parent[n]]
			 	)
			)
			
			-- Move and rotate all nodes.
			for n = 1 to node_array.count do
			(
				-- Move the nodes in parent coordinate system.
				in coordsys parent move node_array[n] [node_translation[n].x, node_translation[n].y, node_translation[n].z]
				
				-- Rotate the nodes in parent coordinate system.
				rotation = quat node_rotation[n][1] node_rotation[n][2] node_rotation[n][3] node_rotation[n][4]
				in coordsys parent rotate node_array[n] rotation
			)
			
			-- Move and rotate markers only if checkbox "Import Markers" is checked.
			if chk_m.checked == true then
			(
				for n = 1 to marker_array.count do
				(
					-- Move the markers in parent coordinate system.
					in coordsys parent move marker_array[n] [marker_translation[n].x, marker_translation[n].y, marker_translation[n].z]
					
					-- Rotate the markers in parent coordinate system.
					rotation = quat marker_rotation[n][1] marker_rotation[n][2] marker_rotation[n][3] marker_rotation[n][4]
					in coordsys parent rotate marker_array[n] rotation
				)
			)
			
			-- This will create the bones in place of the spherical node objects.
			if chk_bones.checked == true then
			(
				for n = 1 to node_array.count do
				(
					-- Check to see if the node is a biped object. If so, create a bone object to represent it.
					if substring node_array[n].name 1 5 as name == "bip01" as name then
					(
						if node_child[n] == 0 then
						(
							-- If the bone has no child index then it is created as a short "stub".
							myBone = BoneSys.createBone node_array[n].pos [node_array[n].pos.x + spn_b.value,node_array[n].pos.y,node_array[n].pos.z] [0,0,1]
							myBone.name = node_array[n].name
							myBone.width = spn_b.value
							myBone.height = spn_b.value
							myBone.taper = 50
						)
						else
						(
							-- Bones are defined by their start and end points. The start point is the position of the node,
							-- and the end point is the position of the child node. Thus the bone "connects" the two.
							myBone = BoneSys.createBone node_array[n].pos node_array[node_child[n]].pos [0,0,1]
							myBone.name = node_array[n].name
							
							-- The pelvis, spine, and spine 1 bones don't connect to their child bones in the same way.
							if (myBone.name as name == "bip01 pelvis" as name) or (myBone.name as name == "bip01 spine" as name) or (myBone.name as name == "bip01 spine1" as name) then
							(
								if myBone.name as name == "bip01 spine1" as name then
								(
									myBone.scale = [0.8,1,1]
								)
								myBone.width = spn_b.value
								myBone.height = spn_b.value / 2
								myBone.taper = -180
							)
							else
							(
								myBone.width = spn_b.value
								myBone.height = spn_b.value
								myBone.taper = 70
							)
						)
						
						myBone.rotation = node_array[n].rotation
						myBone.pos = node_array[n].pos
						
						-- Replace the spherical node with the newly-created bone object.
						delete node_array[n]
						node_array[n] = myBone
					)
					
					-- Recreate the node hierarchy with the new-created bone objects.
					if node_parent[n] != 0 then
					(
						node_array[n].parent = node_array[node_parent[n]]
					)
				)
				
				-- Re-attach the markers to the newly-created bone objects.
				if chk_m.checked == true then
				(
					for n = 1 to marker_array.count do
				 	(
				  		marker_array[n].parent = node_array[marker_parent[n]]
				 	)
				)
			)
						
			-- If "Import Nodes" is not checked, now delete all of the nodes.
			-- They had to be created in the first place in order to position the markers correctly.
			if chk_n.checked != true then
			(
				for n = 1 to node_array.count do
				(
					delete node_array[n]
				)
			)
			
			-- Depending on the state of the radio buttons in the marker import options, delete the unwanted markers.
			if radio_markers.state == 1 then
			(
				for n = 1 to marker_array.count do
				(
					delete_marker = true
					for p = 1 to marker_permutation[n].count do
					(
						if marker_permutation[n][p] == listbox_perm.selected then
						(
							delete_marker = false
						)
					)
					if delete_marker == true and isValidNode marker_array[n] == true then
					(
						delete marker_array[n]
					)
				)
			)
			if radio_markers.state == 2 then
			(
				for n = 1 to marker_array.count do
				(
					if marker_region[n] != listbox_reg.selected then
					(
						delete marker_array[n]
					)
				)
			)
			if radio_markers.state == 3 then
			(
				for n = 1 to marker_array.count do
				(
					delete_marker = true
					for p = 1 to marker_permutation[n].count do
					(
						if marker_permutation[n][p] == listbox_perm.selected then
						(
							delete_marker = false
						)
					)
					if (delete_marker == true or marker_region[n] != listbox_reg.selected) and isValidNode marker_array[n] == true then
					(
						delete marker_array[n]
					)
				)
			)
		)
	)

) -- End of Rollout --

-- Create the rollout dialog and display on the screen.
createDialog roll

-- End of Script --