├── .gitattributes ├── .gitignore ├── README.md └── uQuake ├── Materials ├── 2448.jpg └── Bricks.mat ├── Plugins ├── I18N.CJK.dll ├── I18N.MidEast.dll ├── I18N.Other.dll ├── I18N.Rare.dll ├── I18N.West.dll ├── I18N.dll └── Ionic.Zip.dll ├── Scripts └── uQuake │ ├── GenerateMap.cs │ ├── Lumps │ ├── EntityLump.cs │ ├── FaceLump.cs │ ├── LightmapLump.cs │ ├── TextureLump.cs │ └── VertexLump.cs │ ├── MouseCam.cs │ └── Types │ ├── BSPDirectoryEntry.cs │ ├── BSPHeader.cs │ ├── BSPMap.cs │ ├── BezierMesh.cs │ ├── Face.cs │ ├── TGALoader.cs │ ├── Texture.cs │ └── Vertex.cs └── baseq3 └── place_pk3_files_here /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uQuake 3 2 | 3 | Load Quake 3 maps into Unity3D 4 | 5 | ### What is this? 6 | 7 | It takes a normal Quake 3 .bsp and loads it into a Unity scene. 8 | 9 | ### What parts of the map are recreated? 10 | 11 | - Face geometry is 100% working. 12 | - Polygons 13 | - Meshes 14 | - Bezier patches 15 | - Textures pulled from the .pk3 and applied. 16 | - Baked lightmaps are pulled from the map and applied. 17 | 18 | ### What parts are not recreated? 19 | 20 | - Entities are not used in any way. 21 | - Billboards, like torch flames, aren't rendered at all. 22 | - Liquids, skies, and any other texture that is produced by a shader in Quake 3. 23 | - Shader textures will be replaced with non-shader versions where possible. 24 | - Animated textures don't animate, or are missing. 25 | - Textures with transparency (banners, flags, etc) don't have it. 26 | - Well actually they do, but currently I'm using a shader for the lightmaps that doesn't support transparency. 27 | 28 | ### How do I use it? 29 | 30 | Create an empty scene and place an empty at the origin with the "GenerateMap.cs" script on it. Create a folder called "baseq3" inside your Assets folder. Place the .pk3 files from a retail/Steam/demo Quake 3 installation into that folder. You can add .pk3 files containing custom maps if you like, as well. On the empty you placed in the scene, put a material to use on surfaces that can't be textured correctly into the "Replacement Texture" field. This will be used on surfaces that have missing textures for any reason. Now type in the map name you would like to load, including the .bsp file extension. "q3dm1.bsp" is the first map from Quake 3, if your memory is failing you. Set the other options as you like. Make sure "Map is Inside PK3" is checked if you're loading the maps that came with Quake 3, or a custom map that is inside of a .pk3 file. 31 | 32 | I recommend placing a camera in the scene and attaching the included "MouseCam.cs" script, so you can fly around the rendered level easily. 33 | 34 | You can load a .bsp from outside of the .pk3 files as well if you like. Just make a "maps" folder inside the "baseq3" folder and place the loose maps in there, just like real Quake 3. In this case make sure "Map is Inside PK3" is unchecked. 35 | 36 | Let it rip. It shouldn't take more than a few seconds on smaller maps, and maybe ten or so seconds for larger maps. Performance is decent on modern video cards. Anything as powerful as an Intel 4000 should have no issues rendering even big maps. 37 | 38 | ### Is this done? 39 | 40 | No, I'm still working away at it. I recently picked this project up again after negelcting it for a few months. In those few months my C# skill increased significantly. I'm in the process of cleaning up some bad design and fixing dumb mistakes as I find them. Once I think the project is ready for more mass consumption I'll create better documentation and likely provide a .dll to use, which will make adding Quake 3 maps support to your projects and tools easy. The source will always be available here. 41 | 42 | ### What games does this support? 43 | 44 | I have only tested it extensivly with Quake 3: Arena maps. In theory it will work with any game that uses the Quake 3 map and archive format. This doesn't meany any game that uses the Quake 3 engine will work, only ones that use the Quake 3 map and archive format exactly. 45 | 46 | ### Will you had Quake 1/2/4 support? Half-Life 1/2? 47 | 48 | Short answer: no. 49 | 50 | Long answer: Probably not. I have a sister project to this one that loads Quake 1 maps, but Quake 1 maps are very different than Quake 3 maps. Quake 2 is somewhere in the middle. Half-Life is very very close to Quake 1. Half-Life 2 is more complex than the others, but would be doable I think. It would require its own project, though. I'm not sure it would work very well. Half-Life 2's levels require a lot of props and image effects for their final look and feel, and that would be hard to recreate in Unity with just the data in the map. 51 | 52 | ## Can I fork/use this? What license is this? 53 | 54 | By all means. You don't need to give me any credit or anything. Have fun. This project is in the public domain. 55 | -------------------------------------------------------------------------------- /uQuake/Materials/2448.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Materials/2448.jpg -------------------------------------------------------------------------------- /uQuake/Materials/Bricks.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Materials/Bricks.mat -------------------------------------------------------------------------------- /uQuake/Plugins/I18N.CJK.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/I18N.CJK.dll -------------------------------------------------------------------------------- /uQuake/Plugins/I18N.MidEast.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/I18N.MidEast.dll -------------------------------------------------------------------------------- /uQuake/Plugins/I18N.Other.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/I18N.Other.dll -------------------------------------------------------------------------------- /uQuake/Plugins/I18N.Rare.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/I18N.Rare.dll -------------------------------------------------------------------------------- /uQuake/Plugins/I18N.West.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/I18N.West.dll -------------------------------------------------------------------------------- /uQuake/Plugins/I18N.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/I18N.dll -------------------------------------------------------------------------------- /uQuake/Plugins/Ionic.Zip.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/Plugins/Ionic.Zip.dll -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/GenerateMap.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using System; 4 | using SharpBSP; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using Ionic.Zip; 8 | using System.IO; 9 | 10 | // http://answers.unity3d.com/questions/126048/create-a-button-in-the-inspector.html#answer-360940 11 | [CustomEditor(typeof(GenerateMap))] 12 | class GenerateMapCustomEditor : Editor { 13 | public override void OnInspectorGUI() { 14 | DrawDefaultInspector(); 15 | GenerateMap script = (GenerateMap)target; 16 | if(GUILayout.Button("Generate Map")) { 17 | Debug.Log ("Generate Map: " + script.mapName); 18 | script.Run(); 19 | } 20 | if(GUILayout.Button("Clear Map")) { 21 | // http://forum.unity3d.com/threads/deleting-all-chidlren-of-an-object.92827/ 22 | var children = new List(); 23 | foreach (Transform child in script.gameObject.transform) children.Add(child.gameObject); 24 | children.ForEach(child => DestroyImmediate(child)); 25 | } 26 | } 27 | } 28 | 29 | public class GenerateMap : MonoBehaviour 30 | { 31 | public bool generateAtRuntime = true; 32 | public Material replacementTexture; 33 | public bool useRippedTextures; 34 | public bool renderBezPatches; 35 | public string mapName; 36 | public bool mapIsInsidePK3; 37 | public bool applyLightmaps; 38 | public int tessellations = 5; 39 | private int faceCount = 0; 40 | private BSPMap map; 41 | 42 | void Awake() { 43 | if (generateAtRuntime) 44 | Run(); 45 | } 46 | 47 | public void Run() { 48 | // Create a new BSPmap, which is an object that 49 | // represents the map and all its data as a whole 50 | if (mapIsInsidePK3) 51 | map = new BSPMap(mapName, true); 52 | else 53 | map = new BSPMap("Assets/baseq3/maps/" + mapName, false); 54 | 55 | // Each face is its own gameobject 56 | foreach (Face face in map.faceLump.Faces) 57 | { 58 | if (face.type == 2) 59 | { 60 | if (renderBezPatches) 61 | { 62 | GenerateBezObject(face); 63 | } 64 | faceCount++; 65 | } else if (face.type == 1) 66 | { 67 | GeneratePolygonObject(face); 68 | faceCount++; 69 | } else if (face.type == 3) 70 | { 71 | GeneratePolygonObject(face); 72 | faceCount++; 73 | } else 74 | { 75 | Debug.Log("Skipped Face " + faceCount.ToString() + " because it was not a polygon, mesh, or bez patch."); 76 | faceCount++; 77 | } 78 | } 79 | GC.Collect(); 80 | } 81 | 82 | #region Object Generation 83 | // This makes gameobjects for every bez patch in a face 84 | // they are tessellated according to the "tessellations" field 85 | // in the editor 86 | void GenerateBezObject(Face face) 87 | { 88 | int numPatches = ((face.size [0] - 1) / 2) * ((face.size [1] - 1) / 2); 89 | 90 | for (int i = 0; i < numPatches; i++) 91 | { 92 | GameObject bezObject = new GameObject(); 93 | bezObject.transform.parent = gameObject.transform; 94 | bezObject.name = "BSPface (bez) " + faceCount.ToString(); 95 | bezObject.AddComponent().mesh = GenerateBezMesh(face, i); 96 | bezObject.AddComponent(); 97 | //bezObject.AddComponent(); 98 | if (useRippedTextures) 99 | bezObject.renderer.material = FetchMaterial(face); 100 | else 101 | bezObject.renderer.material = replacementTexture; 102 | bezObject.isStatic = true; 103 | } 104 | } 105 | 106 | 107 | // This takes one face and generates a gameobject complete with 108 | // mesh, renderer, material with texture, and collider. 109 | void GeneratePolygonObject(Face face) 110 | { 111 | GameObject faceObject = new GameObject("BSPface " + faceCount.ToString()); 112 | faceObject.transform.parent = gameObject.transform; 113 | // Our GeneratePolygonMesh will optimze and add the UVs for us 114 | faceObject.AddComponent().mesh = GeneratePolygonMesh(face); 115 | faceObject.AddComponent(); 116 | //faceObject.AddComponent(); 117 | if (useRippedTextures) 118 | faceObject.renderer.material = FetchMaterial(face); 119 | else 120 | faceObject.renderer.material = replacementTexture; 121 | faceObject.isStatic = true; 122 | } 123 | 124 | #endregion 125 | 126 | #region Mesh Generation 127 | // This forms a mesh from a bez patch of your choice 128 | // from the face of your choice. 129 | // It's ready to render with tex coords and all. 130 | Mesh GenerateBezMesh(Face face, int patchNumber) 131 | { 132 | //Calculate how many patches there are using size[] 133 | //There are n_patchesX by n_patchesY patches in the grid, each of those 134 | //starts at a vert (i,j) in the overall grid 135 | //We don't actually need to know how many are on the Y length 136 | //but the forumla is here for historical/academic purposes 137 | int n_patchesX = ((face.size [0]) - 1) / 2; 138 | //int n_patchesY = ((face.size[1]) - 1) / 2; 139 | 140 | 141 | //Calculate what [n,m] patch we want by using an index 142 | //called patchNumber Think of patchNumber as if you 143 | //numbered the patches left to right, top to bottom on 144 | //the grid in a piece of paper. 145 | int pxStep = 0; 146 | int pyStep = 0; 147 | for (int i = 0; i < patchNumber; i++) 148 | { 149 | pxStep++; 150 | if (pxStep == n_patchesX) 151 | { 152 | pxStep = 0; 153 | pyStep++; 154 | } 155 | } 156 | 157 | //Create an array the size of the grid, which is given by 158 | //size[] on the face object. 159 | Vertex[,] vertGrid = new Vertex[face.size [0], face.size [1]]; 160 | 161 | //Read the verts for this face into the grid, making sure 162 | //that the final shape of the grid matches the size[] of 163 | //the face. 164 | int gridXstep = 0; 165 | int gridYstep = 0; 166 | int vertStep = face.vertex; 167 | for (int i = 0; i < face.n_vertexes; i++) 168 | { 169 | vertGrid [gridXstep, gridYstep] = map.vertexLump.Verts [vertStep]; 170 | vertStep++; 171 | gridXstep++; 172 | if (gridXstep == face.size [0]) 173 | { 174 | gridXstep = 0; 175 | gridYstep++; 176 | } 177 | } 178 | 179 | //We now need to pluck out exactly nine vertexes to pass to our 180 | //teselate function, so lets calculate the starting vertex of the 181 | //3x3 grid of nine vertexes that will make up our patch. 182 | //we already know how many patches are in the grid, which we have 183 | //as n and m. There are n by m patches. Since this method will 184 | //create one gameobject at a time, we only need to be able to grab 185 | //one. The starting vertex will be called vi,vj think of vi,vj as x,y 186 | //coords into the grid. 187 | int vi = 2 * pxStep; 188 | int vj = 2 * pyStep; 189 | //Now that we have those, we need to get the vert at [vi,vj] and then 190 | //the two verts at [vi+1,vj] and [vi+2,vj], and then [vi,vj+1], etc. 191 | //the ending vert will at [vi+2,vj+2] 192 | 193 | List bverts = new List(); 194 | 195 | //read texture/lightmap coords while we're at it 196 | //they will be tessellated as well. 197 | List uvs = new List(); 198 | List uv2s = new List(); 199 | 200 | //Top row 201 | bverts.Add(vertGrid [vi, vj].position); 202 | bverts.Add(vertGrid [vi + 1, vj].position); 203 | bverts.Add(vertGrid [vi + 2, vj].position); 204 | 205 | uvs.Add(vertGrid [vi, vj].texcoord); 206 | uvs.Add(vertGrid [vi + 1, vj].texcoord); 207 | uvs.Add(vertGrid [vi + 2, vj].texcoord); 208 | 209 | uv2s.Add(vertGrid [vi, vj].lmcoord); 210 | uv2s.Add(vertGrid [vi + 1, vj].lmcoord); 211 | uv2s.Add(vertGrid [vi + 2, vj].lmcoord); 212 | 213 | //Middle row 214 | bverts.Add(vertGrid [vi, vj + 1].position); 215 | bverts.Add(vertGrid [vi + 1, vj + 1].position); 216 | bverts.Add(vertGrid [vi + 2, vj + 1].position); 217 | 218 | uvs.Add(vertGrid [vi, vj + 1].texcoord); 219 | uvs.Add(vertGrid [vi + 1, vj + 1].texcoord); 220 | uvs.Add(vertGrid [vi + 2, vj + 1].texcoord); 221 | 222 | uv2s.Add(vertGrid [vi, vj + 1].lmcoord); 223 | uv2s.Add(vertGrid [vi + 1, vj + 1].lmcoord); 224 | uv2s.Add(vertGrid [vi + 2, vj + 1].lmcoord); 225 | 226 | //Bottom row 227 | bverts.Add(vertGrid [vi, vj + 2].position); 228 | bverts.Add(vertGrid [vi + 1, vj + 2].position); 229 | bverts.Add(vertGrid [vi + 2, vj + 2].position); 230 | 231 | uvs.Add(vertGrid [vi, vj + 2].texcoord); 232 | uvs.Add(vertGrid [vi + 1, vj + 2].texcoord); 233 | uvs.Add(vertGrid [vi + 2, vj + 2].texcoord); 234 | 235 | uv2s.Add(vertGrid [vi, vj + 2].lmcoord); 236 | uv2s.Add(vertGrid [vi + 1, vj + 2].lmcoord); 237 | uv2s.Add(vertGrid [vi + 2, vj + 2].lmcoord); 238 | 239 | //Now that we have our control grid, it's business as usual 240 | Mesh bezMesh = new Mesh(); 241 | bezMesh.name = "BSPfacemesh (bez)"; 242 | BezierMesh bezPatch = new BezierMesh(tessellations, bverts, uvs, uv2s); 243 | return bezPatch.Mesh; 244 | } 245 | 246 | // Generate a mesh for a simple polygon/mesh face 247 | // It's ready to render with tex coords and all. 248 | Mesh GeneratePolygonMesh(Face face) 249 | { 250 | Mesh worldFace = new Mesh(); 251 | worldFace.name = "BSPface (poly/mesh)"; 252 | 253 | // Rip verts, uvs, and normals 254 | // I have ripping normals commented because it looks 255 | // like it's better to just let Unity recalculate them for us. 256 | List verts = new List(); 257 | List uvs = new List(); 258 | List uv2s = new List(); 259 | int vstep = face.vertex; 260 | for (int i = 0; i < face.n_vertexes; i++) 261 | { 262 | verts.Add(map.vertexLump.Verts [vstep].position); 263 | uvs.Add(map.vertexLump.Verts [vstep].texcoord); 264 | uv2s.Add(map.vertexLump.Verts [vstep].lmcoord); 265 | vstep++; 266 | } 267 | 268 | // add the verts, uvs, and normals we ripped to the gameobjects mesh filter 269 | worldFace.vertices = verts.ToArray(); 270 | 271 | // Add the texture co-ords (or UVs) to the face/mesh 272 | worldFace.uv = uvs.ToArray(); 273 | worldFace.uv2 = uv2s.ToArray(); 274 | 275 | // Rip meshverts / triangles 276 | List mverts = new List(); 277 | int mstep = face.meshvert; 278 | for (int i = 0; i < face.n_meshverts; i++) 279 | { 280 | mverts.Add(map.vertexLump.MeshVerts [mstep]); 281 | mstep++; 282 | } 283 | 284 | // add the meshverts to the object being built 285 | worldFace.triangles = mverts.ToArray(); 286 | 287 | // Let Unity do some heavy lifting for us 288 | worldFace.RecalculateBounds(); 289 | worldFace.RecalculateNormals(); 290 | worldFace.Optimize(); 291 | 292 | return worldFace; 293 | } 294 | #endregion 295 | 296 | #region Material Generation 297 | // This returns a material with the correct texture for a given face 298 | Material FetchMaterial(Face face) 299 | { 300 | string texName = map.textureLump.Textures [face.texture].Name; 301 | 302 | // Load the primary texture for the face from the texture lump 303 | // The texture lump itself will have already looked over all 304 | // available .pk3 files and compiled a dictionary of textures for us. 305 | Texture2D tex; 306 | 307 | if (map.textureLump.ContainsTexture(texName)) 308 | { 309 | tex = map.textureLump.GetTexture(texName); 310 | } else 311 | { 312 | return replacementTexture; 313 | } 314 | 315 | // Lightmapping is on, so calc the lightmaps 316 | if (face.lm_index >= 0 && applyLightmaps) 317 | { 318 | // Pick a shader that supports lightmaps 319 | Material bspMaterial = new Material(Shader.Find("Legacy Shaders/Lightmapped/Diffuse")); 320 | 321 | // LM experiment 322 | Texture2D lmap = map.lightmapLump.Lightmaps [face.lm_index]; 323 | lmap.Compress(true); 324 | lmap.Apply(); 325 | 326 | // Put the textures in the shader. 327 | bspMaterial.mainTexture = tex; 328 | bspMaterial.SetTexture("_LightMap", lmap); 329 | 330 | return bspMaterial; 331 | } else 332 | { // Lightmapping is off, so don't. 333 | Material bspMaterial = new Material(Shader.Find("Diffuse")); 334 | bspMaterial.mainTexture = tex; 335 | return bspMaterial; 336 | } 337 | 338 | } 339 | #endregion 340 | } 341 | 342 | 343 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Lumps/EntityLump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace SharpBSP 5 | { 6 | public class EntityLump 7 | { 8 | public string EntityString 9 | { 10 | get; 11 | private set; 12 | } 13 | 14 | 15 | public EntityLump(string lump) 16 | { 17 | EntityString = lump; 18 | } 19 | 20 | public override string ToString() 21 | { 22 | return EntityString; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Lumps/FaceLump.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace SharpBSP 5 | { 6 | public class FaceLump 7 | { 8 | public Face[] Faces { get; set; } 9 | 10 | public FaceLump(int faceCount) 11 | { 12 | Faces = new Face[faceCount]; 13 | } 14 | 15 | public string PrintInfo() 16 | { 17 | StringBuilder blob = new StringBuilder(); 18 | int count = 0; 19 | foreach (Face face in Faces) 20 | { 21 | blob.AppendLine("Face " + count.ToString() + "\t Tex: " + face.texture.ToString() + "\tType: " + face.type.ToString() + "\tVertIndex: " + face.vertex.ToString() + "\tNumVerts: " + face.n_vertexes.ToString() + "\tMeshVertIndex: " + face.meshvert.ToString() + "\tMeshVerts: " + face.n_meshverts + "\r\n"); 22 | count++; 23 | } 24 | return blob.ToString(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Lumps/LightmapLump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace SharpBSP 8 | { 9 | public class LightmapLump 10 | { 11 | public Texture2D[] Lightmaps { get; set; } 12 | 13 | public LightmapLump(int lightmapCount) 14 | { 15 | Lightmaps = new Texture2D[lightmapCount]; 16 | } 17 | 18 | private static byte CalcLight(byte color) 19 | { 20 | int icolor = (int)color; 21 | //icolor += 200; 22 | 23 | if (icolor > 255) 24 | { 25 | icolor = 255; 26 | } 27 | 28 | return (byte)icolor; 29 | } 30 | 31 | public static Texture2D CreateLightmap(byte[] rgb) 32 | { 33 | Texture2D tex = new Texture2D(128, 128, TextureFormat.RGBA32, false); 34 | Color32[] colors = new Color32[128 * 128]; 35 | int j = 0; 36 | for (int i = 0; i < 128 * 128; i++) 37 | { 38 | colors [i] = new Color32(CalcLight(rgb [j++]), CalcLight(rgb [j++]), CalcLight(rgb [j++]), (byte)1f); 39 | } 40 | tex.SetPixels32(colors); 41 | tex.Apply(); 42 | return tex; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Lumps/TextureLump.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using Ionic.Zip; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace SharpBSP 9 | { 10 | public class TextureLump 11 | { 12 | public Texture[] Textures{ get; set; } 13 | private Dictionary readyTextures = new Dictionary(); 14 | 15 | public TextureLump(int textureCount) 16 | { 17 | Textures = new Texture[textureCount]; 18 | } 19 | 20 | public int TextureCount 21 | { 22 | get 23 | { 24 | return Textures.Length; 25 | } 26 | } 27 | 28 | public bool ContainsTexture(string textureName) 29 | { 30 | foreach (KeyValuePair tex in readyTextures) 31 | { 32 | if (tex.Key == textureName) 33 | return true; 34 | } 35 | return false; 36 | } 37 | 38 | public Texture2D GetTexture(string textureName) 39 | { 40 | return readyTextures [textureName]; 41 | } 42 | 43 | public void PullInTextures(string pakName) 44 | { 45 | using (ZipFile pak = ZipFile.Read("Assets/baseq3/"+pakName)) 46 | { 47 | LoadJPGTextures(pak); 48 | LoadTGATextures(pak); 49 | } 50 | } 51 | 52 | private void LoadJPGTextures(ZipFile pk3) 53 | { 54 | foreach (Texture tex in Textures) 55 | { 56 | // The size of the new Texture2D object doesn't matter. It will be replaced (including its size) with the data from the .jpg texture that's getting pulled from the pk3 file. 57 | if (pk3.ContainsEntry(tex.Name + ".jpg")) 58 | { 59 | Texture2D readyTex = new Texture2D(4, 4); 60 | var entry = pk3 [tex.Name + ".jpg"]; 61 | using (var stream = entry.OpenReader()) 62 | { 63 | var ms = new MemoryStream(); 64 | entry.Extract(ms); 65 | readyTex.LoadImage(ms.GetBuffer()); 66 | } 67 | 68 | readyTex.name = tex.Name; 69 | readyTex.filterMode = FilterMode.Trilinear; 70 | readyTex.Compress(true); 71 | 72 | if (readyTextures.ContainsKey(tex.Name)) 73 | { 74 | Debug.Log("Updating texture with name " + tex.Name); 75 | readyTextures [tex.Name] = readyTex; 76 | } else 77 | readyTextures.Add(tex.Name, readyTex); 78 | } 79 | } 80 | } 81 | 82 | private void LoadTGATextures(ZipFile pk3) 83 | { 84 | foreach (Texture tex in Textures) 85 | { 86 | // The size of the new Texture2D object doesn't matter. It will be replaced (including its size) with the data from the texture that's getting pulled from the pk3 file. 87 | if (pk3.ContainsEntry(tex.Name + ".tga")) 88 | { 89 | Texture2D readyTex = new Texture2D(4, 4); 90 | var entry = pk3 [tex.Name + ".tga"]; 91 | using (var stream = entry.OpenReader()) 92 | { 93 | var ms = new MemoryStream(); 94 | entry.Extract(ms); 95 | readyTex = TGALoader.LoadTGA(ms); 96 | } 97 | 98 | readyTex.name = tex.Name; 99 | readyTex.filterMode = FilterMode.Trilinear; 100 | readyTex.Compress(true); 101 | 102 | if (readyTextures.ContainsKey(tex.Name)) 103 | { 104 | Debug.Log("Updating texture with name " + tex.Name + ".tga"); 105 | readyTextures [tex.Name] = readyTex; 106 | } else 107 | readyTextures.Add(tex.Name, readyTex); 108 | } 109 | } 110 | } 111 | 112 | 113 | public string PrintInfo() 114 | { 115 | StringBuilder blob = new StringBuilder (); 116 | int count = 0; 117 | foreach (Texture tex in Textures) 118 | { 119 | blob.Append("Texture " + count++ + " Name: " + tex.Name.Trim() + "\tFlags: " + tex.Flags.ToString() + "\tContents: " + tex.Contents.ToString() + "\r\n"); 120 | } 121 | return blob.ToString(); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Lumps/VertexLump.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace SharpBSP 4 | { 5 | public class VertexLump 6 | { 7 | public Vertex[] Verts{ get; set; } 8 | public int[] MeshVerts{ get; set; } 9 | 10 | public VertexLump(int VertexCount) 11 | { 12 | Verts = new Vertex[VertexCount]; 13 | } 14 | 15 | public override string ToString() 16 | { 17 | StringBuilder blob = new StringBuilder (); 18 | int count = 0; 19 | foreach (Vertex vert in Verts) 20 | { 21 | blob.Append("Vertex " + count.ToString() + " Pos: " + vert.position.ToString() + " Normal: " + vert.normal.ToString() + "\r\n"); 22 | count++; 23 | } 24 | return blob.ToString(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/MouseCam.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | // This is a modified mouselook script that can be thrown on a camera to be able to fly around the map. 5 | // I don't remember if I made it or if someone else did. 6 | 7 | 8 | /// MouseLook rotates the transform based on the mouse delta. 9 | /// Minimum and Maximum values can be used to constrain the possible rotation 10 | 11 | /// To make an FPS style character: 12 | /// - Create a capsule. 13 | /// - Add the MouseLook script to the capsule. 14 | /// -> Set the mouse look to use LookX. (You want to only turn character but not tilt it) 15 | /// - Add FPSInputController script to the capsule 16 | /// -> A CharacterMotor and a CharacterController component will be automatically added. 17 | 18 | /// - Create a camera. Make the camera a child of the capsule. Reset it's transform. 19 | /// - Add a MouseLook script to the camera. 20 | /// -> Set the mouse look to use LookY. (You want the camera to tilt up and down like a head. The character already turns.) 21 | [AddComponentMenu("Camera-Control/Mouse Look")] 22 | public class MouseCam : MonoBehaviour { 23 | 24 | public enum RotationAxes { MouseXAndY = 0, MouseX = 1, MouseY = 2 } 25 | public RotationAxes axes = RotationAxes.MouseXAndY; 26 | public float sensitivityX = 15F; 27 | public float sensitivityY = 15F; 28 | 29 | public float minimumX = -360F; 30 | public float maximumX = 360F; 31 | 32 | public float minimumY = -90F; 33 | public float maximumY = 90F; 34 | 35 | float rotationY = 0F; 36 | 37 | void Update () 38 | { 39 | if (Input.GetButton("Fire1")) 40 | transform.position += transform.forward * Time.deltaTime * 25; 41 | if (Input.GetButton("Fire2")) 42 | transform.position -= transform.forward * Time.deltaTime * 25; 43 | 44 | if (axes == RotationAxes.MouseXAndY) 45 | { 46 | float rotationX = transform.localEulerAngles.y + Input.GetAxis("Mouse X") * sensitivityX; 47 | 48 | rotationY += Input.GetAxis("Mouse Y") * sensitivityY; 49 | rotationY = Mathf.Clamp (rotationY, minimumY, maximumY); 50 | 51 | transform.localEulerAngles = new Vector3(-rotationY, rotationX, 0); 52 | } 53 | else if (axes == RotationAxes.MouseX) 54 | { 55 | transform.Rotate(0, Input.GetAxis("Mouse X") * sensitivityX, 0); 56 | } 57 | else 58 | { 59 | rotationY += Input.GetAxis("Mouse Y") * sensitivityY; 60 | rotationY = Mathf.Clamp (rotationY, minimumY, maximumY); 61 | 62 | transform.localEulerAngles = new Vector3(-rotationY, transform.localEulerAngles.y, 0); 63 | } 64 | } 65 | 66 | void Start () 67 | { 68 | // Make the rigid body not change rotation 69 | if (rigidbody) 70 | rigidbody.freezeRotation = true; 71 | } 72 | } -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/BSPDirectoryEntry.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SharpBSP 3 | { 4 | public class BSPDirectoryEntry 5 | { 6 | public int Offset 7 | { 8 | get; 9 | private set; 10 | } 11 | 12 | public int Length 13 | { 14 | get; 15 | private set; 16 | } 17 | 18 | public string Name 19 | { 20 | get; 21 | set; 22 | } 23 | 24 | 25 | public BSPDirectoryEntry(int offset, int length) 26 | { 27 | Offset = offset; 28 | Length = length; 29 | } 30 | 31 | public bool Validate() 32 | { 33 | if (Length % 4 == 0) 34 | { 35 | return true; 36 | } else 37 | { 38 | return false; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/BSPHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace SharpBSP 5 | { 6 | public class BSPHeader 7 | { 8 | public BSPDirectoryEntry[] Directory { 9 | get; 10 | set; 11 | } 12 | 13 | public string Magic { 14 | get; 15 | private set; 16 | } 17 | 18 | public uint Version { 19 | get; 20 | private set; 21 | } 22 | 23 | private BinaryReader BSP; 24 | 25 | private const int LumpCount = 17; 26 | 27 | public BSPHeader(BinaryReader BSP) 28 | { 29 | this.BSP = BSP; 30 | 31 | ReadMagic(); 32 | ReadVersion(); 33 | ReadLumps(); 34 | } 35 | 36 | public string PrintInfo() 37 | { 38 | string blob = "\r\n=== BSP Header =====\r\n"; 39 | blob += ("Magic Number: " + Magic + "\r\n"); 40 | blob += ("BSP Version: " + Version + "\r\n"); 41 | blob += ("Header Directory:\r\n"); 42 | int count = 0; 43 | foreach (BSPDirectoryEntry entry in Directory) 44 | { 45 | blob += ("Lump " + count + ": " + entry.Name + " Offset: " + entry.Offset + " Length: " + entry.Length + "\r\n"); 46 | count++; 47 | } 48 | return blob; 49 | } 50 | 51 | private void ReadLumps() 52 | { 53 | Directory = new BSPDirectoryEntry[LumpCount]; 54 | for (int i = 0; i < 17; i++) 55 | { 56 | Directory[i] = new BSPDirectoryEntry(BSP.ReadInt32(), BSP.ReadInt32()); 57 | } 58 | 59 | Directory[0].Name = "Entities"; 60 | Directory[1].Name = "Textures"; 61 | Directory[2].Name = "Planes"; 62 | Directory[3].Name = "Nodes"; 63 | Directory[4].Name = "Leafs"; 64 | Directory[5].Name = "Leaf faces"; 65 | Directory[6].Name = "Leaf brushes"; 66 | Directory[7].Name = "Models"; 67 | Directory[8].Name = "Brushes"; 68 | Directory[9].Name = "Brush sides"; 69 | Directory[10].Name = "Vertexes"; 70 | Directory[11].Name = "Mesh vertexes"; 71 | Directory[12].Name = "Effects"; 72 | Directory[13].Name = "Faces"; 73 | Directory[14].Name = "Lightmaps"; 74 | Directory[15].Name = "Light volumes"; 75 | Directory[16].Name = "Vis data"; 76 | } 77 | 78 | private void ReadMagic() 79 | { 80 | BSP.BaseStream.Seek(0, SeekOrigin.Begin); 81 | Magic = new string(BSP.ReadChars(4)); 82 | } 83 | 84 | 85 | 86 | private void ReadVersion() 87 | { 88 | BSP.BaseStream.Seek(4, SeekOrigin.Begin); 89 | Version = BSP.ReadUInt32(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/BSPMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Ionic.Zip; 4 | using UnityEngine; 5 | 6 | namespace SharpBSP 7 | { 8 | public class BSPMap 9 | { 10 | // This is the reader that seeks around the map 11 | // and grabs the data. 12 | private BinaryReader BSP; 13 | 14 | // The header contains the directory of lumps 15 | public BSPHeader header; 16 | 17 | // These are the objects that hold data extraced from the lumps 18 | // each one has public fields that hold the data in them 19 | // Note that there are many lumps we don't need, so they 20 | // aren't processed. If you want a tool to parse a .bsp 21 | // more throughly, check my github/google for "SharpBSP". 22 | public EntityLump entityLump; 23 | public TextureLump textureLump; 24 | public VertexLump vertexLump; 25 | public FaceLump faceLump; 26 | public LightmapLump lightmapLump; 27 | 28 | public BSPMap(string filename, bool loadFromPK3) 29 | { 30 | filename = "maps/" + filename; 31 | 32 | // Look through all available .pk3 files to find the map. 33 | // The first map will be used, which doesn't match Q3 behavior, as it would use the last found map, but eh. 34 | if (loadFromPK3) 35 | { 36 | foreach (var info in new DirectoryInfo("Assets/baseq3/").GetFiles()) 37 | { 38 | if (info.Name.EndsWith(".PK3") || info.Name.EndsWith(".pk3")) 39 | { 40 | using (ZipFile pk3 = ZipFile.Read("Assets/baseq3/" + info.Name)) 41 | { 42 | if (pk3.ContainsEntry(filename)) 43 | { 44 | var entry = pk3 [filename]; 45 | using (var mapstream = pk3 [filename].OpenReader()) 46 | { 47 | var ms = new MemoryStream(); 48 | entry.Extract(ms); 49 | BSP = new BinaryReader(ms); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } else 56 | { 57 | BSP = new BinaryReader(File.Open(filename, FileMode.Open)); 58 | } 59 | 60 | // Read our header and lumps 61 | ReadHeader(); 62 | ReadEntities(); 63 | ReadTextures(); 64 | ReadVertexes(); 65 | ReadFaces(); 66 | ReadMeshVerts(); 67 | ReadLightmaps(); 68 | 69 | // Comb through all of the available .pk3 files and load in any textures needed by the current map. 70 | // Textures in higher numbered .pk3 files will be used over ones in lower ones. 71 | foreach (var info in new DirectoryInfo("Assets/baseq3/").GetFiles()) 72 | { 73 | if (info.Name.EndsWith(".pk3") || info.Name.EndsWith(".PK3")) 74 | textureLump.PullInTextures(info.Name); 75 | } 76 | 77 | BSP.Close(); 78 | } 79 | 80 | private void ReadHeader() 81 | { 82 | header = new BSPHeader(BSP); 83 | } 84 | 85 | private void ReadEntities() 86 | { 87 | // Load Entity String 88 | // It's just one big mutha' string with a length defined in the header. 89 | // This is the only lump that may not end on an even four-byte block 90 | BSP.BaseStream.Seek(header.Directory [0].Offset, SeekOrigin.Begin); 91 | entityLump = new EntityLump(new String(BSP.ReadChars(header.Directory [0].Length))); 92 | } 93 | 94 | private void ReadTextures() 95 | { 96 | // This calculates the number of textures in the lump, and creates a new texture 97 | // object inside of the texturelump's list for each of them. 98 | // Note that these aren't actually the texture graphics themselves, they're definitions 99 | // for getting the texture from an external source. 100 | BSP.BaseStream.Seek(header.Directory [1].Offset, SeekOrigin.Begin); 101 | // A texture is 72 bytes, so we use 72 to calculate the number of textures in the lump 102 | int textureCount = header.Directory [1].Length / 72; 103 | textureLump = new TextureLump(textureCount); 104 | for (int i = 0; i < textureCount; i++) 105 | { 106 | textureLump.Textures [i] = new Texture(new string(BSP.ReadChars(64)), BSP.ReadInt32(), BSP.ReadInt32()); 107 | } 108 | } 109 | 110 | private void ReadVertexes() 111 | { 112 | // Calc how many verts there are, them rip them into the vertexLump 113 | BSP.BaseStream.Seek(header.Directory [10].Offset, SeekOrigin.Begin); 114 | // A vertex is 44 bytes, so use that to calc how many there are using the lump length from the header 115 | int vertCount = header.Directory [10].Length / 44; 116 | vertexLump = new VertexLump(vertCount); 117 | for (int i = 0; i < vertCount; i++) 118 | { 119 | vertexLump.Verts [i] = new Vertex(new Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()), BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle(), new Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()), BSP.ReadBytes(4)); 120 | } 121 | 122 | } 123 | 124 | private void ReadFaces() 125 | { 126 | BSP.BaseStream.Seek(header.Directory [13].Offset, SeekOrigin.Begin); 127 | // A face is 104 bytes of data, so the count is lenght of the lump / 104. 128 | int faceCount = header.Directory [13].Length / 104; 129 | faceLump = new FaceLump(faceCount); 130 | for (int i = 0; i < faceCount; i++) 131 | { 132 | // This is pretty fucking intense. 133 | faceLump.Faces [i] = new Face(BSP.ReadInt32(), BSP.ReadInt32(), BSP.ReadInt32(), BSP.ReadInt32(), BSP.ReadInt32(), BSP.ReadInt32(), BSP.ReadInt32(), BSP.ReadInt32(), new int[] 134 | { 135 | BSP.ReadInt32(), 136 | BSP.ReadInt32() 137 | }, new int[] 138 | { 139 | BSP.ReadInt32(), 140 | BSP.ReadInt32() 141 | }, new Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()), new Vector3[] 142 | { 143 | new Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()), 144 | new Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()) 145 | }, new Vector3(BSP.ReadSingle(), BSP.ReadSingle(), BSP.ReadSingle()), new int[] 146 | { 147 | BSP.ReadInt32(), 148 | BSP.ReadInt32() 149 | }); 150 | } 151 | } 152 | 153 | private void ReadMeshVerts() 154 | { 155 | BSP.BaseStream.Seek(header.Directory [11].Offset, SeekOrigin.Begin); 156 | // a meshvert is just a 4-byte int, so there are lumplength/4 meshverts 157 | int meshvertCount = header.Directory [11].Length / 4; 158 | vertexLump.MeshVerts = new int[meshvertCount]; 159 | for (int i = 0; i uvs; 20 | 21 | // Calculate UVs for our tessellated vertices 22 | private Vector2 BezCurveUV(float t, Vector2 p0, Vector2 p1, Vector2 p2) 23 | { 24 | Vector2 bezPoint = new Vector2(); 25 | 26 | float a = 1f - t; 27 | float tt = t * t; 28 | 29 | float[] tPoints = new float[2]; 30 | for (int i = 0; i < 2; i++) 31 | { 32 | tPoints [i] = ((a * a) * p0 [i]) + (2 * a) * (t * p1 [i]) + (tt * p2 [i]); 33 | } 34 | 35 | bezPoint.Set(tPoints [0], tPoints [1]); 36 | 37 | return bezPoint; 38 | } 39 | 40 | // Calculate a vector3 at point t on a bezier curve between 41 | // p0 and p2 via p1. 42 | private Vector3 BezCurve(float t, Vector3 p0, Vector3 p1, Vector3 p2) 43 | { 44 | Vector3 bezPoint = new Vector3(); 45 | 46 | float a = 1f - t; 47 | float tt = t * t; 48 | 49 | float[] tPoints = new float[3]; 50 | for (int i = 0; i < 3; i++) 51 | { 52 | tPoints [i] = ((a * a) * p0 [i]) + (2 * a) * (t * p1 [i]) + (tt * p2 [i]); 53 | } 54 | 55 | bezPoint.Set(tPoints [0], tPoints [1], tPoints [2]); 56 | 57 | return bezPoint; 58 | } 59 | 60 | // This takes a tessellation level and three vector3 61 | // p0 is start, p1 is the midpoint, p2 is the endpoint 62 | // The returned list begins with p0, ends with p2, with 63 | // the tessellated verts in between. 64 | private List Tessellate(int level, Vector3 p0, Vector3 p1, Vector3 p2) 65 | { 66 | List vects = new List(); 67 | 68 | float stepDelta = 1.0f / level; 69 | float step = stepDelta; 70 | 71 | vects.Add(p0); 72 | for (int i = 0; i < (level - 1); i++) 73 | { 74 | vects.Add(BezCurve(step, p0, p1, p2)); 75 | step += stepDelta; 76 | } 77 | vects.Add(p2); 78 | return vects; 79 | } 80 | 81 | // Same as above, but for UVs 82 | private List TessellateUV(int level, Vector2 p0, Vector2 p1, Vector2 p2) 83 | { 84 | List vects = new List(); 85 | 86 | float stepDelta = 1.0f / level; 87 | float step = stepDelta; 88 | 89 | vects.Add(p0); 90 | for (int i = 0; i < (level - 1); i++) 91 | { 92 | vects.Add(BezCurveUV(step, p0, p1, p2)); 93 | step += stepDelta; 94 | } 95 | vects.Add(p2); 96 | return vects; 97 | } 98 | 99 | // Where the magic happens. 100 | public BezierMesh(int level, List control, List controlUvs, List controlUv2s) 101 | { 102 | // The mesh we're building 103 | Mesh patchMesh = new Mesh(); 104 | patchMesh.name = "BSPmesh (bez)"; 105 | 106 | // We'll use these two to hold our verts, tris, and uvs 107 | List vertex = new List(); 108 | List index = new List(); 109 | List uvs = new List(); 110 | List uv2s = new List(); 111 | 112 | // The incoming list is 9 entires, 113 | // referenced as p0 through p8 here. 114 | 115 | // Generate extra rows to tessellate 116 | // each row is three control points 117 | // start, curve, end 118 | // The "lines" go as such 119 | // p0s from p0 to p3 to p6 '' 120 | // p1s from p1 p4 p7 121 | // p2s from p2 p5 p8 122 | 123 | List p0suv2; 124 | List p0suv; 125 | List p0s; 126 | p0s = Tessellate(level, control [0], control [3], control [6]); 127 | p0suv = TessellateUV(level, controlUvs [0], controlUvs [3], controlUvs [6]); 128 | p0suv2 = TessellateUV(level, controlUv2s [0], controlUv2s [3], controlUv2s [6]); 129 | 130 | List p1suv2; 131 | List p1suv; 132 | List p1s; 133 | p1s = Tessellate(level, control [1], control [4], control [7]); 134 | p1suv = TessellateUV(level, controlUvs [1], controlUvs [4], controlUvs [7]); 135 | p1suv2 = TessellateUV(level, controlUv2s [1], controlUv2s [4], controlUv2s [7]); 136 | 137 | List p2suv2; 138 | List p2suv; 139 | List p2s; 140 | p2s = Tessellate(level, control [2], control [5], control [8]); 141 | p2suv = TessellateUV(level, controlUvs [2], controlUvs [5], controlUvs [8]); 142 | p2suv2 = TessellateUV(level, controlUv2s [2], controlUv2s [5], controlUv2s [8]); 143 | 144 | // Tessellate all those new sets of control points and pack 145 | // all the results into our vertex array, which we'll return. 146 | // Make our uvs list while we're at it. 147 | for (int i = 0; i <= level; i++) 148 | { 149 | vertex.AddRange(Tessellate(level, p0s [i], p1s [i], p2s [i])); 150 | uvs.AddRange(TessellateUV(level, p0suv [i], p1suv [i], p2suv [i])); 151 | uv2s.AddRange(TessellateUV(level, p0suv2 [i], p1suv2 [i], p2suv2 [i])); 152 | } 153 | 154 | // This will produce (tessellationLevel + 1)^2 verts 155 | int numVerts = (level + 1) * (level + 1); 156 | 157 | // Computer triangle indexes for forming a mesh. 158 | // The mesh will be tessellationlevel + 1 verts 159 | // wide and tall. 160 | int xStep = 1; 161 | int width = level + 1; 162 | for (int i = 0; i < numVerts - width; i++) 163 | { 164 | //on left edge 165 | if (xStep == 1) 166 | { 167 | index.Add(i); 168 | index.Add(i + width); 169 | index.Add(i + 1); 170 | 171 | xStep++; 172 | continue; 173 | } else if (xStep == width) //on right edge 174 | { 175 | index.Add(i); 176 | index.Add(i + (width - 1)); 177 | index.Add(i + width); 178 | 179 | xStep = 1; 180 | continue; 181 | } else // not on an edge, so add two 182 | { 183 | index.Add(i); 184 | index.Add(i + (width - 1)); 185 | index.Add(i + width); 186 | 187 | 188 | index.Add(i); 189 | index.Add(i + width); 190 | index.Add(i + 1); 191 | 192 | xStep++; 193 | continue; 194 | } 195 | } 196 | 197 | // Add the verts and tris 198 | patchMesh.vertices = vertex.ToArray(); 199 | patchMesh.triangles = index.ToArray(); 200 | patchMesh.uv = uvs.ToArray(); 201 | patchMesh.uv2 = uv2s.ToArray(); 202 | 203 | // Dunno if these are needed, but why not? 204 | // They're actually pretty cheap, considering. 205 | patchMesh.RecalculateBounds(); 206 | patchMesh.RecalculateNormals(); 207 | patchMesh.Optimize(); 208 | 209 | //Return the mesh! Shazam! 210 | mesh = patchMesh; 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/Face.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | namespace SharpBSP 5 | { 6 | public class Face 7 | { 8 | // The fields in this class are kind of obtuse. I recommend looking up the Q3 .bsp map spec for full understanding. 9 | 10 | public int texture; 11 | public int effect; 12 | public int type; 13 | public int vertex; 14 | public int n_vertexes; 15 | public int meshvert; 16 | public int n_meshverts; 17 | public int lm_index; 18 | public int[] lm_start; 19 | public int[] lm_size; 20 | public Vector3 lm_origin; 21 | public Vector3[] lm_vecs; 22 | public Vector3 normal; 23 | public int[] size; 24 | 25 | public Face(int texture, int effect, int type, int vertex, int n_vertexes, int meshvert, int n_meshverts, int lm_index, int[] lm_start, int[] lm_size, Vector3 lm_origin, Vector3[] lm_vecs, Vector3 normal, int[] size) 26 | { 27 | this.texture = texture; 28 | this.effect = effect; 29 | this.type = type; 30 | this.vertex = vertex; 31 | this.n_vertexes = n_vertexes; 32 | this.meshvert = meshvert; 33 | this.n_meshverts = n_meshverts; 34 | this.lm_index = lm_index; 35 | this.lm_start = lm_start; 36 | this.lm_size = lm_size; 37 | this.lm_origin = lm_origin; 38 | this.lm_vecs = lm_vecs; 39 | this.normal = normal; 40 | this.size = size; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/TGALoader.cs: -------------------------------------------------------------------------------- 1 | // This was made by aaro4130 on the Unity forums. Thanks boss! 2 | // It's been optimized and slimmed down for the purpose of loading Quake 3 TGA textures from memory streams. 3 | 4 | using System; 5 | using System.IO; 6 | using UnityEngine; 7 | 8 | public static class TGALoader 9 | { 10 | 11 | public static Texture2D LoadTGA(string fileName) 12 | { 13 | using (var imageFile = File.OpenRead(fileName)) 14 | { 15 | return LoadTGA(imageFile); 16 | } 17 | } 18 | 19 | public static Texture2D LoadTGA(Stream TGAStream) 20 | { 21 | 22 | using (BinaryReader r = new BinaryReader(TGAStream)) 23 | { 24 | // Skip some header info we don't care about. 25 | // Even if we did care, we have to move the stream seek point to the beginning, 26 | // as the previous method in the workflow left it at the end. 27 | r.BaseStream.Seek(12, SeekOrigin.Begin); 28 | 29 | short width = r.ReadInt16(); 30 | short height = r.ReadInt16(); 31 | int bitDepth = r.ReadByte(); 32 | 33 | // Skip a byte of header information we don't care about. 34 | r.BaseStream.Seek(1, SeekOrigin.Current); 35 | 36 | Texture2D tex = new Texture2D(width, height); 37 | Color32[] pulledColors = new Color32[width * height]; 38 | 39 | if (bitDepth == 32) 40 | { 41 | for (int i = 0; i < width * height; i++) 42 | { 43 | byte red = r.ReadByte(); 44 | byte green = r.ReadByte(); 45 | byte blue = r.ReadByte(); 46 | byte alpha = r.ReadByte(); 47 | 48 | pulledColors [i] = new Color32(blue, green, red, alpha); 49 | } 50 | } else if (bitDepth == 24) 51 | { 52 | for (int i = 0; i < width * height; i++) 53 | { 54 | byte red = r.ReadByte(); 55 | byte green = r.ReadByte(); 56 | byte blue = r.ReadByte(); 57 | 58 | pulledColors [i] = new Color32(blue, green, red, 1); 59 | } 60 | } else 61 | { 62 | throw new Exception("TGA texture had non 32/24 bit depth."); 63 | } 64 | 65 | tex.SetPixels32(pulledColors); 66 | tex.Apply(); 67 | return tex; 68 | 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/Texture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SharpBSP 4 | { 5 | public class Texture 6 | { 7 | public string Name { 8 | get; 9 | private set; 10 | } 11 | 12 | public int Flags { 13 | get; 14 | private set; 15 | } 16 | 17 | public int Contents { 18 | get; 19 | private set; 20 | } 21 | 22 | 23 | public Texture(string rawName, int flags, int contents) 24 | { 25 | //The string is read as 64 characters, which includes a bunch of null bytes. We strip them to avoid oddness when printing and using the texture names. 26 | Name = rawName.Replace("\0", string.Empty); 27 | Flags = flags; 28 | Contents = contents; 29 | 30 | // Remove some common shader modifiers to get normal 31 | // textures instead. This is kind of a hack, and could 32 | // bit you if a texture just happens to have any of these 33 | // in its name but isn't actually a shader texture. 34 | Name = Name.Replace("_hell", string.Empty); 35 | Name = Name.Replace("_trans", string.Empty); 36 | Name = Name.Replace("flat_400", string.Empty); 37 | Name = Name.Replace("_750", string.Empty); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /uQuake/Scripts/uQuake/Types/Vertex.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace SharpBSP 4 | { 5 | public class Vertex 6 | { 7 | public Vector3 position; 8 | public Vector3 normal; 9 | public byte[] color; 10 | 11 | // These are texture coords, or UVs 12 | public Vector2 texcoord = new Vector2(); 13 | public Vector2 lmcoord = new Vector2(); 14 | 15 | public Vertex(Vector3 position, float texX, float texY, float lmX, float lmY, Vector3 normal, byte[] color) 16 | { 17 | this.position = position; 18 | this.normal = normal; 19 | 20 | // Color data doesn't get used 21 | this.color = color; 22 | 23 | // Invert the texture coords, to account for 24 | // the difference in the way Unity and Quake3 25 | // handle them. 26 | texcoord.x = texX; 27 | texcoord.y = -texY; 28 | 29 | // Lightmaps aren't used for now, but store the 30 | // data for them anyway. Inverted, same as above. 31 | lmcoord.x = lmX; 32 | lmcoord.y = lmY; 33 | 34 | // Do that swizzlin'. 35 | // Dunno why it's called swizzle. An article I was reading called it that. 36 | // Swizzle, swizzle, swizzle. 37 | Swizzle(); 38 | } 39 | 40 | // This converts the verts from the format Q3 uses to the one Unity3D uses. 41 | // Look up the Q3 map/rendering specs if you want the details. 42 | // Quake3 also uses an odd scale where 0.03 units is about 1 meter, so scale it way down 43 | // while you're at it. 44 | private void Swizzle() 45 | { 46 | float tempz = position.z; 47 | float tempy = position.y; 48 | position.y = tempz; 49 | position.z = -tempy; 50 | position.x = -position.x; 51 | 52 | tempz = normal.z; 53 | tempy = normal.y; 54 | 55 | normal.y = tempz; 56 | normal.z = -tempy; 57 | normal.x = -normal.x; 58 | 59 | position.Scale(new Vector3(0.03f, 0.03f, 0.03f)); 60 | normal.Scale(new Vector3(0.03f, 0.03f, 0.03f)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /uQuake/baseq3/place_pk3_files_here: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikezila/uQuake3/9c72f515dadba17b2769846216640766a5121a01/uQuake/baseq3/place_pk3_files_here --------------------------------------------------------------------------------