Generate Map: mbt_dm1.pk3
UnityEngine.Debug:Log (object)
GenerateMapCustomEditor:OnInspectorGUI () (at Assets/uQuake/Scripts/uQuake/GenerateMap.cs:17)
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

Could not find map 'maps/mbt_dm1.pk3' in any .pk3 file inside F:/Unity-Projects/Custom_Exo/CustomExo/Assets\baseq3
UnityEngine.Debug:LogError (object)
SharpBSP.BSPMap:.ctor (string,bool) (at Assets/uQuake/Scripts/uQuake/Types/BSPMap.cs:69)
GenerateMap:Run () (at Assets/uQuake/Scripts/uQuake/GenerateMap.cs:51)
GenerateMapCustomEditor:OnInspectorGUI () (at Assets/uQuake/Scripts/uQuake/GenerateMap.cs:18)
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

NullReferenceException: Object reference not set to an instance of an object
GenerateMap.Run () (at Assets/uQuake/Scripts/uQuake/GenerateMap.cs:56)
GenerateMapCustomEditor.OnInspectorGUI () (at Assets/uQuake/Scripts/uQuake/GenerateMap.cs:18)
UnityEditor.UIElements.InspectorElement+<>c__DisplayClass77_0.<CreateInspectorElementUsingIMGUI>b__0 () (at <916f8759030446de8f67b63248a8a6d1>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)


using System;
using System.IO;
using Ionic.Zip;
using UnityEngine;

namespace SharpBSP
{
    public class BSPMap
    {
        // This is the reader that seeks around the map and grabs the data.
        private BinaryReader BSP;

        // The header contains the directory of lumps
        public BSPHeader header;

        // These are the objects that hold data extracted from the lumps
        // Note: you only need to process lumps relevant to your tool/game.
        public EntityLump entityLump;
        public TextureLump textureLump;
        public VertexLump vertexLump;
        public FaceLump faceLump;
        public LightmapLump lightmapLump;

        /// <summary>
        /// Constructor to load BSP map either directly from disk or inside .pk3 archives.
        /// </summary>
        /// <param name="filename">The BSP filename relative to maps folder. Example: "q3dm17.bsp"</param>
        /// <param name="loadFromPK3">If true, try to load BSP from inside .pk3 zip archives in Assets/baseq3/ folder.</param>
        public BSPMap(string filename, bool loadFromPK3)
        {
            filename = "maps/" + filename; // Q3 maps inside pk3s are typically in maps/ folder

            if (loadFromPK3)
            {
                string baseq3Path = Path.Combine(Application.dataPath, "baseq3");
                bool mapFound = false;

                if (Directory.Exists(baseq3Path))
                {
                    foreach (var info in new DirectoryInfo(baseq3Path).GetFiles())
                    {
                        if (info.Name.EndsWith(".pk3", StringComparison.OrdinalIgnoreCase))
                        {
                            using (ZipFile pk3 = ZipFile.Read(Path.Combine(baseq3Path, info.Name)))
                            {
                                if (pk3.ContainsEntry(filename))
                                {
                                    var entry = pk3[filename];

                                    var ms = new MemoryStream();
                                    entry.Extract(ms);
                                    ms.Position = 0; // Important: rewind stream!

                                    BSP = new BinaryReader(ms);
                                    mapFound = true;
                                    break; // found map, stop searching further pk3s
                                }
                            }
                        }
                    }
                }
                else
                {
                    Debug.LogWarning($"Directory not found: {baseq3Path}");
                }

                if (!mapFound)
                {
                    Debug.LogError($"Could not find map '{filename}' in any .pk3 file inside {baseq3Path}");
                    return; // Abort constructor early to avoid errors
                }
            }
            else
            {
                // Load BSP file directly from disk
                string fullPath = Path.Combine(Application.dataPath, filename);
                if (!File.Exists(fullPath))
                {
                    Debug.LogError($"BSP map file not found: {fullPath}");
                    return; // Abort constructor
                }
                BSP = new BinaryReader(File.OpenRead(fullPath));
            }

            if (BSP == null)
            {
                Debug.LogError("Failed to initialize BSP BinaryReader.");
                return; // Abort constructor if reader is null
            }

            // Now safe to read header and lump data
            ReadHeader();
            ReadEntities();
            ReadTextures();
            ReadVertexes();
            ReadFaces();
            ReadMeshVerts();
            ReadLightmaps();

            // Load textures from all pk3 files (for example to resolve external texture references)
            string baseq3PathTexture = Path.Combine(Application.dataPath, "baseq3");
            if (Directory.Exists(baseq3PathTexture))
            {
                foreach (var info in new DirectoryInfo(baseq3PathTexture).GetFiles())
                {
                    if (info.Name.EndsWith(".pk3", StringComparison.OrdinalIgnoreCase))
                    {
                        textureLump?.PullInTextures(info.Name);
                    }
                }
            }
            else
            {
                Debug.LogWarning($"Directory not found: {baseq3PathTexture}");
            }

            BSP.Close();
        }

        private void ReadHeader()
        {
            header = new BSPHeader(BSP);
        }

        private void ReadEntities()
        {
            BSP.BaseStream.Seek(header.Directory[0].Offset, SeekOrigin.Begin);
            entityLump = new EntityLump(new string(BSP.ReadChars(header.Directory[0].Length)));
        }

        private void ReadTextures()
        {
            BSP.BaseStream.Seek(header.Directory[1].Offset, SeekOrigin.Begin);
            int textureCount = header.Directory[1].Length / 72; // Each texture entry is 72 bytes
            textureLump = new TextureLump(textureCount);
            for (int i = 0; i < textureCount; i++)
            {
                textureLump.Textures[i] = new Texture(
                    new string(BSP.ReadChars(64)), // texture name is 64 bytes
                    BSP.ReadInt32(),
                    BSP.ReadInt32());
            }
        }

        private void ReadVertexes()
        {
            BSP.BaseStream.Seek(header.Directory[10].Offset, SeekOrigin.Begin);
            int vertCount = header.Directory[10].Length / 44; // Each vertex is 44 bytes
            vertexLump = new VertexLump(vertCount);
            for (int i = 0; i < vertCount; i++)
            {
                vertexLump.Verts[i] = new Vertex(
                    new UnityEngine.Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()),
                    BSP.ReadSingle(),
                    BSP.ReadSingle(),
                    BSP.ReadSingle(),
                    BSP.ReadSingle(),
                    new UnityEngine.Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()),
                    BSP.ReadBytes(4));
            }
        }

        private void ReadFaces()
        {
            BSP.BaseStream.Seek(header.Directory[13].Offset, SeekOrigin.Begin);
            int faceCount = header.Directory[13].Length / 104; // Each face is 104 bytes
            faceLump = new FaceLump(faceCount);
            for (int i = 0; i < faceCount; i++)
            {
                faceLump.Faces[i] = new Face(
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    BSP.ReadInt32(),
                    new int[] { BSP.ReadInt32(), BSP.ReadInt32() },
                    new int[] { BSP.ReadInt32(), BSP.ReadInt32() },
                    new UnityEngine.Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()),
                    new UnityEngine.Vector3[] {
                        new UnityEngine.Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()),
                        new UnityEngine.Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle())
                    },
                    new UnityEngine.Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()),
                    new int[] { BSP.ReadInt32(), BSP.ReadInt32() }
                );
            }
        }

        private void ReadMeshVerts()
        {
            BSP.BaseStream.Seek(header.Directory[11].Offset, SeekOrigin.Begin);
            int meshvertCount = header.Directory[11].Length / 4; // Each meshvert is 4 bytes
            vertexLump.MeshVerts = new int[meshvertCount];
            for (int i = 0; i < meshvertCount; i++)
            {
                vertexLump.MeshVerts[i] = BSP.ReadInt32();
            }
        }

        private void ReadLightmaps()
        {
            BSP.BaseStream.Seek(header.Directory[14].Offset, SeekOrigin.Begin);
            int lightmapCount = header.Directory[14].Length / 49152; // Each lightmap is 49152 bytes
            lightmapLump = new LightmapLump(lightmapCount);
            for (int i = 0; i < lightmapCount; i++)
            {
                lightmapLump.Lightmaps[i] = LightmapLump.CreateLightmap(BSP.ReadBytes(49152));
            }
        }
    }
}
using UnityEngine;
using UnityEditor;
using System;
using SharpBSP;
using System.Collections;
using System.Collections.Generic;
using Ionic.Zip;
using System.IO;

// http://answers.unity3d.com/questions/126048/create-a-button-in-the-inspector.html#answer-360940
[CustomEditor(typeof(GenerateMap))]
class GenerateMapCustomEditor : Editor {
	public override void OnInspectorGUI() {
		DrawDefaultInspector();
		GenerateMap script = (GenerateMap)target;
		if(GUILayout.Button("Generate Map")) {
			Debug.Log ("Generate Map: " + script.mapName);
			script.Run();
		}
		if(GUILayout.Button("Clear Map")) {
			// http://forum.unity3d.com/threads/deleting-all-chidlren-of-an-object.92827/
			var children = new List<GameObject>();
			foreach (Transform child in script.gameObject.transform) children.Add(child.gameObject);
			children.ForEach(child => DestroyImmediate(child));
		}
	}
}

public class GenerateMap : MonoBehaviour
{
	public bool generateAtRuntime = true;
	public Material replacementTexture;
    public bool useRippedTextures;
    public bool renderBezPatches;
    public string mapName;
    public bool mapIsInsidePK3;
    public bool applyLightmaps;
    public int tessellations = 5;
    private int faceCount = 0;
    private BSPMap map;

    void Awake() {
		if (generateAtRuntime)
			Run();
	}

	public void Run() {
        // Create a new BSPmap, which is an object that
        // represents the map and all its data as a whole
        if (mapIsInsidePK3)
            map = new BSPMap(mapName, true);
        else
            map = new BSPMap("Assets/baseq3/maps/" + mapName, false);

        // Each face is its own gameobject
        foreach (Face face in map.faceLump.Faces)
        {
            if (face.type == 2)
            {
                if (renderBezPatches)
                {
                    GenerateBezObject(face);
                }
                faceCount++;
            } else if (face.type == 1)
            {
                GeneratePolygonObject(face);
                faceCount++;
            } else if (face.type == 3)
            {
                GeneratePolygonObject(face);
                faceCount++;
            } else
            {
                Debug.Log("Skipped Face " + faceCount.ToString() + " because it was not a polygon, mesh, or bez patch.");
                faceCount++;
            }
        }
        GC.Collect();
    }

    #region Object Generation
    // This makes gameobjects for every bez patch in a face
    // they are tessellated according to the "tessellations" field
    // in the editor
    void GenerateBezObject(Face face)
    {
        int numPatches = ((face.size [0] - 1) / 2) * ((face.size [1] - 1) / 2);

        for (int i = 0; i < numPatches; i++)
        {
            GameObject bezObject = new GameObject();
            bezObject.transform.parent = gameObject.transform;
            bezObject.name = "BSPface (bez) " + faceCount.ToString();
            bezObject.AddComponent<MeshFilter>().mesh = GenerateBezMesh(face, i);
            bezObject.AddComponent<MeshRenderer>();
            //bezObject.AddComponent<MeshCollider>();
            if (useRippedTextures)
                bezObject.GetComponent<Renderer>().material = FetchMaterial(face);
            else
                bezObject.GetComponent<Renderer>().material = replacementTexture;
            bezObject.isStatic = true;
        }
    }


    // This takes one face and generates a gameobject complete with
    // mesh, renderer, material with texture, and collider.
    void GeneratePolygonObject(Face face)
    {
        GameObject faceObject = new GameObject("BSPface " + faceCount.ToString());
        faceObject.transform.parent = gameObject.transform;
        // Our GeneratePolygonMesh will optimze and add the UVs for us
        faceObject.AddComponent<MeshFilter>().mesh = GeneratePolygonMesh(face);
        faceObject.AddComponent<MeshRenderer>();
        //faceObject.AddComponent<MeshCollider>();
        if (useRippedTextures)
            faceObject.GetComponent<Renderer>().material = FetchMaterial(face);
        else
            faceObject.GetComponent<Renderer>().material = replacementTexture;
        faceObject.isStatic = true;
    }

    #endregion

    #region Mesh Generation
    // This forms a mesh from a bez patch of your choice
    // from the face of your choice.
    // It's ready to render with tex coords and all.
    Mesh GenerateBezMesh(Face face, int patchNumber)
    {
        //Calculate how many patches there are using size[]
        //There are n_patchesX by n_patchesY patches in the grid, each of those
        //starts at a vert (i,j) in the overall grid
        //We don't actually need to know how many are on the Y length
        //but the forumla is here for historical/academic purposes
        int n_patchesX = ((face.size [0]) - 1) / 2;
        //int n_patchesY = ((face.size[1]) - 1) / 2;


        //Calculate what [n,m] patch we want by using an index
        //called patchNumber  Think of patchNumber as if you 
        //numbered the patches left to right, top to bottom on
        //the grid in a piece of paper.
        int pxStep = 0;
        int pyStep = 0;
        for (int i = 0; i < patchNumber; i++)
        {
            pxStep++;
            if (pxStep == n_patchesX)
            {
                pxStep = 0;
                pyStep++;
            }
        }

        //Create an array the size of the grid, which is given by
        //size[] on the face object.
        Vertex[,] vertGrid = new Vertex[face.size [0], face.size [1]];

        //Read the verts for this face into the grid, making sure
        //that the final shape of the grid matches the size[] of
        //the face.
        int gridXstep = 0;
        int gridYstep = 0;
        int vertStep = face.vertex;
        for (int i = 0; i < face.n_vertexes; i++)
        {
            vertGrid [gridXstep, gridYstep] = map.vertexLump.Verts [vertStep];
            vertStep++;
            gridXstep++;
            if (gridXstep == face.size [0])
            {
                gridXstep = 0;
                gridYstep++;
            }
        }

        //We now need to pluck out exactly nine vertexes to pass to our
        //teselate function, so lets calculate the starting vertex of the
        //3x3 grid of nine vertexes that will make up our patch.
        //we already know how many patches are in the grid, which we have
        //as n and m.  There are n by m patches.  Since this method will
        //create one gameobject at a time, we only need to be able to grab
        //one.  The starting vertex will be called vi,vj think of vi,vj as x,y
        //coords into the grid.
        int vi = 2 * pxStep;
        int vj = 2 * pyStep;
        //Now that we have those, we need to get the vert at [vi,vj] and then
        //the two verts at [vi+1,vj] and [vi+2,vj], and then [vi,vj+1], etc.
        //the ending vert will at [vi+2,vj+2]

        List<Vector3> bverts = new List<Vector3>();

        //read texture/lightmap coords while we're at it
        //they will be tessellated as well.
        List<Vector2> uvs = new List<Vector2>();
        List<Vector2> uv2s = new List<Vector2>();

        //Top row
        bverts.Add(vertGrid [vi, vj].position);
        bverts.Add(vertGrid [vi + 1, vj].position);
        bverts.Add(vertGrid [vi + 2, vj].position);

        uvs.Add(vertGrid [vi, vj].texcoord);
        uvs.Add(vertGrid [vi + 1, vj].texcoord);
        uvs.Add(vertGrid [vi + 2, vj].texcoord);

        uv2s.Add(vertGrid [vi, vj].lmcoord);
        uv2s.Add(vertGrid [vi + 1, vj].lmcoord);
        uv2s.Add(vertGrid [vi + 2, vj].lmcoord);

        //Middle row
        bverts.Add(vertGrid [vi, vj + 1].position);
        bverts.Add(vertGrid [vi + 1, vj + 1].position);
        bverts.Add(vertGrid [vi + 2, vj + 1].position);

        uvs.Add(vertGrid [vi, vj + 1].texcoord);
        uvs.Add(vertGrid [vi + 1, vj + 1].texcoord);
        uvs.Add(vertGrid [vi + 2, vj + 1].texcoord);

        uv2s.Add(vertGrid [vi, vj + 1].lmcoord);
        uv2s.Add(vertGrid [vi + 1, vj + 1].lmcoord);
        uv2s.Add(vertGrid [vi + 2, vj + 1].lmcoord);

        //Bottom row
        bverts.Add(vertGrid [vi, vj + 2].position);
        bverts.Add(vertGrid [vi + 1, vj + 2].position);
        bverts.Add(vertGrid [vi + 2, vj + 2].position);

        uvs.Add(vertGrid [vi, vj + 2].texcoord);
        uvs.Add(vertGrid [vi + 1, vj + 2].texcoord);
        uvs.Add(vertGrid [vi + 2, vj + 2].texcoord);

        uv2s.Add(vertGrid [vi, vj + 2].lmcoord);
        uv2s.Add(vertGrid [vi + 1, vj + 2].lmcoord);
        uv2s.Add(vertGrid [vi + 2, vj + 2].lmcoord);

        //Now that we have our control grid, it's business as usual
        Mesh bezMesh = new Mesh();
        bezMesh.name = "BSPfacemesh (bez)";
        BezierMesh bezPatch = new BezierMesh(tessellations, bverts, uvs, uv2s);
        return bezPatch.Mesh;
    }

    // Generate a mesh for a simple polygon/mesh face
    // It's ready to render with tex coords and all.
    Mesh GeneratePolygonMesh(Face face)
    {
        Mesh worldFace = new Mesh();
        worldFace.name = "BSPface (poly/mesh)";

        // Rip verts, uvs, and normals
        // I have ripping normals commented because it looks
        // like it's better to just let Unity recalculate them for us.
        List<Vector3> verts = new List<Vector3>();
        List<Vector2> uvs = new List<Vector2>();
        List<Vector2> uv2s = new List<Vector2>();
        int vstep = face.vertex;
        for (int i = 0; i < face.n_vertexes; i++)
        {
            verts.Add(map.vertexLump.Verts [vstep].position);
            uvs.Add(map.vertexLump.Verts [vstep].texcoord);
            uv2s.Add(map.vertexLump.Verts [vstep].lmcoord);
            vstep++;
        }

        // add the verts, uvs, and normals we ripped to the gameobjects mesh filter
        worldFace.vertices = verts.ToArray();

        // Add the texture co-ords (or UVs) to the face/mesh
        worldFace.uv = uvs.ToArray();
        worldFace.uv2 = uv2s.ToArray();

        // Rip meshverts / triangles
        List<int> mverts = new List<int>();
        int mstep = face.meshvert;
        for (int i = 0; i < face.n_meshverts; i++)
        {
            mverts.Add(map.vertexLump.MeshVerts [mstep]);
            mstep++;
        }

        // add the meshverts to the object being built
        worldFace.triangles = mverts.ToArray();

        // Let Unity do some heavy lifting for us
        worldFace.RecalculateBounds();
        worldFace.RecalculateNormals();
        worldFace.Optimize();

        return worldFace;
    }
    #endregion

    #region Material Generation
    // This returns a material with the correct texture for a given face
    Material FetchMaterial(Face face)
    {
        string texName = map.textureLump.Textures [face.texture].Name;

        // Load the primary texture for the face from the texture lump
        // The texture lump itself will have already looked over all
        // available .pk3 files and compiled a dictionary of textures for us.
        Texture2D tex;

        if (map.textureLump.ContainsTexture(texName))
        {
            tex = map.textureLump.GetTexture(texName);
        } else
        {
            return replacementTexture;
        }

        // Lightmapping is on, so calc the lightmaps
        if (face.lm_index >= 0 && applyLightmaps)
        {
            // Pick a shader that supports lightmaps
            Material bspMaterial = new Material(Shader.Find("Legacy Shaders/Lightmapped/Diffuse"));

            // LM experiment
            Texture2D lmap = map.lightmapLump.Lightmaps [face.lm_index];
            lmap.Compress(true);
            lmap.Apply();

            // Put the textures in the shader.
            bspMaterial.mainTexture = tex;
            bspMaterial.SetTexture("_LightMap", lmap);

            return bspMaterial;
        } else
        { // Lightmapping is off, so don't.
            Material bspMaterial = new Material(Shader.Find("Diffuse"));
            bspMaterial.mainTexture = tex;
            return bspMaterial;
        }
        
    }
	#endregion
}