using System; using System.Collections.Generic; using System.Globalization; using System.Text; using FlaxEngine; // https://web.archive.org/web/20160316213335/http://forums.ubergames.net/topic/2658-understanding-the-quake-3-map-format/ // https://web.archive.org/web/20210228125854/https://forums.thedarkmod.com/index.php?/topic/15668-plugin-request-save-map-in-quake-3-format/ namespace Game; public struct MapFacePlane { public Float3 v1, v2, v3; public Plane plane; public string texture; public Float2 offset; public float rotation; public Float2 scale; public int contentFlags, surfaceFlags, surfaceValue; } public class MapBrush { public MapFacePlane[] planes; } public class MapPatch { public string name; } public struct PatchVertex { public Float3 v; public Float2 uv; } public class MapEntity { public List brushes = new List(); public List entities = new List(); public List patches = new List(); public Dictionary properties = new Dictionary(); } public static class MapParser { public static MapEntity Parse(byte[] data) { if (data.Length == 0) return null; MapEntity rootEntity = new MapEntity(); MapEntity currentEntity = rootEntity; int level = 0; int index = 0; //for (int i=0; i 2) // throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString()); } while (++index < data.Length); return rootEntity; } private static void ParseComment(byte[] data, ref int index) { for (; index < data.Length; index++) if (data[index] == '\n') break; } private static void ParseEntity(MapEntity currentEntity, byte[] data, ref int index) { bool entityParsed = false; do { char c = (char)data[index]; char c1 = index + 1 < data.Length ? (char)data[index + 1] : (char)0; switch (c) { case '\n': case '\r': break; case '/': if (c1 == '/') ParseComment(data, ref index); else throw new Exception("unexpected character: '" + c + "'"); break; // "name" "value" case '"': { string propName = ParseQuotedString(data, ref index); string propValue = ParseQuotedString(data, ref index); if (currentEntity.properties.ContainsKey(propName)) throw new Exception("Failed to parse .map file: multiple properties defined for " + propName + " at line ?"); // + lineNumber.ToString()); currentEntity.properties.Add(propName, propValue); break; } // brush case '{': { index++; for (; index < data.Length; index++) { if (data[index] != ' ' && data[index] != '\r' && data[index] != '\n') break; //index++; } if (index >= data.Length) break; if (data[index] == '(') currentEntity.brushes.Add(ParseBrush(data, ref index)); else if (char.IsLetter((char)data[index]) || char.IsNumber((char)data[index])) currentEntity.patches.Add(ParsePatch(data, ref index)); break; } case '}': { entityParsed = true; break; } default: throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !entityParsed); } private static string ParseQuotedString(byte[] data, ref int index) { StringBuilder sb = new StringBuilder(); index++; for (; index < data.Length; index++) { if (data[index] == '"') break; sb.Append((char)data[index]); } index++; while (index < data.Length) { if (data[index] != ' ' && data[index] != '\t') break; index++; } return sb.ToString(); } private static string ParseString(byte[] data, ref int index) { StringBuilder sb = new StringBuilder(); for (; index < data.Length; index++) { if (data[index] == ' ' || data[index] == '\r' || data[index] == '\n') break; sb.Append((char)data[index]); } //if (data[index] == '\n') // index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } return sb.ToString(); } private static float ParseFloat(byte[] data, ref int index) { string fs = ParseString(data, ref index); if (float.TryParse(fs, NumberStyles.Float, CultureInfo.InvariantCulture, out float value)) return value; //else if (float.TryParse(fs, CultureInfo.InvariantCulture, out intValue)) // return intValue; throw new Exception("failed to ParseFloat: " + fs); } private static int ParseInt(byte[] data, ref int index) { string fs = ParseString(data, ref index); if (int.TryParse(fs, out int value)) return value; throw new Exception("failed to ParseInt: " + fs); } private static Float3 ParseFloat3(byte[] data, ref int index) { return new Float3( ParseFloat(data, ref index), ParseFloat(data, ref index), ParseFloat(data, ref index) ); } private static Float2 ParseFloat2(byte[] data, ref int index) { return new Float2( ParseFloat(data, ref index), ParseFloat(data, ref index) ); } private static Float3 ParsePlaneFloat3(byte[] data, ref int index) { index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } Float3 vector = ParseFloat3(data, ref index); // rounding /*float temp = vector.Z; vector.Z = vector.Y; vector.Y = temp;*/ /*vector.X = (float)Math.Round(vector.X, 1); vector.Y = (float)Math.Round(vector.Y, 1); vector.Z = (float)Math.Round(vector.Z, 1);*/ while (index < data.Length) { if (data[index] == ')') break; index++; } index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } return vector; } private static MapBrush ParseBrush(byte[] data, ref int index) { MapBrush brush = new MapBrush(); var planes = new List(6); bool brushParsed = false; do { char c = (char)data[index]; //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\r': case '\n': break; // brush face (quake format): // ( ) ( ) ( ) // brush face (quake3 format): // ( ) ( ) ( ) // brush face (valve format): // ( ) ( ) ( ) [ ] [ ] case '(': { MapFacePlane plane = new MapFacePlane(); plane.v1 = ParsePlaneFloat3(data, ref index); plane.v2 = ParsePlaneFloat3(data, ref index); plane.v3 = ParsePlaneFloat3(data, ref index); plane.texture = ParseString(data, ref index); if (true) // quake or quake3 format { plane.offset = ParseFloat2(data, ref index); plane.rotation = ParseFloat(data, ref index); plane.scale = ParseFloat2(data, ref index); char peekChar = (char)data[index]; if (peekChar != '\n') // quake3 format { plane.contentFlags = ParseInt(data, ref index); plane.surfaceFlags = ParseInt(data, ref index); plane.surfaceValue = ParseInt(data, ref index); } } // Flip Y and Z plane.v1 = new Float3(plane.v1.X, plane.v1.Z, plane.v1.Y); plane.v2 = new Float3(plane.v2.X, plane.v2.Z, plane.v2.Y); plane.v3 = new Float3(plane.v3.X, plane.v3.Z, plane.v3.Y); plane.plane = new Plane(plane.v1, plane.v2, plane.v3); planes.Add(plane); break; } case '}': { brushParsed = true; break; } default: if (char.IsLetter(c) || char.IsNumber(c)) { // patch name } throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !brushParsed); brush.planes = planes.ToArray(); return brush; } private static MapPatch ParsePatch(byte[] data, ref int index) { MapPatch patch = new MapPatch(); patch.name = ParseString(data, ref index); bool patchParsed = false; do { char c = (char)data[index]; //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\r': case '\n': break; case '{': { index++; ParsePatchInner(patch, data, ref index); break; } case '}': { patchParsed = true; break; } default: throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !patchParsed); return patch; } // unfinished and untested private static void ParsePatchInner(MapPatch patch, byte[] data, ref int index) { string shaderName = ParseString(data, ref index); while (index < data.Length) { if (data[index] == '(') break; index++; } index++; int width = ParseInt(data, ref index); int height = ParseInt(data, ref index); int dummy1 = ParseInt(data, ref index); int dummy2 = ParseInt(data, ref index); int dummy3 = ParseInt(data, ref index); while (index < data.Length) { if (data[index] == ')') break; index++; } index++; while (index < data.Length) { if (data[index] == '(') break; index++; } index++; var vertices = new PatchVertex[width * height]; bool verticesParsed = false; int vertexIndex = 0; do { char c = (char)data[index]; //char c1 = (index + 1 < data.Length) ? (char) data[index + 1] : (char) 0; switch (c) { case '\r': case '\n': break; case '(': { index++; for (int iw = 0; iw < width; iw++) { while (index < data.Length) { if (data[index] == '(') break; index++; } index++; while (index < data.Length) { if (data[index] != ' ') break; index++; } vertices[vertexIndex].v = ParseFloat3(data, ref index); vertices[vertexIndex].uv = new Float2(ParseFloat(data, ref index), ParseFloat(data, ref index)); vertexIndex++; while (index < data.Length) { if (data[index] == ')') break; index++; } index++; } break; } case '}': { verticesParsed = true; break; } default: throw new Exception("unsupported character: '" + c + "'"); } } while (index++ < data.Length && !verticesParsed); } }