Files
GoakeFlax/Source/Game/MapParser/MapParser.cs
2021-07-05 15:52:51 +03:00

497 lines
10 KiB
C#

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 Vector3 v1, v2, v3;
public string texture;
public Vector2 offset;
public float rotation;
public Vector2 scale;
public int contentFlags, surfaceFlags, surfaceValue;
}
public class MapBrush
{
public MapFacePlane[] planes;
}
public class MapPatch
{
public string name;
}
public struct PatchVertex
{
public Vector3 v;
public Vector2 uv;
}
public class MapEntity
{
public Dictionary<string, string> properties = new Dictionary<string, string>();
public List<MapEntity> entities = new List<MapEntity>();
public List<MapBrush> brushes = new List<MapBrush>();
public List<MapPatch> patches = new List<MapPatch>();
}
public static class MapParser
{
static public 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<data.Length; i++)
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;
case '{':
{
currentEntity = new MapEntity();
rootEntity.entities.Add(currentEntity);
level++;
for (; index < data.Length; index++)
{
if (data[index] == '\n')
break;
}
index++;
ParseEntity(currentEntity, data, ref index);
break;
}
case '}':
{
//currentEntity = rootEntity;
break;
}
default:
throw new Exception("unsupported character: '" + c + "'");
}
//if (level < 0 || level > 2)
// throw new Exception("Failed to parse .map file: unexpected entity found at line " + lineNumber.ToString());
} while (++index < data.Length);
return rootEntity;
}
static void ParseComment(byte[] data, ref int index)
{
for (; index < data.Length; index++)
{
if (data[index] == '\n')
break;
}
}
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);
}
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] != ' ')
break;
index++;
}
return sb.ToString();
}
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]);
}
index++;
while (index < data.Length)
{
if (data[index] != ' ')
break;
index++;
}
return sb.ToString();
}
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;
else
throw new Exception("failed to ParseFloat: " + fs);
}
static int ParseInt(byte[] data, ref int index)
{
string fs = ParseString(data, ref index);
if (int.TryParse(fs, out int value))
return value;
else
throw new Exception("failed to ParseInt: " + fs);
}
static Vector3 ParseVector3(byte[] data, ref int index)
{
return new Vector3(
ParseFloat(data, ref index),
ParseFloat(data, ref index),
ParseFloat(data, ref index)
);
}
static Vector2 ParseVector2(byte[] data, ref int index)
{
return new Vector2(
ParseFloat(data, ref index),
ParseFloat(data, ref index)
);
}
static Vector3 ParsePlaneVector3(byte[] data, ref int index)
{
index++;
while (index < data.Length)
{
if (data[index] != ' ')
break;
index++;
}
Vector3 vector = ParseVector3(data, ref index);
while (index < data.Length)
{
if (data[index] == ')')
break;
index++;
}
index++;
while (index < data.Length)
{
if (data[index] != ' ')
break;
index++;
}
return vector;
}
static MapBrush ParseBrush(byte[] data, ref int index)
{
MapBrush brush = new MapBrush();
List<MapFacePlane> planes = new List<MapFacePlane>(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
// ( <v1> ) ( <v2> ) ( <v3> ) <shader> <x_shift> <y_shift> <rotation> <x_scale> <y_scale> <content_flags> <surface_flags> <value>
case '(':
{
MapFacePlane plane;
plane.v1 = ParsePlaneVector3(data, ref index);
plane.v2 = ParsePlaneVector3(data, ref index);
plane.v3 = ParsePlaneVector3(data, ref index);
plane.texture = ParseString(data, ref index);
plane.offset = ParseVector2(data, ref index);
plane.rotation = ParseFloat(data, ref index);
plane.scale = ParseVector2(data, ref index);
plane.contentFlags = ParseInt(data, ref index);
plane.surfaceFlags = ParseInt(data, ref index);
plane.surfaceValue = ParseInt(data, ref index);
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;
}
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
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++;
PatchVertex[] 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 = ParseVector3(data, ref index);
vertices[vertexIndex].uv = new Vector2(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);
}
}
}