﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using System;
using System.IO;
using UnityEditor;

namespace T1
{
	public static class Const
	{
		public const int MAP_CHUNK_ROOT_VERSION = 0;
		public const int MAP_CHUNK_ROOT_WORLD = 1;
		public const int MAP_CHUNK_ROOT_WORLDSKYMAT = 2;
		public const int MAP_CHUNK_ROOT_COLLISION = 3;
		public const int MAP_CHUNK_ROOT_GRIDBOUNDS = 4;
		public const int MAP_CHUNK_ROOT_GRIDSECTIONS = 5;
		public const int MAP_CHUNK_ROOT_ACTORS = 6;
		public const int MAP_CHUNK_ROOT_VISIBILITY = 7;
		public const int MAP_CHUNK_ROOT_COUNT = 8;

		public const int MAP_CHUNK_WORLD_SUNDIRECTION = 0;
		public const int MAP_CHUNK_WORLD_SUNCOLOR = 1;
		public const int MAP_CHUNK_WORLD_AMBIENTCOLOR = 2;
		public const int MAP_CHUNK_WORLD_COUNT = 3;

		public const int MAP_CHUNK_COLLISION_VERTICES = 0;
		public const int MAP_CHUNK_COLLISION_SECTORSETS = 1;
		public const int MAP_CHUNK_COLLISION_SECTORS = 2;
		public const int MAP_CHUNK_COLLISION_COUNT = 3;

		public const int MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE = 0;
		public const int MAP_CHUNK_GRIDBOUNDS_MIN = 1;
		public const int MAP_CHUNK_GRIDBOUNDS_MAX = 2;
		public const int MAP_CHUNK_GRIDBOUNDS_COUNT = 3;

		public const int MAP_CHUNK_GRIDSECTIONS_MESHES = 0;
		public const int MAP_CHUNK_GRIDSECTIONS_MODELFILES = 1;
		public const int MAP_CHUNK_GRIDSECTIONS_COUNT = 2;

		public const int MAP_CHUNK_ACTOR_INFO = 0;
		public const int MAP_CHUNK_ACTOR_MODELFILES = 1;
		public const int MAP_CHUNK_ACTOR_ANIMFILES = 2;
		public const int MAP_CHUNK_ACTOR_COUNT = 3;

		public const int SIZEOF_VERSION = 4;
		public const int SIZEOF_VECTOR3 = 12;
		public const int SIZEOF_VECTOR4 = 16;
		public const int SIZEOF_COLLISION_VERTICE = 16;
		public const int SIZEOF_COLLISION_SECTORSET = 64;
		public const int SIZEOF_COLLISION_SECTOR = 18;
		public const int SIZEOF_STATICMESH = 88;
		public const int SIZEOF_ACTORINFO = 140;
	}
}

public class T1MapMono : MonoBehaviour
{
    public T1Map map;
}

[Serializable]
public class T1Map
{
    [HideInInspector]
    public int version; //always 1
    public T1WorldInfo worldInfo;
    [HideInInspector]
    public Vector4[] collisionVertices;
    [HideInInspector]
    public T1SectorSet[] collisionSectorSets;
    [HideInInspector]
    public T1Sector[] collisionSectors;
    [HideInInspector]
    public T1GridBounds gridBounds;
    public T1ActorInfo[] actors;
    [HideInInspector]
    public T1Visibility[] visibility; //size: byte to each bitmask lookups for staticmeshes, count: number of sectors (which staticmeshes are visible from this sector)

    public T1Map()
    {
        Clear();
    }

    public void Clear()
    {
        version = 1;
        worldInfo = new T1WorldInfo();
        collisionVertices = new Vector4[0];
        collisionSectorSets = new T1SectorSet[0];
        collisionSectors = new T1Sector[0];
        gridBounds = new T1GridBounds();
        actors = new T1ActorInfo[0];
        visibility = new T1Visibility[0];
    }

    public void Save(string path)
    {
        //-----Create Archives of this map structure for writing
		int rootChunks = T1.Const.MAP_CHUNK_ROOT_COUNT;
		if (visibility.Length == 0)
			rootChunks--;
        ArchiveIndex mapArc = new ArchiveIndex(0, rootChunks);

        //Version
        mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_VERSION, null, T1.Const.SIZEOF_VERSION + 4); //has 4 bytes padding

        //World Properties
        ArchiveIndex worldArc = new ArchiveIndex(mapArc[T1.Const.MAP_CHUNK_ROOT_WORLD], T1.Const.MAP_CHUNK_WORLD_COUNT);
        worldArc.SetBlock(T1.Const.MAP_CHUNK_WORLD_SUNDIRECTION, null, T1.Const.SIZEOF_VECTOR3 + 4); //has 4 bytes padding
        worldArc.SetBlock(T1.Const.MAP_CHUNK_WORLD_SUNCOLOR, null, T1.Const.SIZEOF_VECTOR4);
        worldArc.SetBlock(T1.Const.MAP_CHUNK_WORLD_AMBIENTCOLOR, null, T1.Const.SIZEOF_VECTOR4);

		mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_WORLD, worldArc);
		mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_WORLDSKYMAT, ArchiveDataSet.FromString(mapArc[T1.Const.MAP_CHUNK_ROOT_WORLDSKYMAT], worldInfo.material));

        //Collision
        ArchiveIndex collisionArc = new ArchiveIndex(mapArc[T1.Const.MAP_CHUNK_ROOT_COLLISION], T1.Const.MAP_CHUNK_COLLISION_COUNT);

        //Collision Vertices
        ArchiveDataSet collisionVertsArc = new ArchiveDataSet(collisionArc[T1.Const.MAP_CHUNK_COLLISION_VERTICES], collisionVertices.Length, T1.Const.SIZEOF_COLLISION_VERTICE);
        collisionArc.SetBlock(T1.Const.MAP_CHUNK_COLLISION_VERTICES, collisionVertsArc);

        //Collision SectorSets
        ArchiveDataSet collisionSectorSetsArc = new ArchiveDataSet(collisionArc[T1.Const.MAP_CHUNK_COLLISION_SECTORSETS], collisionSectorSets.Length, T1.Const.SIZEOF_COLLISION_SECTORSET);
        collisionArc.SetBlock(T1.Const.MAP_CHUNK_COLLISION_SECTORSETS, collisionSectorSetsArc);

        //Collision Sectors
        ArchiveDataSet collisionSectorsArc = new ArchiveDataSet(collisionArc[T1.Const.MAP_CHUNK_COLLISION_SECTORS], collisionSectors.Length, T1.Const.SIZEOF_COLLISION_SECTOR);
        collisionArc.SetBlock(T1.Const.MAP_CHUNK_COLLISION_SECTORS, collisionSectorsArc);

        mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_COLLISION, collisionArc);

        //GridBounds
        ArchiveIndex gridBoundsArc = new ArchiveIndex(mapArc[T1.Const.MAP_CHUNK_ROOT_GRIDBOUNDS], T1.Const.MAP_CHUNK_GRIDBOUNDS_COUNT);

        //GridBounds BlockSize
        ArchiveDataSet gridBoundsBlockSizeArc = new ArchiveDataSet(gridBoundsArc[T1.Const.MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE], 2, 2);
        gridBoundsArc.SetBlock(T1.Const.MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE, gridBoundsBlockSizeArc);

        //GridBounds Mins
        ArchiveDataSet gridBoundsMinsArc = new ArchiveDataSet(gridBoundsArc[T1.Const.MAP_CHUNK_GRIDBOUNDS_MIN], gridBounds.sections.Length, 8);
        gridBoundsArc.SetBlock(T1.Const.MAP_CHUNK_GRIDBOUNDS_MIN, gridBoundsMinsArc);

        //GridBounds Maxs
        ArchiveDataSet gridBoundsMaxsArc = new ArchiveDataSet(gridBoundsArc[T1.Const.MAP_CHUNK_GRIDBOUNDS_MAX], gridBounds.sections.Length, 8);
        gridBoundsArc.SetBlock(T1.Const.MAP_CHUNK_GRIDBOUNDS_MAX, gridBoundsMaxsArc);

        mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_GRIDBOUNDS, gridBoundsArc);

        //GridSections
        ArchiveIndex gridSectionsArc = new ArchiveIndex(mapArc[T1.Const.MAP_CHUNK_ROOT_GRIDSECTIONS], gridBounds.sections.Length);
        for (int i = 0; i < gridSectionsArc.count; i++)
        {
            ArchiveIndex gSectionArc;
            if (gridBounds.sections[i].staticMeshes.Length > 0)
            {
                gSectionArc = new ArchiveIndex(gridSectionsArc[i], T1.Const.MAP_CHUNK_GRIDSECTIONS_COUNT);
                //StaticMeshes
                ArchiveDataSet staticMeshesArc = new ArchiveDataSet(gSectionArc[T1.Const.MAP_CHUNK_GRIDSECTIONS_MESHES], gridBounds.sections[i].staticMeshes.Length, T1.Const.SIZEOF_STATICMESH);
                gSectionArc.SetBlock(T1.Const.MAP_CHUNK_GRIDSECTIONS_MESHES, staticMeshesArc);

                //ModelFiles
                ArchiveIndex modelFilesArc = new ArchiveIndex(gSectionArc[T1.Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES], gridBounds.sections[i].staticMeshes.Length);
                for (int j = 0; j < gridBounds.sections[i].staticMeshes.Length; j++)
                {
                    ArchiveDataSet modelNameArc = ArchiveDataSet.FromString(modelFilesArc[j], gridBounds.sections[i].staticMeshes[j].modelPath);
                    modelFilesArc.SetBlock(j, modelNameArc);
                }
                gSectionArc.SetBlock(T1.Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES, modelFilesArc);
            }
            else
            {
                gSectionArc = new ArchiveIndex(gridSectionsArc[i], 0);
            }

            gridSectionsArc.SetBlock(i, gSectionArc);
        }

        mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_GRIDSECTIONS, gridSectionsArc);

        //Actors
        ArchiveIndex actorsArc = new ArchiveIndex(mapArc[T1.Const.MAP_CHUNK_ROOT_ACTORS], T1.Const.MAP_CHUNK_ACTOR_COUNT);

		ArchiveDataSet actorsInfoArc = new ArchiveDataSet(actorsArc[T1.Const.MAP_CHUNK_ACTOR_INFO], actors.Length, T1.Const.SIZEOF_ACTORINFO);
		actorsArc.SetBlock(T1.Const.MAP_CHUNK_ACTOR_INFO, actorsInfoArc);

		ArchiveIndex actorsModelFileArc = new ArchiveIndex(actorsArc[T1.Const.MAP_CHUNK_ACTOR_MODELFILES], actors.Length);
		for (int i = 0; i < actors.Length; i++)
		{
			actorsModelFileArc.SetBlock(i, ArchiveDataSet.FromString(actorsModelFileArc[i], actors[i].modelPath));
		}
		actorsArc.SetBlock(T1.Const.MAP_CHUNK_ACTOR_MODELFILES, actorsModelFileArc);

		ArchiveIndex actorsAnimFileArc = new ArchiveIndex(actorsArc[T1.Const.MAP_CHUNK_ACTOR_ANIMFILES], actors.Length);
		for (int i = 0; i < actors.Length; i++)
		{
			actorsAnimFileArc.SetBlock(i, ArchiveDataSet.FromString(actorsAnimFileArc[i], actors[i].animPath));
		}
		actorsArc.SetBlock(T1.Const.MAP_CHUNK_ACTOR_ANIMFILES, actorsAnimFileArc);

        mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_ACTORS, actorsArc);

        //Visibility
        ArchiveDataSet visArc = null;
        if (visibility.Length != 0)
        {
            visArc = new ArchiveDataSet(mapArc[T1.Const.MAP_CHUNK_ROOT_VISIBILITY], visibility.Length, visibility[0].staticMeshes.Length);
			mapArc.SetBlock(T1.Const.MAP_CHUNK_ROOT_VISIBILITY, visArc);
        }

        //----------Start Write----------
        IOUtils.writeBuffer = new byte[mapArc.TotalSize];

        mapArc.WriteHeader();

        //Write Version
        IOUtils.writeIndex = mapArc[T1.Const.MAP_CHUNK_ROOT_VERSION];
        IOUtils.WriteInt32(version);

        worldArc.WriteHeader();
        IOUtils.writeIndex = worldArc[T1.Const.MAP_CHUNK_WORLD_SUNDIRECTION];
        IOUtils.WriteVector3(worldInfo.sunlightDirection);
        IOUtils.writeIndex = worldArc[T1.Const.MAP_CHUNK_WORLD_SUNCOLOR];
        IOUtils.WriteColorf(worldInfo.sunlightColor);
        IOUtils.writeIndex = worldArc[T1.Const.MAP_CHUNK_WORLD_AMBIENTCOLOR];
        IOUtils.WriteColorf(worldInfo.ambientColor);

		ArchiveDataSet worldSkyMatArc = mapArc.archives[T1.Const.MAP_CHUNK_ROOT_WORLDSKYMAT] as ArchiveDataSet;
        worldSkyMatArc.WriteHeader();
        IOUtils.writeIndex = worldSkyMatArc[0];
        IOUtils.WriteString(worldInfo.material);

        collisionArc.WriteHeader();
        //Write Collision Vertices
        collisionVertsArc.WriteHeader();
        for (int i = 0; i < collisionVertsArc.count; i++)
        {
            IOUtils.writeIndex = collisionVertsArc[i];
            IOUtils.WriteVector4(collisionVertices[i]);
        }

        //Write Collision SectorSets
        collisionSectorSetsArc.WriteHeader();
        for (int i = 0; i < collisionSectorSetsArc.count; i++)
        {
            collisionSectorSets[i].Write(collisionSectorSetsArc[i]);
        }

        //Write Collision Regions
        collisionSectorsArc.WriteHeader();
        for (int i = 0; i < collisionSectorsArc.count; i++)
        {
            collisionSectors[i].Write(collisionSectorsArc[i]);
        }

        gridBoundsArc.WriteHeader();
        //Write Gridbounds Block Size
        gridBoundsBlockSizeArc.WriteHeader();
        IOUtils.writeIndex = gridBoundsBlockSizeArc[0];
        IOUtils.WriteInt16(gridBounds.width);
        IOUtils.writeIndex = gridBoundsBlockSizeArc[1];
        IOUtils.WriteInt16(gridBounds.height);

        //Write Gridbounds Mins
        gridBoundsMinsArc.WriteHeader();
        for (int i = 0; i < gridBoundsMinsArc.count; i++)
        {
            IOUtils.writeIndex = gridBoundsMinsArc[i];
            IOUtils.WriteFloat(gridBounds.sections[i].boundsMin.x);
            IOUtils.WriteFloat(gridBounds.sections[i].boundsMin.y);
        }

        //Write Gridbounds Maxs
        gridBoundsMaxsArc.WriteHeader();
        for (int i = 0; i < gridBoundsMaxsArc.count; i++)
        {
            IOUtils.writeIndex = gridBoundsMaxsArc[i];
            IOUtils.WriteFloat(gridBounds.sections[i].boundsMax.x);
            IOUtils.WriteFloat(gridBounds.sections[i].boundsMax.y);
        }

        //Write GridSections
        gridSectionsArc.WriteHeader();
        for (int i = 0; i < gridSectionsArc.count; i++)
        {
            if (gridBounds.sections[i].staticMeshes.Length > 0)
            {
                ArchiveIndex sectionArc = gridSectionsArc.archives[i] as ArchiveIndex;
                sectionArc.WriteHeader();

                //StaticMeshes
                ArchiveDataSet staticMeshesArc = sectionArc.archives[T1.Const.MAP_CHUNK_GRIDSECTIONS_MESHES] as ArchiveDataSet;
                staticMeshesArc.WriteHeader();
                for (int j = 0; j < staticMeshesArc.count; j++)
                {
                    gridBounds.sections[i].staticMeshes[j].Write(staticMeshesArc[j]);
                }

                //ModelFiles
                ArchiveIndex modelFilesArc = sectionArc.archives[T1.Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES] as ArchiveIndex;
                modelFilesArc.WriteHeader();
                for (int j = 0; j < modelFilesArc.count; j++)
                {
                    ArchiveDataSet modelNameArc = modelFilesArc.archives[j] as ArchiveDataSet;
                    modelNameArc.WriteHeader();
                    IOUtils.writeIndex = modelNameArc[0];
                    IOUtils.WriteString(gridBounds.sections[i].staticMeshes[j].modelPath);
                }
            }
        }

        //Write Actors
        actorsArc.WriteHeader();
        actorsInfoArc.WriteHeader();
		for (int i = 0; i < actorsInfoArc.count; i++)
		{
			actors[i].Write(actorsInfoArc[i]);
		}
        actorsModelFileArc.WriteHeader();
		for (int i = 0; i < actorsModelFileArc.count; i++)
		{
			ArchiveDataSet modelFileArc = actorsModelFileArc.archives[i] as ArchiveDataSet;
			modelFileArc.WriteHeader();
			IOUtils.writeIndex = modelFileArc[0];
			IOUtils.WriteString(actors[i].modelPath);
		}
        actorsAnimFileArc.WriteHeader();
		for (int i = 0; i < actorsAnimFileArc.count; i++)
		{
			ArchiveDataSet animFileArc = actorsAnimFileArc.archives[i] as ArchiveDataSet;
			animFileArc.WriteHeader();
			IOUtils.writeIndex = animFileArc[0];
			IOUtils.WriteString(actors[i].animPath);
		}

		//Write Visibility
		if (visArc != null)
		{
			visArc.WriteHeader();
			for (int i = 0; i < visibility.Length; i++)
			{
				IOUtils.writeIndex = visArc[i];
				IOUtils.WriteBytes(visibility[i].staticMeshes);
			}
		}

        File.WriteAllBytes(path, IOUtils.writeBuffer);
    }

    public void Load(string path)
    {
        Clear();
        IOUtils.readData = File.ReadAllBytes(path);
        ArchiveIndex mapArc = ArchiveIndex.FromReadBuffer(0);

        //Read Root Version
        IOUtils.readByteIndex = mapArc[T1.Const.MAP_CHUNK_ROOT_VERSION];
        version = IOUtils.ReadInt32(); //should be 1

        //Read Root World Properties
        ArchiveIndex worldArc = ArchiveIndex.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_WORLD]);
		IOUtils.readByteIndex = worldArc[T1.Const.MAP_CHUNK_WORLD_SUNDIRECTION];
		worldInfo.sunlightDirection = IOUtils.ReadVector3();
		IOUtils.readByteIndex = worldArc[T1.Const.MAP_CHUNK_WORLD_SUNCOLOR];
		worldInfo.sunlightColor = IOUtils.ReadColorf();
		IOUtils.readByteIndex = worldArc[T1.Const.MAP_CHUNK_WORLD_AMBIENTCOLOR];
		worldInfo.ambientColor = IOUtils.ReadColorf();
		worldInfo.material = ArchiveDataSet.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_WORLDSKYMAT]).AsString();

        //Read Root Collision
        ArchiveIndex collisionArc = ArchiveIndex.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_COLLISION]);

        //Read Collision Vertices
        ArchiveDataSet collisionVertsArc = ArchiveDataSet.FromReadBuffer(collisionArc[T1.Const.MAP_CHUNK_COLLISION_VERTICES]);
        collisionVertices = new Vector4[collisionVertsArc.count];
        for (int i = 0; i < collisionVertsArc.count; i++)
        {
            IOUtils.readByteIndex = collisionVertsArc[i];
            collisionVertices[i] = IOUtils.ReadVector4();
        }

		//Read Collision SectorSets
		ArchiveDataSet collisionSectorSetsArc = ArchiveDataSet.FromReadBuffer(collisionArc[T1.Const.MAP_CHUNK_COLLISION_SECTORSETS]);
		collisionSectorSets = new T1SectorSet[collisionSectorSetsArc.count];
		for (int i = 0; i < collisionSectorSetsArc.count; i++)
		{
			collisionSectorSets[i].ReadFromOffset(collisionSectorSetsArc[i]);
		}

		//Read Collision Sectors
		ArchiveDataSet collisionSectorsArc = ArchiveDataSet.FromReadBuffer(collisionArc[T1.Const.MAP_CHUNK_COLLISION_SECTORS]);
		collisionSectors = new T1Sector[collisionSectorsArc.count];
		for (int i = 0; i < collisionSectorsArc.count; i++)
		{
			collisionSectors[i].ReadFromOffset(collisionSectorsArc[i]);
		}

        //Read Root Grid Bounds and Grid Sections
        gridBounds.ReadFromGridBounds(ArchiveIndex.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_GRIDBOUNDS]));
        gridBounds.ReadFromGridSections(ArchiveIndex.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_GRIDSECTIONS]));


        //Read Root Actors
        ArchiveIndex actorsArc = ArchiveIndex.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_ACTORS]);

		ArchiveDataSet actorsInfoArc = ArchiveDataSet.FromReadBuffer(actorsArc[T1.Const.MAP_CHUNK_ACTOR_INFO]);
        actors = new T1ActorInfo[actorsInfoArc.count];
		for (int i = 0; i < actorsInfoArc.count; i++)
		{
			actors[i] = new T1ActorInfo();
			actors[i].ReadFromOffset(actorsInfoArc[i]);
		}
        ArchiveIndex actorsModelFilesArc = ArchiveIndex.FromReadBuffer(actorsArc[T1.Const.MAP_CHUNK_ACTOR_MODELFILES]);
		for (int i = 0; i < actorsModelFilesArc.count; i++)
		{
			actors[i].modelPath = ArchiveDataSet.FromReadBuffer(actorsModelFilesArc[i]).AsString();
		}
        ArchiveIndex actorsAnimFilesArc = ArchiveIndex.FromReadBuffer(actorsArc[T1.Const.MAP_CHUNK_ACTOR_ANIMFILES]);
		for (int i = 0; i < actorsAnimFilesArc.count; i++)
		{
			actors[i].animPath = ArchiveDataSet.FromReadBuffer(actorsAnimFilesArc[i]).AsString();
		}

		//Read Root Visibility (if has it)
		if (mapArc.count > T1.Const.MAP_CHUNK_ROOT_VISIBILITY)
		{
			ArchiveDataSet visArc = ArchiveDataSet.FromReadBuffer(mapArc[T1.Const.MAP_CHUNK_ROOT_VISIBILITY]);
			visibility = new T1Visibility[visArc.count];
			for (int i = 0; i < visArc.count; i++)
			{
				IOUtils.readByteIndex = visArc[i];
				visibility[i] = new T1Visibility();
				visibility[i].staticMeshes = new byte[visArc.size];
				for (int j = 0; j < visArc.size; j++)
				{
					visibility[i].staticMeshes[j] = IOUtils.ReadByte();
				}
			}
		}
	}
}

[Serializable]
public class T1ActorInfo
{
	public string modelPath;
	public string animPath;
    public int actorID; //AKA Type in Turok EX
    public Vector3 origin;
    public Vector3 scale;
    public float yaw; //in rads
	public int sectorIndex;
    public Vector3 boundsMin;
    public Vector3 boundsMax;
	public float sightRange; //(is ^2. Sqr to get editor value)
	public float sightFOV; //in rads
	public float loudRange; //(is ^2. Sqr to get editor value)
	public float quietRange; //(is ^2. Sqr to get editor value)
	public float attackRange; //(is ^2. Sqr to get editor value)
	public float flyHeight; //(is ^2. Sqr to get editor value)
	public float width;
	public float wallWidth;
	public float height;
	public float deadHeight; //AKA Step Height
	public float meleeRange; //(is ^2. Sqr to get editor value)
	public float leashRadius; //(is ^2. Sqr to get editor value)
	public float triggerDelay;
    [EnumFlags]
	public ActorSpeciesFlags speciesMask; //Unused in Turok EX
    [EnumFlags]
    public ActorSpawnFlags1 spawnFlags1;
    [EnumFlags]
    public ActorSpawnFlags2 spawnFlags2;
    [EnumFlags]
    public ActorSpawnFlags3 spawnFlags3;
    public short health;
    public short TID;
    public short targetTID;
    public short maxRegenerations;
    public byte attackChance;
    public byte triggerAnim;
    public sbyte variant;
    public sbyte texture;
    public sbyte params1;
    public sbyte params2;

    [Flags]
    public enum ActorSpawnFlags1 : uint
    {
        SOLID = 1 << 0,
        PROJECTILE_ATTACK1 = 1 << 1,
        LEADER = 1 << 2,
        SNAP_TO_FLOOR = 1 << 3,
        EXPLOSION_DEATH = 1 << 4,
        CLIMB_WALLS = 1 << 5,
        PROJECTILE_ATTACK2 = 1 << 6,
        NO_REPEAT_EXPLOSION = 1 << 7,
        DIE_ON_EXPLOSION = 1 << 8,
        FLOCKER = 1 << 9,
        BIT10 = 1 << 10,
        BIT11 = 1 << 11,
        BIT12 = 1 << 12,
        KAMIKAZE = 1 << 13,
        AVOID_PLAYERS = 1 << 14,
        FLOAT_IN_WATER_ON_DEATH = 1 << 15,
        TELEPORT = 1 << 16,
        CAST_SHADOW = 1 << 17,
        TELEPORT_WAIT = 1 << 18,
        USE_STRONG_ATTACKS = 1 << 19,
        USE_WEAK_ATTACKS = 1 << 20,
        SNIPER = 1 << 21,
        MELT_ON_DEATH = 1 << 22,
        AVOID_WATER = 1 << 23,
        FLYING = 1 << 24,
        TELEPORT_AVOID_WATER = 1 << 25,
        TELEPORT_AVOID_CLIFFS = 1 << 26,
        TRIGGER_STUFF = 1 << 27,
        CANNOT_CAUSE_A_FIGHT = 1 << 28,
        NO_WALL_COLLISION = 1 << 29,
        SCREEN_SHAKE = 1 << 30,
        RESPAWN_ANIMATION = 1u << 31
    };

	[Flags]
    public enum ActorSpawnFlags2 : uint
    {
        DROP_ITEM_MASK1 = 1 << 0,
        DROP_ITEM_MASK2 = 1 << 1,
        DROP_ITEM_MASK3 = 1 << 2,
        DROP_ITEM_MASK4 = 1 << 3,
        DROP_ITEM_MASK5 = 1 << 4,
        DROP_ITEM_MASK6 = 1 << 5,
        DROP_ITEM_MASK7 = 1 << 6,
        DROP_ITEM_MASK8 = 1 << 7,
        DROP_ITEM_MASK9 = 1 << 8,
        DROP_ITEM_MASK10 = 1 << 9,
        DROP_ITEM_MASK11 = 1 << 10,
        DROP_ITEM_MASK12 = 1 << 11,
        DROP_ITEM_MASK13 = 1 << 12,
        DROP_ITEM_MASK14 = 1 << 13,
        REMOVE_ON_COMPLETION = 1 << 14,
        NO_BLOOD = 1 << 15,
        HOLD_TRIGGER_ANIMATION = 1 << 16,
        PROJECTILE_ATTACK3 = 1 << 17,
        PROJECTILE_ATTACK4 = 1 << 18,
        DROP_ITEM_ON_DAMAGE = 1 << 19,
        NO_AUTOMAP_DRAW = 1 << 20,
        ALTERNATE_MOVES = 1 << 21,
		BIT22 = 1 << 22,
		BIT23 = 1 << 23,
        PROJECTILE_ATTACK5 = 1 << 24,
        PROJECTILE_ATTACK6 = 1 << 25,
        MORTAL_WOUND_IMPACT = 1 << 26,
        STAY_IN_WATER = 1 << 27,
		BIT28 = 1 << 28,
		BIT29 = 1 << 29,
        PROJECTILE_ATTACK7 = 1 << 30,
        PROJECTILE_ATTACK8 = 1u << 31,
    };

	[Flags]
    public enum ActorSpawnFlags3 : ushort
    {
        BIT0 = 1 << 0,
        PLAY_TRIGGER_ANIM_ONCE = 1 << 1,
        REGENERATE_FROM_START = 1 << 2,
        WALK_IN_STRAIGHT_LINE = 1 << 3,
        BIT4 = 1 << 4,
        NO_THINKER = 1 << 5,
        AVOID_PLAYERS2 = 1 << 6,
        NO_VIOLENT_DEATH = 1 << 7,
        PROJECTILE_ATTACK9 = 1 << 8,
        PROJECTILE_ATTACK10 = 1 << 9,
        MAKE_SPAWN_ANIM_VISIBLE = 1 << 10,
        NO_DRAW_ON_CAMERA = 1 << 11,
        BIT12 = 1 << 12,
        BIT13 = 1 << 13,
        BIT14 = 1 << 14,
        BIT15 = 1 << 15
    };

	[Flags]
	public enum ActorSpeciesFlags : uint //unused.
	{
		RAPTOR = 1 << 0, // raptor dinosaur
		HUMAN1 = 1 << 1, // human being type 1
		SABERTIGER = 1 << 2, // saber tooth tiger
		DIMETRODON = 1 << 3, // dimetrodon
		TRICERATOPS = 1 << 4, // triceratops
		MOSCHOPS = 1 << 5, // moschops
		PTERANODON = 1 << 6, // pteranodon
		SUBTERRANEAN = 1 << 7, // subterranean
		LEAPER = 1 << 8, // leaper
		ALIEN = 1 << 9, // alien
		HULK = 1 << 10, // hulk
		ROBOT = 1 << 11, // robot
		MANTIS_BOSS = 1 << 12, // mantis boss
		KEX_DEFAULT = 0xFF //Kex Editor default value
	};

    public T1ActorInfo()
    {
        scale = new Vector3(0.5f, 0.5f, 0.5f);
		boundsMin = new Vector3(-0.5f, -0.5f, -0.5f);
		boundsMax = new Vector3(0.5f, 0.5f, 0.5f);
		sightRange = 2359296.0f;
		sightFOV = 0.78500003f;
		loudRange = 2359296.0f;
		quietRange = 41943.035f;
		attackRange = 671088.56f;
		width = 10.24f;
		wallWidth = 5.1199999f;
		height = 10.24f;
		deadHeight = 3.072f;
		meleeRange = 6710.8862f;
		leashRadius = 4194304.0f;
		speciesMask = ActorSpeciesFlags.KEX_DEFAULT;
		spawnFlags1 = ActorSpawnFlags1.SOLID;
		health = 100;
		TID = -1;
		targetTID = -1;
		attackChance = 100;
    }

    public static T1ActorInfo CreateFrom(T1ActorInfo other)
    {
        T1ActorInfo actor = new T1ActorInfo();
        actor.actorID = other.actorID;
        actor.origin = other.origin;
        actor.scale = other.scale;
        actor.yaw = other.yaw;
		actor.sectorIndex = other.sectorIndex;
        actor.boundsMin = other.boundsMin;
        actor.boundsMax = other.boundsMax;
        actor.sightRange = other.sightRange;
        actor.sightFOV = other.sightFOV;
        actor.loudRange = other.loudRange;
        actor.quietRange = other.quietRange;
        actor.attackRange = other.attackRange;
        actor.flyHeight = other.flyHeight;
        actor.width = other.width;
        actor.wallWidth = other.wallWidth;
        actor.health = other.health;
        actor.deadHeight = other.deadHeight;
        actor.meleeRange = other.meleeRange;
        actor.leashRadius = other.leashRadius;
        actor.triggerDelay = other.triggerDelay;
        actor.speciesMask = other.speciesMask;
        actor.spawnFlags1 = other.spawnFlags1;
        actor.spawnFlags2 = other.spawnFlags2;
        actor.spawnFlags3 = other.spawnFlags3;
        actor.health = other.health;
        actor.TID = other.TID;
        actor.targetTID = other.targetTID;
        actor.maxRegenerations = other.maxRegenerations;
        actor.attackChance = other.attackChance;
        actor.triggerAnim = other.triggerAnim;
        actor.variant = other.variant;
        actor.texture = other.texture;
        actor.params1 = other.params1;
        actor.params2 = other.params2;

        return actor;
    }

    public void ReadFromOffset(int offset)
    {
		//140
		IOUtils.readByteIndex = offset;
		actorID = IOUtils.ReadInt32();
        origin = IOUtils.ReadVector3();
        scale = IOUtils.ReadVector3();
        yaw = IOUtils.ReadFloat();
        sectorIndex = IOUtils.ReadInt32();
        boundsMin = IOUtils.ReadVector3();
        boundsMax = IOUtils.ReadVector3();
        sightRange = IOUtils.ReadFloat();
        sightFOV = IOUtils.ReadFloat();
        loudRange = IOUtils.ReadFloat();
        quietRange = IOUtils.ReadFloat();
        attackRange = IOUtils.ReadFloat();
        flyHeight = IOUtils.ReadFloat();
        width = IOUtils.ReadFloat();
        wallWidth = IOUtils.ReadFloat();
        height = IOUtils.ReadFloat();
        deadHeight = IOUtils.ReadFloat();
        meleeRange = IOUtils.ReadFloat();
        leashRadius = IOUtils.ReadFloat();
        triggerDelay = IOUtils.ReadFloat();
        speciesMask = (ActorSpeciesFlags)IOUtils.ReadUInt32();
        spawnFlags1 = (ActorSpawnFlags1)IOUtils.ReadUInt32();
        spawnFlags2 = (ActorSpawnFlags2)IOUtils.ReadUInt32();
        spawnFlags3 = (ActorSpawnFlags3)IOUtils.ReadUInt16();
        health = IOUtils.ReadInt16();
        TID = IOUtils.ReadInt16();
        targetTID = IOUtils.ReadInt16();
        maxRegenerations = IOUtils.ReadInt16();
        attackChance = IOUtils.ReadByte();
        triggerAnim = IOUtils.ReadByte();
        variant = IOUtils.ReadSByte();
        texture = IOUtils.ReadSByte();
        params1 = IOUtils.ReadSByte();
        params2 = IOUtils.ReadSByte();
    }

    public void Write(int offset)
    {
        IOUtils.writeIndex = offset;
        IOUtils.WriteInt32(actorID);
        IOUtils.WriteVector3(origin);
        IOUtils.WriteVector3(scale);
        IOUtils.WriteFloat(yaw);
        IOUtils.WriteInt32(sectorIndex);
        IOUtils.WriteVector3(boundsMin);
        IOUtils.WriteVector3(boundsMax);
        IOUtils.WriteFloat(sightRange);
        IOUtils.WriteFloat(sightFOV);
        IOUtils.WriteFloat(loudRange);
        IOUtils.WriteFloat(quietRange);
        IOUtils.WriteFloat(attackRange);
        IOUtils.WriteFloat(flyHeight);
        IOUtils.WriteFloat(width);
        IOUtils.WriteFloat(wallWidth);
        IOUtils.WriteFloat(height);
        IOUtils.WriteFloat(deadHeight);
        IOUtils.WriteFloat(meleeRange);
        IOUtils.WriteFloat(leashRadius);
        IOUtils.WriteFloat(triggerDelay);
        IOUtils.WriteUInt32((uint)speciesMask);
        IOUtils.WriteUInt32((uint)spawnFlags1);
        IOUtils.WriteUInt32((uint)spawnFlags2);
        IOUtils.WriteUInt16((ushort)spawnFlags3);
		IOUtils.WriteInt16(health);
		IOUtils.WriteInt16(TID);
		IOUtils.WriteInt16(targetTID);
		IOUtils.WriteInt16(maxRegenerations);
		IOUtils.WriteByte(attackChance);
		IOUtils.WriteByte(triggerAnim);
		IOUtils.WriteSByte(variant);
		IOUtils.WriteSByte(texture);
		IOUtils.WriteSByte(params1);
		IOUtils.WriteSByte(params2);
    }
}

[Serializable]
public class T1GridSection
{
	public Vector2 boundsMin;
	public Vector2 boundsMax;
	public T1StaticMesh[] staticMeshes;

	public T1GridSection()
	{
		staticMeshes = new T1StaticMesh[0];
	}

	public void ReadFromGridSection(ArchiveIndex sectionArc)
	{
		//sectionArc should have 2 blocks (meshes and model file paths) or 0
		if (sectionArc.count > 0)
		{
			//Read Grid Sections Meshes
			ArchiveDataSet meshesArc = ArchiveDataSet.FromReadBuffer(sectionArc[T1.Const.MAP_CHUNK_GRIDSECTIONS_MESHES]);
			staticMeshes = new T1StaticMesh[meshesArc.count];
			for (int i = 0; i < meshesArc.count; i++)
			{
				staticMeshes[i].ReadFromOffset(meshesArc[i]);
			}

			//Read Grid Sections Model Files
			ArchiveIndex modelFilesArc = ArchiveIndex.FromReadBuffer(sectionArc[T1.Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES]); //should be same amount as meshesArc count
			for (int i = 0; i < modelFilesArc.count; i++)
			{
				staticMeshes[i].modelPath = ArchiveDataSet.FromReadBuffer(modelFilesArc[i]).AsString();
			}
		}
	}
}

[Serializable]
public class T1GridBounds
{
    public short width;
    public short height;
    public T1GridSection[] sections;

    public T1GridBounds()
    {
        sections = new T1GridSection[0];
    }

    public void ReadFromGridBounds(ArchiveIndex gridBoundsArc)
    {
        //gridBoundsArc should have 3 blocks: blocksize, min, max

        //Read Grid Bounds Block Size (should be 2, width/height)
        ArchiveDataSet blockSizeArc = ArchiveDataSet.FromReadBuffer(gridBoundsArc[T1.Const.MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE]);
        IOUtils.readByteIndex = blockSizeArc[0];
        width = IOUtils.ReadInt16();
        IOUtils.readByteIndex = blockSizeArc[1];
        height = IOUtils.ReadInt16();

        sections = new T1GridSection[width * height];

        //Read Grid Bounds Sections Bounds Min
        ArchiveDataSet minsArc = ArchiveDataSet.FromReadBuffer(gridBoundsArc[T1.Const.MAP_CHUNK_GRIDBOUNDS_MIN]); //should be same number as width*height
        for (int i = 0; i < minsArc.count; i++)
        {
            sections[i] = new T1GridSection();
            IOUtils.readByteIndex = minsArc[i];
            float x = IOUtils.ReadFloat();
            float y = IOUtils.ReadFloat();
            sections[i].boundsMin.Set(x, y);
        }

        //Read Grid Bounds Sections Bounds Max
        ArchiveDataSet maxsArc = ArchiveDataSet.FromReadBuffer(gridBoundsArc[T1.Const.MAP_CHUNK_GRIDBOUNDS_MAX]); //should be same number as width*height
        for (int i = 0; i < maxsArc.count; i++)
        {
            IOUtils.readByteIndex = maxsArc[i];
            float x = IOUtils.ReadFloat();
            float y = IOUtils.ReadFloat();
            sections[i].boundsMax.Set(x, y);
        }
    }

    public void ReadFromGridSections(ArchiveIndex gridSectionsArc)
    {
        //gridSectionsArc should be have same number of blocks as as width*height
        for (int i = 0; i < gridSectionsArc.count; i++)
        {
            sections[i].ReadFromGridSection(ArchiveIndex.FromReadBuffer(gridSectionsArc[i]));
        }
    }
}

[Serializable]
public struct T1WorldInfo
{
    public Color sunlightColor;
    public Color ambientColor;
    public Vector3 sunlightDirection;
    public string material;

    public void SetToDefault()
    {
        sunlightColor = new Color32(0, 0, 0, 255);
        ambientColor = new Color(0.24705882f, 0.24705882f, 0.24705882f, 1.0f);
		sunlightDirection = new Vector3(0.0f, 0.0f, 0.0f);
        material = "";
    }
}

[Serializable]
//links is the index to the other region that shares an edge with the current region
//if its -1 then nothing is linked and the edge is a solid wall
public struct T1Sector
{
    public short sectorSetIndex;
    [EnumFlags]
    public SectorFlags flags;
    public short indice1;
    public short indice2;
    public short indice3;
    public short edgeLink1;
    public short edgeLink2;
    public short edgeLink3;
    public short drawOrder;

    [Flags]
    public enum SectorFlags : ushort
    {
        WATER = 1 << 0,
        BLOCK = 1 << 1,
        TOGGLE = 1 << 2,
        CLIFF = 1 << 3,
        CLIMB = 1 << 4,
        ONESIDED = 1 << 5, //Bridge
        CEILING = 1 << 6,
        CRAWL = 1 << 7,
        ENTERCRAWL = 1 << 8,
        HIDDEN = 1 << 9,
        ENTERED = 1 << 10, //Not shown in Kex Editor
        SECRET = 1 << 11, //Not defined in Turok EX scripting
        RESTRICTED = 1 << 12,
        SLOPETEST = 1 << 13,
        DEATHPIT = 1 << 14,
        MAPPED = 1 << 15
    }

    public void ReadFromOffset(int offset)
    {
        IOUtils.readByteIndex = offset;
        sectorSetIndex = IOUtils.ReadInt16();
        flags = (SectorFlags)IOUtils.ReadUInt16();
        indice1 = IOUtils.ReadInt16();
        indice2 = IOUtils.ReadInt16();
        indice3 = IOUtils.ReadInt16();
        edgeLink1 = IOUtils.ReadInt16();
        edgeLink2 = IOUtils.ReadInt16();
        edgeLink3 = IOUtils.ReadInt16();
        drawOrder = IOUtils.ReadInt16();
    }

    public void Write(int offset)
    {
        IOUtils.writeIndex = offset;
        IOUtils.WriteInt16(sectorSetIndex);
        IOUtils.WriteUInt16((ushort)flags);
        IOUtils.WriteInt16(indice1);
        IOUtils.WriteInt16(indice2);
        IOUtils.WriteInt16(indice3);
        IOUtils.WriteInt16(edgeLink1);
        IOUtils.WriteInt16(edgeLink2);
        IOUtils.WriteInt16(edgeLink3);
        IOUtils.WriteInt16(drawOrder);
    }
}

[Serializable]
public struct T1SectorSet //AKA Area in Turok EX
{
    [EnumFlags]
    public SectorSetFlags flags;
    public Color32 fogColor;
    public Color32 waterColor;
    public float fogZFar;
    public float waterZFar;
    public float fogStart;
    public float waterStart;
    public float waterHeight;
    public float skyHeight;
    public float skySpeed; //unused in Turok EX
    public float blendLength;
    public short arg1, arg2, arg3, arg4, arg5, arg6;
    public byte floorImpactID;
    public byte wallImpactID;
    public byte ambience;
    public byte automapColor;
    public byte music;
	public byte cullBits; //default to 255
	public byte unused1; //just padding in the mapfile
	public byte unused2; //just padding in the mapfile

    [Flags]
    public enum SectorSetFlags : uint //AKA EnumAreaFlags in Turok EX
    {
        WATER = 1 << 0,
        BLOCK = 1 << 1,
        TOGGLE = 1 << 2,
        CLIFF = 1 << 3,
        CLIMB = 1 << 4,
        ONESIDED = 1 << 5, //Bridge
        CEILING = 1 << 6,
        CRAWL = 1 << 7,
        ENTERCRAWL = 1 << 8,
        HIDDEN = 1 << 9,
        ENTERED = 1 << 10, //Not shown in Kex Editor
        SECRET = 1 << 11, //Not defined in Turok EX scripting
        RESTRICTED = 1 << 12,
        SLOPETEST = 1 << 13,
        DEATHPIT = 1 << 14,
        MAPPED = 1 << 15,
        EVENT = 1 << 16,
        REPEATABLE = 1 << 17,
        TELEPORT = 1 << 18,
        DAMAGE = 1 << 19,
        DRAWSKY = 1 << 20,
        TELEPORTAIR = 1 << 21,
        LAVA = 1 << 22,
        EVENTSOUND = 1 << 23,
        ANTIGRAVITY = 1 << 24,
        LADDER = 1 << 25,
        CHECKPOINT = 1 << 26,
        SAVEGAME = 1 << 27,
        WARPRETURN = 1 << 28,
        SHALLOWWATER = 1 << 29,
        DRAWSUN = 1 << 30,
        STOREWARPRETURN = 1u << 31 //Not defined in Turok EX scripting
    };

    public void ReadFromOffset(int offset)
    {
        IOUtils.readByteIndex = offset;
        flags = (SectorSetFlags)IOUtils.ReadUInt32();
        fogColor = IOUtils.ReadColor();
        waterColor = IOUtils.ReadColor();
        fogZFar = IOUtils.ReadFloat();
        waterZFar = IOUtils.ReadFloat();
        fogStart = IOUtils.ReadFloat();
        waterStart = IOUtils.ReadFloat();
        waterHeight = IOUtils.ReadFloat();
        skyHeight = IOUtils.ReadFloat();
        skySpeed = IOUtils.ReadFloat();
        blendLength = IOUtils.ReadFloat();
        arg1 = IOUtils.ReadInt16();
        arg2 = IOUtils.ReadInt16();
        arg3 = IOUtils.ReadInt16();
        arg4 = IOUtils.ReadInt16();
        arg5 = IOUtils.ReadInt16();
        arg6 = IOUtils.ReadInt16();
        floorImpactID = IOUtils.ReadByte();
        wallImpactID = IOUtils.ReadByte();
        ambience = IOUtils.ReadByte();
        automapColor = IOUtils.ReadByte();
        music = IOUtils.ReadByte();
		cullBits = IOUtils.ReadByte();
		unused1 = IOUtils.ReadByte();
		unused2 = IOUtils.ReadByte();
    }

    public void Write(int offset)
    {
        IOUtils.writeIndex = offset;
        IOUtils.WriteUInt32((uint)flags);
        IOUtils.WriteColor(fogColor);
        IOUtils.WriteColor(waterColor);
        IOUtils.WriteFloat(fogZFar);
        IOUtils.WriteFloat(waterZFar);
        IOUtils.WriteFloat(fogStart);
        IOUtils.WriteFloat(waterStart);
        IOUtils.WriteFloat(waterHeight);
        IOUtils.WriteFloat(skyHeight);
        IOUtils.WriteFloat(skySpeed);
        IOUtils.WriteFloat(blendLength);
        IOUtils.WriteInt16(arg1);
        IOUtils.WriteInt16(arg2);
        IOUtils.WriteInt16(arg3);
        IOUtils.WriteInt16(arg4);
        IOUtils.WriteInt16(arg5);
        IOUtils.WriteInt16(arg6);
        IOUtils.WriteByte(floorImpactID);
        IOUtils.WriteByte(wallImpactID);
        IOUtils.WriteByte(ambience);
        IOUtils.WriteByte(automapColor);
        IOUtils.WriteByte(music);
		IOUtils.WriteByte(cullBits);
		IOUtils.WriteByte(unused1);
		IOUtils.WriteByte(unused2);
    }
}

[Serializable]
public struct T1StaticMesh
{
    public string modelPath;
    public Vector3 origin;
    public Vector3 scale;
	public Quaternion rotation;
    public Vector3 boundsMin;
    public Vector3 boundsMax;
    public int texIndex;
    [EnumFlags]
    public StaticMeshFlags flags;
    public int regionIndex;
    public float collisionWidth;
    public float collisionHeight;
    public int cullBits; //default to -1

    [Flags]
    public enum StaticMeshFlags : int
    {
        COLLIDE = 1 << 0,
        NOPOLYCOLLISION = 1 << 1,
        NODRAWCAMERA = 1 << 2
    }

	public void SetToDefault()
	{
		modelPath = "";
		origin = Vector3.zero;
		scale = new Vector3(0.35f, 0.35f, 0.35f);
		rotation = new Quaternion();
		boundsMin = new Vector3(-0.5f, -0.5f, -0.5f);
		boundsMax = new Vector3(0.5f, 0.5f, 0.5f);
		texIndex = 0;
		flags = 0;
		regionIndex = -1;
		collisionWidth = 0.0f;
		collisionHeight = 0.0f;
		cullBits = -1;
	}

    //Doesn't set model path
    public void ReadFromOffset(int offset)
    {
        IOUtils.readByteIndex = offset;
        origin = IOUtils.ReadVector3();
        scale = IOUtils.ReadVector3();
        rotation = IOUtils.ReadQuaternion();
        boundsMin = IOUtils.ReadVector3();
        boundsMax = IOUtils.ReadVector3();
        texIndex = IOUtils.ReadInt32();
        flags = (StaticMeshFlags)IOUtils.ReadInt32();
        regionIndex = IOUtils.ReadInt32();
        collisionWidth = IOUtils.ReadFloat();
        collisionHeight = IOUtils.ReadFloat();
        cullBits = IOUtils.ReadInt32();
    }

    //Doesn't write the model path
    public void Write(int offset)
    {
        IOUtils.writeIndex = offset;
        IOUtils.WriteVector3(origin);
        IOUtils.WriteVector3(scale);
        IOUtils.WriteQuaternion(rotation);
        IOUtils.WriteVector3(boundsMin);
        IOUtils.WriteVector3(boundsMax);
        IOUtils.WriteInt32(texIndex);
        IOUtils.WriteInt32((int)flags);
        IOUtils.WriteInt32(regionIndex);
        IOUtils.WriteFloat(collisionWidth);
        IOUtils.WriteFloat(collisionHeight);
        IOUtils.WriteInt32(cullBits);
    }
}

[Serializable]
public class T1Visibility
{
    public byte[] staticMeshes; //lookups for staticmeshes

    public T1Visibility()
    {
        staticMeshes = new byte[0]; //bit ON = visible
    }
}

[Serializable]
public struct MapGridInfo
{
    public Vector2Int blockSize;
    public Vector2 min;
    public Vector2 max;
    public Vector2 size;
    public Vector2 unit;

    //cWorldBounds is based on the total sum of all bounding boxes of every static mesh in the level
    public void BuildGridInfo(Bounds cWorldBounds, float fGridSize)
    {
        Assert.IsTrue(fGridSize >= 1.0f);

        min[0] = cWorldBounds.min.x;
        min[1] = cWorldBounds.min.y;
        max[0] = cWorldBounds.max.x;
        max[1] = cWorldBounds.max.y;

        size[0] = (cWorldBounds.max.x - cWorldBounds.min.x);
        size[1] = (cWorldBounds.max.y - cWorldBounds.min.y);

        blockSize[0] = (int)(size[0] / fGridSize);
        blockSize[1] = (int)(size[1] / fGridSize);

        unit[0] = Mathf.Max(Mathf.Floor((blockSize[0] / size[0]) * 2.0f), 1.0f);
        unit[1] = Mathf.Max(Mathf.Floor((blockSize[1] / size[1]) * 2.0f), 1.0f);

        Assert.IsTrue(blockSize[0] >= 0 && blockSize[1] >= 0);

        if (blockSize[0] == 0 || blockSize[1] == 0)
        {
            blockSize[0] = 1;
            blockSize[1] = 1;

            size[0] = Mathf.Infinity;
            size[1] = Mathf.Infinity;
        }
    }

    public int GetGridIndexFromOrigin(Vector3 vOrigin)
    {
        int nGrids = blockSize[0] * blockSize[1];

        for(int y = 0; y < blockSize[1]; ++y)
        {
            for(int x = 0; x < blockSize[0]; ++x)
            {
                Vector2Int org = new Vector2Int();
                Vector2Int pos = new Vector2Int();
                int idx;

                for(int i = 0; i < 2; ++i)
                {
                    org[i] = (int)vOrigin[i] - ((int)min[i] -
                        (int)((float)blockSize[i] / unit[i]));

                    pos[i] = org[i] / (int)(size[i] / (float)blockSize[i]);
                }

                Mathf.Clamp(pos[0], 0, blockSize[0]-1);
                Mathf.Clamp(pos[1], 0, blockSize[1]-1);

                if(x != pos[0] || y != pos[1])
                {
                    continue;
                }

                idx = (pos[1] * blockSize[0]) + pos[0];

                if(idx >= nGrids)
                {
                    idx = nGrids-1;
                }

                return idx;
            }
        }

        return -1;
    }
}
