497 lines
10 KiB
C#
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);
|
|
}
|
|
|
|
}
|
|
} |