From 25544952fd21b18fa0b2df6443128b176e0c4d4e Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 15 Sep 2015 23:11:43 -0600 Subject: [PATCH] Initial commit; all structs and extensions. --- LibBSP.sln | 22 + LibBSP/Extensions/Color32Extensions.cs | 37 + LibBSP/Extensions/PlaneExtensions.cs | 158 ++++ LibBSP/Extensions/RectExtensions.cs | 56 ++ LibBSP/Extensions/StringExtensions.cs | 152 ++++ LibBSP/Extensions/UIVertexExtensions.cs | 66 ++ LibBSP/LibBSP.csproj | 98 +++ LibBSP/Properties/AssemblyInfo.cs | 36 + LibBSP/Structs/BSP/BSP.cs | 158 ++++ LibBSP/Structs/BSP/Brush.cs | 145 ++++ LibBSP/Structs/BSP/BrushSide.cs | 169 +++++ LibBSP/Structs/BSP/Edge.cs | 117 +++ LibBSP/Structs/BSP/Face.cs | 244 ++++++ LibBSP/Structs/BSP/Leaf.cs | 169 +++++ .../Structs/BSP/Lumps/SourceDispVertices.cs | 58 ++ LibBSP/Structs/BSP/Lumps/SourceStaticProps.cs | 47 ++ LibBSP/Structs/BSP/Lumps/Textures.cs | 137 ++++ LibBSP/Structs/BSP/Model.cs | 179 +++++ LibBSP/Structs/BSP/Node.cs | 134 ++++ LibBSP/Structs/BSP/SourceCubemap.cs | 100 +++ LibBSP/Structs/BSP/SourceDispInfo.cs | 128 ++++ LibBSP/Structs/BSP/SourceDispVertex.cs | 53 ++ LibBSP/Structs/BSP/SourceStaticProp.cs | 116 +++ LibBSP/Structs/BSP/SourceTexData.cs | 67 ++ LibBSP/Structs/BSP/TexInfo.cs | 186 +++++ LibBSP/Structs/BSP/Texture.cs | 117 +++ LibBSP/Structs/Common/Entity.cs | 716 ++++++++++++++++++ LibBSP/Structs/Common/Lumps/Entities.cs | 134 ++++ LibBSP/Structs/Common/NumList.cs | 85 +++ LibBSP/Structs/Common/Plane.cs | 423 +++++++++++ LibBSP/Structs/Common/Ray.cs | 95 +++ LibBSP/Structs/Common/Rect.cs | 297 ++++++++ LibBSP/Structs/Common/UIVertex.cs | 45 ++ LibBSP/Structs/Common/Vector2d.cs | 449 +++++++++++ LibBSP/Structs/Common/Vector3d.cs | 513 +++++++++++++ LibBSP/Structs/Common/Vector4d.cs | 514 +++++++++++++ LibBSP/Structs/Doom/DoomMap.cs | 72 ++ LibBSP/Structs/Doom/Linedef.cs | 108 +++ LibBSP/Structs/Doom/Node.cs | 73 ++ LibBSP/Structs/Doom/Sector.cs | 59 ++ LibBSP/Structs/Doom/Segment.cs | 58 ++ LibBSP/Structs/Doom/Sidedef.cs | 70 ++ LibBSP/Structs/Doom/Thing.cs | 104 +++ LibBSP/Structs/MAP/MAPBrush.cs | 73 ++ LibBSP/Structs/MAP/MAPBrushSide.cs | 150 ++++ LibBSP/Structs/MAP/MAPDisplacement.cs | 128 ++++ LibBSP/Structs/MAP/MAPPatch.cs | 82 ++ 47 files changed, 7197 insertions(+) create mode 100644 LibBSP.sln create mode 100644 LibBSP/Extensions/Color32Extensions.cs create mode 100644 LibBSP/Extensions/PlaneExtensions.cs create mode 100644 LibBSP/Extensions/RectExtensions.cs create mode 100644 LibBSP/Extensions/StringExtensions.cs create mode 100644 LibBSP/Extensions/UIVertexExtensions.cs create mode 100644 LibBSP/LibBSP.csproj create mode 100644 LibBSP/Properties/AssemblyInfo.cs create mode 100644 LibBSP/Structs/BSP/BSP.cs create mode 100644 LibBSP/Structs/BSP/Brush.cs create mode 100644 LibBSP/Structs/BSP/BrushSide.cs create mode 100644 LibBSP/Structs/BSP/Edge.cs create mode 100644 LibBSP/Structs/BSP/Face.cs create mode 100644 LibBSP/Structs/BSP/Leaf.cs create mode 100644 LibBSP/Structs/BSP/Lumps/SourceDispVertices.cs create mode 100644 LibBSP/Structs/BSP/Lumps/SourceStaticProps.cs create mode 100644 LibBSP/Structs/BSP/Lumps/Textures.cs create mode 100644 LibBSP/Structs/BSP/Model.cs create mode 100644 LibBSP/Structs/BSP/Node.cs create mode 100644 LibBSP/Structs/BSP/SourceCubemap.cs create mode 100644 LibBSP/Structs/BSP/SourceDispInfo.cs create mode 100644 LibBSP/Structs/BSP/SourceDispVertex.cs create mode 100644 LibBSP/Structs/BSP/SourceStaticProp.cs create mode 100644 LibBSP/Structs/BSP/SourceTexData.cs create mode 100644 LibBSP/Structs/BSP/TexInfo.cs create mode 100644 LibBSP/Structs/BSP/Texture.cs create mode 100644 LibBSP/Structs/Common/Entity.cs create mode 100644 LibBSP/Structs/Common/Lumps/Entities.cs create mode 100644 LibBSP/Structs/Common/NumList.cs create mode 100644 LibBSP/Structs/Common/Plane.cs create mode 100644 LibBSP/Structs/Common/Ray.cs create mode 100644 LibBSP/Structs/Common/Rect.cs create mode 100644 LibBSP/Structs/Common/UIVertex.cs create mode 100644 LibBSP/Structs/Common/Vector2d.cs create mode 100644 LibBSP/Structs/Common/Vector3d.cs create mode 100644 LibBSP/Structs/Common/Vector4d.cs create mode 100644 LibBSP/Structs/Doom/DoomMap.cs create mode 100644 LibBSP/Structs/Doom/Linedef.cs create mode 100644 LibBSP/Structs/Doom/Node.cs create mode 100644 LibBSP/Structs/Doom/Sector.cs create mode 100644 LibBSP/Structs/Doom/Segment.cs create mode 100644 LibBSP/Structs/Doom/Sidedef.cs create mode 100644 LibBSP/Structs/Doom/Thing.cs create mode 100644 LibBSP/Structs/MAP/MAPBrush.cs create mode 100644 LibBSP/Structs/MAP/MAPBrushSide.cs create mode 100644 LibBSP/Structs/MAP/MAPDisplacement.cs create mode 100644 LibBSP/Structs/MAP/MAPPatch.cs diff --git a/LibBSP.sln b/LibBSP.sln new file mode 100644 index 0000000..8ad836e --- /dev/null +++ b/LibBSP.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30723.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibBSP", "libBSP\LibBSP.csproj", "{33E4FFCA-8D0B-4089-AFD2-527547531578}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {33E4FFCA-8D0B-4089-AFD2-527547531578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33E4FFCA-8D0B-4089-AFD2-527547531578}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33E4FFCA-8D0B-4089-AFD2-527547531578}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33E4FFCA-8D0B-4089-AFD2-527547531578}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/LibBSP/Extensions/Color32Extensions.cs b/LibBSP/Extensions/Color32Extensions.cs new file mode 100644 index 0000000..b1d36c8 --- /dev/null +++ b/LibBSP/Extensions/Color32Extensions.cs @@ -0,0 +1,37 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +#if UNITY +using UnityEngine; +#else +using System.Drawing; +#endif + +namespace LibBSP { +#if !UNITY + using Color32 = Color; +#endif + /// + /// Static class containing helper methods for Color32 objects. + /// + public static class Color32Extensions { + + /// + /// Constructs a new Color32 from the passed values. + /// + /// A component + /// R component + /// G component + /// B component + /// The resulting Color32 object + public static Color32 FromArgb(int a, int r, int g, int b) { +#if UNITY + return new Color32((byte)r, (byte)g, (byte)b, (byte)a); +#else + return Color.FromArgb(a, r, g, b); +#endif + } + + } +} diff --git a/LibBSP/Extensions/PlaneExtensions.cs b/LibBSP/Extensions/PlaneExtensions.cs new file mode 100644 index 0000000..455f91b --- /dev/null +++ b/LibBSP/Extensions/PlaneExtensions.cs @@ -0,0 +1,158 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Static class containing helper methods for Plane objects. + /// + public static class PlaneExtensions { + /// + /// Intersects three Planes at a Vector3. Returns NaN for all components if two or more Planes are parallel. + /// + /// Plane to intersect + /// Plane to intersect + /// Plane to intersect + /// Point of intersection if all three Planes meet at a point, (NaN, NaN, NaN) otherwise + public static Vector3 Intersection(Plane p1, Plane p2, Plane p3) { + Vector3 aN = p1.normal; + Vector3 bN = p2.normal; + Vector3 cN = p3.normal; + +#if UNITY + float partSolx1 = (bN.y * cN.z) - (bN.z * cN.y); + float partSoly1 = (bN.z * cN.x) - (bN.x * cN.z); + float partSolz1 = (bN.x * cN.y) - (bN.y * cN.x); + float det = (aN.x * partSolx1) + (aN.y * partSoly1) + (aN.z * partSolz1); +#else + double partSolx1 = (bN.y * cN.z) - (bN.z * cN.y); + double partSoly1 = (bN.z * cN.x) - (bN.x * cN.z); + double partSolz1 = (bN.x * cN.y) - (bN.y * cN.x); + double det = (aN.x * partSolx1) + (aN.y * partSoly1) + (aN.z * partSolz1); +#endif + if (det == 0) { + return new Vector3(System.Single.NaN, System.Single.NaN, System.Single.NaN); + } + + return new Vector3((p1.distance * partSolx1 + p2.distance * (cN.y * aN.z - cN.z * aN.y) + p3.distance * (aN.y * bN.z - aN.z * bN.y)) / det, + (p1.distance * partSoly1 + p2.distance * (aN.x * cN.z - aN.z * cN.x) + p3.distance * (bN.x * aN.z - bN.z * aN.x)) / det, + (p1.distance * partSolz1 + p2.distance * (cN.x * aN.y - cN.y * aN.x) + p3.distance * (aN.x * bN.y - aN.y * bN.x)) / det); + } + + /// + /// Intersects this Plane with two other Planes at a Vector3. Returns NaN for all components if two or more Planes are parallel. + /// + /// This Plane + /// Plane to intersect + /// Plane to intersect + /// Point of intersection if all three Planes meet at a point, (NaN, NaN, NaN) otherwise + public static Vector3 Intersect(this Plane p1, Plane p2, Plane p3) { + return Intersection(p1, p2, p3); + } + + /// + /// Intersects a Plane "" with a Ray "" at a Vector3. Returns NaN for all components if they do not intersect. + /// + /// Plane to intersect with + /// Ray to intersect + /// Point of intersection if "" intersects "", (NaN, NaN, NaN) otherwise + public static Vector3 Intersection(Plane p, Ray r) { +#if UNITY + float enter; +#else + double enter; +#endif + bool intersected = p.Raycast(r, out enter); + if(enter != 0 || intersected) { + return r.GetPoint(enter); + } else { + return new Vector3(System.Single.NaN, System.Single.NaN, System.Single.NaN); + } + } + + /// + /// Intersects this Plane with a Ray "" at a Vector3. Returns NaN for all components if they do not intersect. + /// + /// This Plane + /// Ray to intersect + /// Point of intersection if "" intersects this Plane, (NaN, NaN, NaN) otherwise + public static Vector3 Intersect(this Plane p, Ray r) { + return Intersection(p, r); + } + + /// + /// Intersects a Plane "" with this Ray at a Vector3. Returns NaN for all components if they do not intersect. + /// + /// This Ray + /// Plane to intersect with + /// Point of intersection if this Ray intersects "", (NaN, NaN, NaN) otherwise + public static Vector3 Intersect(this Ray r, Plane p) { + return Intersection(p, r); + } + + /// + /// Intersects two Planes at a Ray. Returns NaN for all components of both Vector3s of the Ray if the Planes are parallel. + /// + /// Plane to intersect + /// Plane to intersect + /// Line of intersection where "" intersects "", ((NaN, NaN, NaN) + p(NaN, NaN, NaN)) otherwise + public static Ray Intersection(Plane p1, Plane p2) { + Vector3 direction = Vector3.Cross(p1.normal, p2.normal); + if (direction == Vector3.zero) { + return new Ray(new Vector3(System.Single.NaN, System.Single.NaN, System.Single.NaN), new Vector3(System.Single.NaN, System.Single.NaN, System.Single.NaN)); + } + // If x == 0, solve for y in terms of z, or z in terms of y + + Vector3 origin; + + Vector3 sqrDirection = Vector3.Scale(direction, direction); + if (sqrDirection.x >= sqrDirection.y && sqrDirection.x >= sqrDirection.z) { +#if UNITY + float denom = (p1.normal.y * p2.normal.z) - (p2.normal.y * p1.normal.z); +#else + double denom = (p1.normal.y * p2.normal.z) - (p2.normal.y * p1.normal.z); +#endif + origin = new Vector3(0, + ((p1.normal.z * p2.distance) - (p2.normal.z * p1.distance)) / denom, + ((p2.normal.y * p1.distance) - (p1.normal.y * p2.distance)) / denom); + } else if (sqrDirection.y >= sqrDirection.x && sqrDirection.y >= sqrDirection.z) { +#if UNITY + float denom = (p1.normal.x * p2.normal.z) - (p2.normal.x * p1.normal.z); +#else + double denom = (p1.normal.x * p2.normal.z) - (p2.normal.x * p1.normal.z); +#endif + origin = new Vector3(((p1.normal.z * p2.distance) - (p2.normal.z * p1.distance)) / denom, + 0, + ((p2.normal.x * p1.distance) - (p1.normal.x * p2.distance)) / denom); + } else { +#if UNITY + float denom = (p1.normal.x * p2.normal.y) - (p2.normal.x * p1.normal.y); +#else + double denom = (p1.normal.x * p2.normal.y) - (p2.normal.x * p1.normal.y); +#endif + origin = new Vector3(((p1.normal.y * p2.distance) - (p2.normal.y * p1.distance)) / denom, + ((p2.normal.x * p1.distance) - (p1.normal.x * p2.distance)) / denom, + 0); + } + + return new Ray(origin, direction); + } + + /// + /// Intersects this Plane with another Plane at a Ray. Returns NaN for all components of both Vector3s of the Ray if the Planes are parallel. + /// + /// This Plane + /// Plane to intersect + /// Line of intersection where this Plane intersects "", ((NaN, NaN, NaN) + p(NaN, NaN, NaN)) otherwise + public static Ray Intersect(this Plane p1, Plane p2) { + return Intersection(p1, p2); + } + } +} diff --git a/LibBSP/Extensions/RectExtensions.cs b/LibBSP/Extensions/RectExtensions.cs new file mode 100644 index 0000000..984491e --- /dev/null +++ b/LibBSP/Extensions/RectExtensions.cs @@ -0,0 +1,56 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector2 = Vector2d; +#endif + /// + /// Static class containing helper methods for Rect objects. + /// + public static class RectExtensions { + + // Determines if this node's partition vector (as a line segment) intersects the passed box. + // Seems rather esoteric, no? But it's needed. Algorithm adapted from top answer at + // http://stackoverflow.com/questions/99353/how-to-test-if-a-line-segment-intersects-an-axis-aligned-rectange-in-2d + /// + /// Determines if this Rect is intersected by the line segment defined by and . + /// + /// This Rect + /// First point defining the line segment + /// The change and x and y for the coordinates of the tail + /// true if the line segment intersects this Rect at any point + public static bool IntersectsSegment(Rect rect, Vector2 head, Vector2 tail) { + // Compute the signed distance from the line to each corner of the box + double[] dist = new double[4]; + double x1 = head.x; + double x2 = head.x + tail.x; + double y1 = head.y; + double y2 = head.y + tail.y; + dist[0] = tail.y * rect.min.x + tail.x * rect.min.y + (x2 * y1 - x1 * y2); + dist[1] = tail.y * rect.min.x + tail.x * rect.max.y + (x2 * y1 - x1 * y2); + dist[2] = tail.y * rect.max.x + tail.x * rect.min.y + (x2 * y1 - x1 * y2); + dist[3] = tail.y * rect.max.x + tail.x * rect.max.y + (x2 * y1 - x1 * y2); + if (dist[0] >= 0 && dist[1] >= 0 && dist[2] >= 0 && dist[3] >= 0) { + return false; + } else { + if (dist[0] <= 0 && dist[1] <= 0 && dist[2] <= 0 && dist[3] <= 0) { + return false; + } else { + // If we get to this point, the line intersects the box. Figure out if the line SEGMENT actually cuts it. + if ((x1 > rect.max.x && x2 > rect.max.x) || (x1 < rect.min.x && x2 < rect.min.x) || (y1 > rect.max.y && y2 > rect.max.y) || (y1 < rect.min.y && y2 < rect.min.y)) { + return false; + } else { + return true; + } + } + } + } + + } +} diff --git a/LibBSP/Extensions/StringExtensions.cs b/LibBSP/Extensions/StringExtensions.cs new file mode 100644 index 0000000..2f0d279 --- /dev/null +++ b/LibBSP/Extensions/StringExtensions.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibBSP { + /// + /// Static class containing helper methods for string objects + /// + public static class StringExtensions { + /// + /// Splits a string using a Unicode character, unless that character is between two instances of a container. + /// + /// The string to split + /// Unicode character that delimits the substrings in this instance + /// Container character. Any characters that occur between two instances of this character will be ignored + /// Array of string objects that are the resulting substrings + public static string[] SplitUnlessInContainer(this string st, char separator, char container, StringSplitOptions options = StringSplitOptions.None) { + List results = new List(); + bool inContainer = false; + StringBuilder current = new StringBuilder(); + foreach (char c in st) { + if (c == container) { + inContainer = !inContainer; + continue; + } + + if (!inContainer) { + if (c == separator) { + switch (options) { + case StringSplitOptions.RemoveEmptyEntries: { + if (current.Length > 0) { + results.Add(current.ToString()); + } + current.Length = 0; + break; + } + case StringSplitOptions.None: { + results.Add(current.ToString()); + current.Length = 0; + break; + } + } + } else { + current.Append(c); + } + } else { + current.Append(c); + } + } + + if (current.Length > 0) { + results.Add(current.ToString()); + } + + return results.ToArray(); + } + + /// + /// Splits a string using a Unicode character, unless that character is contained between matching and Unicode characters. + /// + /// The string to split + /// Unicode character that delimits the substrings in this instance + /// The starting (left) container character. EX: '(' + /// The ending (right) container character. EX: ')' + /// Array of string objects that are the resulting substrings + public static string[] SplitUnlessBetweenDelimiters(this string st, char separator, char start, char end, StringSplitOptions options = StringSplitOptions.None) { + List results = new List(); + int containerLevel = 0; + StringBuilder current = new StringBuilder(); + foreach (char c in st) { + if (c == start) { + ++containerLevel; + if (containerLevel == 1) { + continue; + } + } + + if (c == end) { + --containerLevel; + if (containerLevel == 0) { + continue; + } + } + + if (containerLevel == 0) { + if (c == separator) { + switch (options) { + case StringSplitOptions.RemoveEmptyEntries: { + if (current.Length > 0) { + results.Add(current.ToString()); + } + current.Length = 0; + break; + } + case StringSplitOptions.None: { + results.Add(current.ToString()); + current.Length = 0; + break; + } + } + } else { + current.Append(c); + } + } else { + current.Append(c); + } + } + + if (current.Length > 0) { + results.Add(current.ToString()); + } + + return results.ToArray(); + } + + /// + /// Parses the bytes in a byte array into an ASCII string up until the first null byte (0x00). + /// + /// Bytes to parse + /// Position in the array to start copying from + /// Number of bytes to read before stopping. Negative values will read to the end of the array. + /// The resulting string + public static string ToNullTerminatedString(this byte[] bytes, int offset = 0, int length = -1) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; ++i) { + if (i > length && length >= 0) { + break; + } + if (bytes[i + offset] == 0) { + break; + } + sb.Append((char)bytes[i + offset]); + } + return sb.ToString(); + } + + /// + /// Parses the bytes in a byte array into an ASCII string. + /// + /// Bytes to parse + /// The resulting string + public static string ToRawString(this byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; ++i) { + sb.Append((char)bytes[i]); + } + return sb.ToString(); + } + } + +} diff --git a/LibBSP/Extensions/UIVertexExtensions.cs b/LibBSP/Extensions/UIVertexExtensions.cs new file mode 100644 index 0000000..39390c7 --- /dev/null +++ b/LibBSP/Extensions/UIVertexExtensions.cs @@ -0,0 +1,66 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Static class containing helper methods for UIVertex objects. + /// + public static class UIVertexExtensions { + + /// + /// Scales the position of this UIVertex by a number + /// + /// This UIVertex + /// Scalar + /// The scaled UIVertex + public static UIVertex Scale(this UIVertex v1, float scalar) { + v1.position *= scalar; + return v1; + } + + /// + /// Adds the position of this UIVertex to another UIVertex. + /// + /// This UIVertex + /// The other UIVertex + /// The resulting UIVertex + public static UIVertex Add(this UIVertex v1, UIVertex v2) { + return new UIVertex { + color = v1.color, + normal = v1.normal, + position = v1.position + v2.position, + tangent = v1.tangent, + uv0 = v1.uv0 + v2.uv0, + uv1 = v1.uv1 + v2.uv1 + }; + } + + /// + /// Adds the position of a Vector3 to this UIVertex. + /// + /// This UIVertex + /// The Vector3 + /// The resulting UIVertex + public static UIVertex Translate(this UIVertex v1, Vector3 v2) { + return new UIVertex { + color = v1.color, + normal = v1.normal, + position = v1.position + v2, + tangent = v1.tangent, + uv0 = v1.uv0, + uv1 = v1.uv1 + }; + } + + } +} diff --git a/LibBSP/LibBSP.csproj b/LibBSP/LibBSP.csproj new file mode 100644 index 0000000..63c0c43 --- /dev/null +++ b/LibBSP/LibBSP.csproj @@ -0,0 +1,98 @@ + + + + + Debug + AnyCPU + {33E4FFCA-8D0B-4089-AFD2-527547531578} + Library + Properties + LibBSP + libBSP + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibBSP/Properties/AssemblyInfo.cs b/LibBSP/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3be99f4 --- /dev/null +++ b/LibBSP/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LibBSP")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LibBSP")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("51d29a90-e476-4719-8960-7d178abce36d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LibBSP/Structs/BSP/BSP.cs b/LibBSP/Structs/BSP/BSP.cs new file mode 100644 index 0000000..919cc02 --- /dev/null +++ b/LibBSP/Structs/BSP/BSP.cs @@ -0,0 +1,158 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + public enum MapType : int { + Undefined = 0, + Quake = 29, + // TYPE_GOLDSRC = 30, // Uses same algorithm and structures as Quake + Nightfire = 42, + Vindictus = 346131372, + STEF2 = 556942937, + MOHAA = 892416069, + // TYPE_MOHBT = 1095516506, // Similar enough to MOHAA to use the same structures and algorithm + Doom = 1145132868, // "DWAD" + Hexen = 1145132872, // "HWAD" + STEF2Demo = 1263223129, + FAKK = 1263223152, + TacticalIntervention = 1268885814, + CoD2 = 1347633741, // Uses same algorithm and structures as COD1. Read differently. + SiN = 1347633747, // The headers for SiN and Jedi Outcast are exactly the same + Raven = 1347633748, + CoD4 = 1347633759, // Uses same algorithm and structures as COD1. Read differently. + Source17 = 1347633767, + Source18 = 1347633768, + Source19 = 1347633769, + Source20 = 1347633770, + Source21 = 1347633771, + Source22 = 1347633772, + Source23 = 1347633773, + Quake2 = 1347633775, + Source27 = 1347633777, + Daikatana = 1347633778, + SoF = 1347633782, // Uses the same header as Q3. + Quake3 = 1347633783, + // TYPE_RTCW = 1347633784, // Uses same algorithm and structures as Quake 3 + CoD = 1347633796, + DMoMaM = 1347895914, + } + + /// + /// Holds data for any and all BSP formats. Any unused lumps in a given format + /// will be left as null. Then it will be fed into a universal decompile method + /// which should be able to perform its job based on what data is stored. + /// + public class BSP { + + public MapType version { get; set; } + + public string filePath { get; private set; } + + // Map structures + // Quake 1/GoldSrc + public Entities entities; + public List planes; + public Textures textures; + public List vertices; + public List nodes; + public List texInfo; + public List faces; + public List leaves; + public NumList markSurfaces; + public List edges; + public NumList surfEdges; + public List models; + public byte[] pvs; + // Quake 2 + public List brushes; + public List brushSides; + public NumList markBrushes; + // MOHAA + // public MoHAAStaticProps staticProps; + // Nightfire + public Textures materials; + public NumList indices; + // Source + public List originalFaces; + public NumList texTable; + public List texDatas; + public List dispInfos; + public SourceDispVertices dispVerts; + public NumList displacementTriangles; + public SourceStaticProps staticProps; + public List cubemaps; + // public SourceOverlays overlays; + + /// + /// Gets the file name of this map. + /// + public string MapName { + get { + int i; + for (i = 0; i < filePath.Length; ++i) { + if (filePath[filePath.Length - 1 - i] == '\\') { + break; + } + if (filePath[filePath.Length - 1 - i] == '/') { + break; + } + } + return filePath.Substring(filePath.Length - i, (filePath.Length) - (filePath.Length - i)); + } + } + + /// + /// Gets the file name of this map without the ".BSP" extension. + /// + public string MapNameNoExtension { + get { + string name = MapName; + int i; + for (i = 0; i < name.Length; ++i) { + if (name[name.Length - 1 - i] == '.') { + break; + } + } + return name.Substring(0, (name.Length - 1 - i) - (0)); + } + } + + /// + /// Gets the folder path where this map is located. + /// + public string Folder { + get { + int i; + for (i = 0; i < filePath.Length; ++i) { + if (filePath[filePath.Length - 1 - i] == '\\') { + break; + } + if (filePath[filePath.Length - 1 - i] == '/') { + break; + } + } + return filePath.Substring(0, (filePath.Length - i) - (0)); + } + } + + /// + /// Creates a new BSP instance with no initial data. The Lists in this class must be populated elsewhere. + /// + /// The path to the .BSP file + /// The version of the map + public BSP(string filePath, MapType version) { + this.filePath = filePath; + this.version = version; + } + } +} diff --git a/LibBSP/Structs/BSP/Brush.cs b/LibBSP/Structs/BSP/Brush.cs new file mode 100644 index 0000000..8562dc4 --- /dev/null +++ b/LibBSP/Structs/BSP/Brush.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Holds the data used by the brush structures of all formats of BSP + /// + public struct Brush { + + public int firstSide { get; private set; } + public int numSides { get; private set; } + public int texture { get; private set; } + public int contents { get; private set; } + + /// + /// Creates a new Brush object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Brush(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + firstSide = -1; + numSides = -1; + texture = -1; + contents = -1; + switch (type) { + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SiN: + case MapType.SoF: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.Vindictus: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + firstSide = BitConverter.ToInt32(data, 0); + numSides = BitConverter.ToInt32(data, 4); + contents = BitConverter.ToInt32(data, 8); + break; + } + case MapType.Nightfire: { + contents = BitConverter.ToInt32(data, 0); + firstSide = BitConverter.ToInt32(data, 4); + numSides = BitConverter.ToInt32(data, 8); + break; + } + case MapType.MOHAA: + case MapType.STEF2Demo: + case MapType.Raven: + case MapType.Quake3: + case MapType.FAKK: { + firstSide = BitConverter.ToInt32(data, 0); + numSides = BitConverter.ToInt32(data, 4); + texture = BitConverter.ToInt32(data, 8); + break; + } + case MapType.STEF2: { + numSides = BitConverter.ToInt32(data, 0); + firstSide = BitConverter.ToInt32(data, 4); + texture = BitConverter.ToInt32(data, 8); + break; + } + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: { + numSides = BitConverter.ToInt16(data, 0); + texture = BitConverter.ToInt16(data, 2); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Brush class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Brush objects. + /// + /// The data to parse + /// The map type + /// A List of Brush objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SiN: + case MapType.SoF: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: + case MapType.Nightfire: + case MapType.STEF2: + case MapType.MOHAA: + case MapType.STEF2Demo: + case MapType.Raven: + case MapType.Quake3: + case MapType.FAKK: { + structLength = 12; + break; + } + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: { + structLength = 4; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Brush lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new Brush(bytes, type)); + } + return lump; + } + + } +} diff --git a/LibBSP/Structs/BSP/BrushSide.cs b/LibBSP/Structs/BSP/BrushSide.cs new file mode 100644 index 0000000..3f5b2f7 --- /dev/null +++ b/LibBSP/Structs/BSP/BrushSide.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Holds the data used by the brush side structures of all formats of BSP + /// + public struct BrushSide { + + public int plane { get; private set; } + public float dist { get; private set; } + public int texture { get; private set; } // -1 is a valid texture index in Quake 2. However it means "unused" there + public int face { get; private set; } + public int displacement { get; private set; } // In theory, this should always point to the side's displacement info. In practice, displacement brushes are removed on compile, leaving only the faces. + public bool bevel { get; private set; } + + /// + /// Creates a new BrushSide object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public BrushSide(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + plane = -1; + dist = -1; + texture = -1; + face = -1; + displacement = -1; + bevel = false; + switch (type) { + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: { + // Call of Duty's format sucks. The first field is either a float or an int + // depending on whether or not it's one of the first six sides in a brush. + // Store both possibilities, and the algorithm will determine which to use. + dist = BitConverter.ToSingle(data, 0); + goto case MapType.Quake3; + } + case MapType.Quake3: + case MapType.FAKK: + case MapType.STEF2Demo: + case MapType.MOHAA: { + plane = BitConverter.ToInt32(data, 0); + texture = BitConverter.ToInt32(data, 4); + break; + } + case MapType.STEF2: { + texture = BitConverter.ToInt32(data, 0); + plane = BitConverter.ToInt32(data, 4); + break; + } + case MapType.Raven: { + plane = BitConverter.ToInt32(data, 0); + texture = BitConverter.ToInt32(data, 4); + face = BitConverter.ToInt32(data, 8); + break; + } + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + this.displacement = BitConverter.ToInt16(data, 4); + // In little endian format, this byte takes the least significant bits of a short + // and can therefore be used for all Source engine formats, including Portal 2. + this.bevel = data[6] > 0; + goto case MapType.SiN; + } + case MapType.SiN: + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SoF: { + plane = BitConverter.ToUInt16(data, 0); + texture = BitConverter.ToInt16(data, 2); + break; + } + case MapType.Vindictus: { + plane = BitConverter.ToInt32(data, 0); + texture = BitConverter.ToInt32(data, 4); + displacement = BitConverter.ToInt32(data, 8); + bevel = data[12] > 0; + break; + } + case MapType.Nightfire: { + face = BitConverter.ToInt32(data, 0); + plane = BitConverter.ToInt32(data, 4); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the BrushSide class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of BrushSide objects. + /// + /// The data to parse + /// The map type + /// A List of BrushSide objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SoF: { + structLength = 4; + break; + } + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: + case MapType.SiN: + case MapType.Nightfire: + case MapType.Quake3: + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: + case MapType.FAKK: { + structLength = 8; + break; + } + case MapType.MOHAA: + case MapType.Raven: { + structLength = 12; + break; + } + case MapType.Vindictus: { + structLength = 16; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the BrushSide lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; i++) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new BrushSide(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/Edge.cs b/LibBSP/Structs/BSP/Edge.cs new file mode 100644 index 0000000..94310ec --- /dev/null +++ b/LibBSP/Structs/BSP/Edge.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Holds all the data for an edge in a BSP map. Doubles as a subsector class for Doom maps, and has accessors for them. + /// + public class Edge { + + public int firstVertex { get; private set; } + public int secondVertex { get; private set; } + public int NumSegs { get { return firstVertex; } private set { firstVertex = value; } } + public int FirstSeg { get { return secondVertex; } private set { secondVertex = value; } } + + /// + /// Creates a new Edge object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Edge(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + switch (type) { + case MapType.Quake: + case MapType.SiN: + case MapType.Daikatana: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: + case MapType.Quake2: + case MapType.SoF: { + firstVertex = BitConverter.ToUInt16(data, 0); + secondVertex = BitConverter.ToUInt16(data, 2); + break; + } + case MapType.Vindictus: { + firstVertex = BitConverter.ToInt32(data, 0); + secondVertex = BitConverter.ToInt32(data, 4); + break; + } + case MapType.Doom: + case MapType.Hexen: { + firstVertex = BitConverter.ToUInt16(data, 0); + secondVertex = BitConverter.ToUInt16(data, 2); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Edge class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Edge objects. + /// + /// The data to parse + /// The map type + /// A List of Edge objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake: + case MapType.SiN: + case MapType.Daikatana: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: + case MapType.Quake2: + case MapType.SoF: { + structLength = 4; + break; + } + case MapType.Vindictus: { + structLength = 8; + break; + } + case MapType.Doom: + case MapType.Hexen: { + structLength = 4; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Edge lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new Edge(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/Face.cs b/LibBSP/Structs/BSP/Face.cs new file mode 100644 index 0000000..2ea2d17 --- /dev/null +++ b/LibBSP/Structs/BSP/Face.cs @@ -0,0 +1,244 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector2 = Vector2d; +#endif + + /// + /// Holds all the data for a face in a BSP map. + /// + /// + /// Faces is one of the more different lumps between versions. Some of these fields + /// are only used by one format. However, there are some commonalities which make + /// it worthwhile to unify these. All formats use a plane, a texture, and vertices + /// in some way. Also (unused for the decompiler) they all use lightmaps. + /// + public struct Face { + + public int plane { get; private set; } + public int side { get; private set; } + public int firstEdge { get; private set; } + public int numEdges { get; private set; } + public int texture { get; private set; } + public int firstVertex { get; private set; } + public int numVertices { get; private set; } + public int material { get; private set; } + public int textureScale { get; private set; } + public int displacement { get; private set; } + public int original { get; private set; } + public int flags { get; private set; } + public int firstIndex { get; private set; } + public int numIndices { get; private set; } + public int unknown { get; private set; } + public int lightStyles { get; private set; } + public int lightMaps { get; private set; } + public Vector2 patchSize { get; private set; } + + /// + /// Creates a new Face object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Face(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + plane = -1; + side = -1; + firstEdge = -1; + numEdges = -1; + texture = -1; + firstVertex = -1; + numVertices = -1; + material = -1; + textureScale = -1; + displacement = -1; + original = -1; + flags = -1; + firstIndex = -1; + numIndices = -1; + unknown = -1; + lightStyles = -1; + lightMaps = -1; + patchSize = new Vector2(Single.NaN, Single.NaN); + switch (type) { + case MapType.Quake: + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SiN: + case MapType.SoF: { + plane = BitConverter.ToUInt16(data, 0); + side = BitConverter.ToUInt16(data, 2); + firstEdge = BitConverter.ToInt32(data, 4); + numEdges = BitConverter.ToUInt16(data, 8); + texture = BitConverter.ToUInt16(data, 10); + break; + } + case MapType.Quake3: + case MapType.Raven: + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.MOHAA: + case MapType.FAKK: { + texture = BitConverter.ToInt32(data, 0); + flags = BitConverter.ToInt32(data, 8); + firstVertex = BitConverter.ToInt32(data, 12); + numVertices = BitConverter.ToInt32(data, 16); + firstIndex = BitConverter.ToInt32(data, 20); + numIndices = BitConverter.ToInt32(data, 24); + patchSize = new Vector2(BitConverter.ToInt32(data, 96), BitConverter.ToInt32(data, 100)); + break; + } + case MapType.Source17: { + plane = BitConverter.ToUInt16(data, 32); + side = (int)data[34]; + firstEdge = BitConverter.ToInt32(data, 36); + numEdges = BitConverter.ToUInt16(data, 40); + textureScale = BitConverter.ToUInt16(data, 42); + displacement = BitConverter.ToInt16(data, 44); + original = BitConverter.ToInt32(data, 96); + break; + } + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + plane = BitConverter.ToUInt16(data, 0); + side = (int)data[2]; + firstEdge = BitConverter.ToInt32(data, 4); + numEdges = BitConverter.ToUInt16(data, 8); + textureScale = BitConverter.ToUInt16(data, 10); + displacement = BitConverter.ToInt16(data, 12); + original = BitConverter.ToInt32(data, 44); + break; + } + case MapType.Vindictus: { + plane = BitConverter.ToInt32(data, 0); + side = (int)data[4]; + firstEdge = BitConverter.ToInt32(data, 8); + numEdges = BitConverter.ToInt32(data, 12); + textureScale = BitConverter.ToInt32(data, 16); + displacement = BitConverter.ToInt32(data, 20); + original = BitConverter.ToInt32(data, 56); + break; + } + case MapType.Nightfire: { + plane = BitConverter.ToInt32(data, 0); + firstVertex = BitConverter.ToInt32(data, 4); + numVertices = BitConverter.ToInt32(data, 8); + firstIndex = BitConverter.ToInt32(data, 12); + numIndices = BitConverter.ToInt32(data, 16); + flags = BitConverter.ToInt32(data, 20); + texture = BitConverter.ToInt32(data, 24); + material = BitConverter.ToInt32(data, 28); + textureScale = BitConverter.ToInt32(data, 32); + unknown = BitConverter.ToInt32(data, 36); + lightStyles = BitConverter.ToInt32(data, 40); + lightMaps = BitConverter.ToInt32(data, 44); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Face class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Face objects. + /// + /// The data to parse + /// The map type + /// A List of Face objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake: + case MapType.Quake2: + case MapType.Daikatana: { + structLength = 20; + break; + } + case MapType.SiN: { + structLength = 36; + break; + } + case MapType.SoF: { + structLength = 40; + break; + } + case MapType.Nightfire: { + structLength = 48; + break; + } + case MapType.Source17: { + structLength = 104; + break; + } + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + structLength = 56; + break; + } + case MapType.Vindictus: { + structLength = 72; + break; + } + case MapType.Quake3: { + structLength = 104; + break; + } + case MapType.MOHAA: { + structLength = 108; + break; + } + case MapType.STEF2: + case MapType.STEF2Demo: { + structLength = 132; + break; + } + case MapType.Raven: { + structLength = 148; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Face lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new Face(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/Leaf.cs b/LibBSP/Structs/BSP/Leaf.cs new file mode 100644 index 0000000..3ffdd64 --- /dev/null +++ b/LibBSP/Structs/BSP/Leaf.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Holds data for a leaf structure in a BSP map + /// + public struct Leaf { + + public int contents { get; private set; } + public int firstMarkBrush { get; private set; } + public int numMarkBrushes { get; private set; } + public int firstMarkFace { get; private set; } + public int numMarkFaces { get; private set; } + public int pvs { get; private set; } + + /// + /// Creates a new Leaf object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Leaf(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + contents = -1; + firstMarkBrush = -1; + numMarkBrushes = -1; + firstMarkFace = -1; + numMarkFaces = -1; + pvs = -1; + switch (type) { + case MapType.SoF: { + contents = BitConverter.ToInt32(data, 0); + firstMarkFace = BitConverter.ToUInt16(data, 22); + numMarkFaces = BitConverter.ToUInt16(data, 24); + firstMarkBrush = BitConverter.ToUInt16(data, 26); + numMarkBrushes = BitConverter.ToUInt16(data, 28); + break; + } + case MapType.Quake2: + case MapType.SiN: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: + case MapType.Daikatana: { + contents = BitConverter.ToInt32(data, 0); + firstMarkBrush = BitConverter.ToUInt16(data, 24); + numMarkBrushes = BitConverter.ToUInt16(data, 26); + goto case MapType.Quake; + } + case MapType.Quake: { + firstMarkFace = BitConverter.ToUInt16(data, 20); + numMarkFaces = BitConverter.ToUInt16(data, 22); + break; + } + case MapType.Vindictus: { + contents = BitConverter.ToInt32(data, 0); + firstMarkFace = BitConverter.ToInt32(data, 36); + numMarkFaces = BitConverter.ToInt32(data, 40); + firstMarkBrush = BitConverter.ToInt32(data, 44); + numMarkBrushes = BitConverter.ToInt32(data, 48); + break; + } + case MapType.Nightfire: { + contents = BitConverter.ToInt32(data, 0); + pvs = BitConverter.ToInt32(data, 4); + goto case MapType.Raven; + } + case MapType.Quake3: + case MapType.FAKK: + case MapType.STEF2Demo: + case MapType.STEF2: + case MapType.MOHAA: + case MapType.Raven: { + firstMarkFace = BitConverter.ToInt32(data, 32); + numMarkFaces = BitConverter.ToInt32(data, 36); + firstMarkBrush = BitConverter.ToInt32(data, 40); + numMarkBrushes = BitConverter.ToInt32(data, 44); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Leaf class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Leaf objects. + /// + /// The data to parse + /// The map type + /// A List of Leaf objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake: + case MapType.Quake2: + case MapType.SiN: { + structLength = 28; + break; + } + case MapType.Source17: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.SoF: + case MapType.Daikatana: + case MapType.DMoMaM: { + structLength = 32; + break; + } + case MapType.Vindictus: { + structLength = 56; + break; + } + case MapType.CoD: { + structLength = 36; + break; + } + case MapType.Nightfire: + case MapType.Quake3: + case MapType.FAKK: + case MapType.STEF2Demo: + case MapType.STEF2: + case MapType.Raven: { + structLength = 48; + break; + } + case MapType.Source18: + case MapType.Source19: { + structLength = 56; + break; + } + case MapType.MOHAA: { + structLength = 64; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Leaf lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new Leaf(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/Lumps/SourceDispVertices.cs b/LibBSP/Structs/BSP/Lumps/SourceDispVertices.cs new file mode 100644 index 0000000..90cd467 --- /dev/null +++ b/LibBSP/Structs/BSP/Lumps/SourceDispVertices.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Class representing a group of SourceDispVertex objects. Contains helpful methods to handle Displacement Vertices in the List + /// + public class SourceDispVertices : List { + + /// + /// Parses the passed byte array into a List of SourceDispVertices + /// + /// Array of bytes to parse + /// Format identifier + /// was null + public SourceDispVertices(byte[] data, MapType type) : base(data.Length / 20) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 20; + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + Add(new SourceDispVertex(bytes, type)); + } + } + + /// + /// Gets enough vertices from the list for a displacement of power , starting at . + /// + /// The first vertex to get + /// The power of the displacement + /// Array of SourceDispVertex objects containing all the vertices in this displacement + public virtual SourceDispVertex[] GetVertsInDisp(int first, int power) { + int numVerts = 0; + switch (power) { + case 2: { + numVerts = 25; + break; + } + case 3: { + numVerts = 81; + break; + } + case 4: { + numVerts = 289; + break; + } + } + SourceDispVertex[] ret = new SourceDispVertex[numVerts]; + for (int i = 0; i < numVerts; ++i) { + ret[i] = this[first + i]; + } + return ret; + } + + } +} diff --git a/LibBSP/Structs/BSP/Lumps/SourceStaticProps.cs b/LibBSP/Structs/BSP/Lumps/SourceStaticProps.cs new file mode 100644 index 0000000..01ba8ed --- /dev/null +++ b/LibBSP/Structs/BSP/Lumps/SourceStaticProps.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// List of SourceStaticProp objects containing data relevant to Static Props, like the dictionary of actual model paths. + /// + public class SourceStaticProps : List { + + public string[] dictionary { get; private set; } + + /// + /// Parses the passed byte array into a List of SourceStaticProp objects + /// + /// Array of bytes to parse + /// Format identifier + /// Version of static prop lump this is + /// was null + public SourceStaticProps(byte[] data, MapType type, int version) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + string[] dictionary = new string[0]; + if (data.Length > 0) { + int offset = 0; + dictionary = new string[BitConverter.ToInt32(data, 0)]; + for (int i = 0; i < dictionary.Length; ++i) { + byte[] temp = new byte[128]; + Array.Copy(data, (i * 128) + 4, temp, 0, 128); + dictionary[i] = temp.ToNullTerminatedString(); + } + int numLeafDefinitions = BitConverter.ToInt32(data, (dictionary.Length * 128) + 4); + int numProps = BitConverter.ToInt32(data, (dictionary.Length * 128) + (numLeafDefinitions * 2) + 8); + if (numProps > 0) { + structLength = (data.Length - ((dictionary.Length * 128) + (numLeafDefinitions * 2) + 12)) / numProps; + byte[] bytes = new byte[structLength]; + for (int i = 0; i < numProps; ++i) { + Array.Copy(data, (dictionary.Length * 128) + (numLeafDefinitions * 2) + 12 + (i * structLength), bytes, 0, structLength); + Add(new SourceStaticProp(bytes, type, version)); + offset += structLength; + } + } + } + } + } +} diff --git a/LibBSP/Structs/BSP/Lumps/Textures.cs b/LibBSP/Structs/BSP/Lumps/Textures.cs new file mode 100644 index 0000000..a928d5b --- /dev/null +++ b/LibBSP/Structs/BSP/Lumps/Textures.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// List<Texture> with some useful methods for manipulating Texture objects, + /// especially when handling them as a group. + /// + public class Textures : List { + + /// + /// Parses a byte array into this List of Texture objects + /// + /// The data to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Textures(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Nightfire: { + structLength = 64; + break; + } + case MapType.Quake3: + case MapType.Raven: + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: { + structLength = 72; + break; + } + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SoF: + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.FAKK: { + structLength = 76; + break; + } + case MapType.MOHAA: { + structLength = 140; + break; + } + case MapType.SiN: { + structLength = 180; + break; + } + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: { + int offset = 0; + byte[] bytes; + for (int i = 0; i < data.Length; ++i) { + if (data[i] == (byte)0x00) { + // They are null-terminated strings, of non-constant length (not padded) + bytes = new byte[i - offset - 1]; + Array.Copy(data, offset, bytes, 0, i - offset - 1); + Add(new Texture(bytes, type)); + } + } + return; + } + case MapType.Quake: { + int numElements = BitConverter.ToInt32(data, 0); + structLength = 40; + byte[] bytes = new byte[structLength]; + for (int i = 0; i < numElements; ++i) { + Array.Copy(data, BitConverter.ToInt32(data, (i + 1) * 4), bytes, 0, structLength); + Add(new Texture(bytes, type)); + } + return; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Node class."); + } + } + + { + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + Add(new Texture(bytes, type)); + } + } + } + + /// + /// Gets the name of the texture at the specified offset. + /// + /// Lump offset of the texture name to find + /// The name of the texture at offset , or null if it doesn't exist + public string GetTextureAtOffset(uint offset) { + int current = 0; + for (int i = 0; i < Count; ++i) { + if (current < offset) { + // Add 1 for the missing null byte. + current += this[i].name.Length + 1; + } else { + return this[i].name; + } + } + // If we get to this point, the strings ended before target offset was reached + return null; + } + + /// + /// Finds the offset of the specified texture name. + /// + /// The texture name to find in the lump + /// The offset of the specified texture, or -1 if it wasn't found + public int GetOffsetOf(string inTexture) { + int offset = 0; + for (int i = 0; i < Count; ++i) { + if (this[i].name.Equals(inTexture, StringComparison.CurrentCultureIgnoreCase)) { + return offset; + } else { + offset += this[i].name.Length + 1; + } + } + // If we get here, the requested texture didn't exist. + return -1; + } + } +} diff --git a/LibBSP/Structs/BSP/Model.cs b/LibBSP/Structs/BSP/Model.cs new file mode 100644 index 0000000..f6ffdbf --- /dev/null +++ b/LibBSP/Structs/BSP/Model.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// An attempt at an all-encompassing model class containing all data needed for any + /// given models lump in any given BSP. + /// + /// + /// In general, we need to use models to find one or more leaves containing the + /// information for the solids described by this model. Some formats do it by + /// referencing a head node to iterate through and find the leaves. Others + /// directly point to a set of leaves, and still others simply directly reference + /// brushes. The ideal format simply points to brush information from here (Quake + /// 3-based engines do), but most of them don't. + /// + public struct Model { + + public int headNode { get; private set; } // Quake, Half-life, Quake 2, SiN + public int firstLeaf { get; private set; } // 007 nightfire + public int numLeaves { get; private set; } + public int firstBrush { get; private set; } // Quake 3 and derivatives + public int numBrushes { get; private set; } + public int firstFace { get; private set; } // Quake/GoldSrc + public int numFaces { get; private set; } + + /// + /// Creates a new Model object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Model(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + headNode = -1; + firstLeaf = -1; + numLeaves = -1; + firstBrush = -1; + numBrushes = -1; + firstFace = -1; + numFaces = -1; + switch (type) { + case MapType.Quake: { + headNode = BitConverter.ToInt32(data, 36); + firstFace = BitConverter.ToInt32(data, 56); + numFaces = BitConverter.ToInt32(data, 60); + break; + } + case MapType.Quake2: + case MapType.Daikatana: + case MapType.SiN: + case MapType.SoF: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: { + headNode = BitConverter.ToInt32(data, 36); + firstFace = BitConverter.ToInt32(data, 40); + numFaces = BitConverter.ToInt32(data, 44); + break; + } + case MapType.DMoMaM: { + headNode = BitConverter.ToInt32(data, 40); + firstFace = BitConverter.ToInt32(data, 44); + numFaces = BitConverter.ToInt32(data, 48); + break; + } + case MapType.Nightfire: { + firstLeaf = BitConverter.ToInt32(data, 40); + numLeaves = BitConverter.ToInt32(data, 44); + firstFace = BitConverter.ToInt32(data, 48); + numFaces = BitConverter.ToInt32(data, 52); + break; + } + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.Quake3: + case MapType.MOHAA: + case MapType.Raven: + case MapType.FAKK: { + firstFace = BitConverter.ToInt32(data, 24); + numFaces = BitConverter.ToInt32(data, 28); + firstBrush = BitConverter.ToInt32(data, 32); + numBrushes = BitConverter.ToInt32(data, 36); + break; + } + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: { + firstBrush = BitConverter.ToInt32(data, 40); + numBrushes = BitConverter.ToInt32(data, 44); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Model class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Model objects. + /// + /// The data to parse + /// The map type + /// A List of Model objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake3: + case MapType.Raven: + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.MOHAA: + case MapType.FAKK: { + structLength = 40; + break; + } + case MapType.Quake2: + case MapType.Daikatana: + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: + case MapType.SiN: + case MapType.SoF: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: { + structLength = 48; + break; + } + case MapType.DMoMaM: { + structLength = 52; + break; + } + case MapType.Nightfire: { + structLength = 56; + break; + } + case MapType.Quake: { + structLength = 64; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Leaf lump factory."); + } + } + int offset = 0; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new Model(bytes, type)); + offset += structLength; + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/Node.cs b/LibBSP/Structs/BSP/Node.cs new file mode 100644 index 0000000..117afb8 --- /dev/null +++ b/LibBSP/Structs/BSP/Node.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Contains all data needed for a node in a BSP tree. + /// + public struct Node { + + public int plane { get; private set; } + public int child1 { get; private set; } // Negative values are valid here. However, the child can never be zero, + public int child2 { get; private set; } // since that would reference the head node causing an infinite loop. + + /// + /// Creates a new Node object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Node(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + plane = BitConverter.ToInt32(data, 0); // All formats I've seen use the first 4 bytes as an int, plane index + switch (type) { + case MapType.Quake: { + this.child1 = BitConverter.ToInt16(data, 4); + this.child2 = BitConverter.ToInt16(data, 6); + break; + } + // These all use the first three ints for planenum and children + case MapType.SiN: + case MapType.SoF: + case MapType.Quake2: + case MapType.Daikatana: + case MapType.Nightfire: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: + case MapType.STEF2: + case MapType.MOHAA: + case MapType.STEF2Demo: + case MapType.Raven: + case MapType.Quake3: + case MapType.FAKK: + case MapType.CoD: { + this.child1 = BitConverter.ToInt32(data, 4); + this.child2 = BitConverter.ToInt32(data, 8); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Node class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Node objects. + /// + /// The data to parse + /// The map type + /// A List of Node objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake: { + structLength = 24; + break; + } + case MapType.Quake2: + case MapType.SiN: + case MapType.SoF: + case MapType.Daikatana: { + structLength = 28; + break; + } + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + structLength = 32; + break; + } + case MapType.Vindictus: { + structLength = 48; + break; + } + case MapType.Quake3: + case MapType.FAKK: + case MapType.CoD: + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.MOHAA: + case MapType.Raven: + case MapType.Nightfire: { + structLength = 36; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Node lump factory."); + } + } + int offset = 0; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new Node(bytes, type)); + offset += structLength; + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/SourceCubemap.cs b/LibBSP/Structs/BSP/SourceCubemap.cs new file mode 100644 index 0000000..5aad21c --- /dev/null +++ b/LibBSP/Structs/BSP/SourceCubemap.cs @@ -0,0 +1,100 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Holds all data for a Cubemap from Source engine. + /// + public struct SourceCubemap { + + public Vector3 origin { get; private set; } + public int size { get; private set; } + + /// + /// Creates a new SourceCubemap object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public SourceCubemap(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + switch (type) { + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: { + origin = new Vector3(BitConverter.ToInt32(data, 0), BitConverter.ToInt32(data, 4), BitConverter.ToInt32(data, 8)); + size = BitConverter.ToInt32(data, 12); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the SourceCubemap class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of SourceCubemap objects. + /// + /// The data to parse + /// The map type + /// A List of SourceCubemap objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: { + structLength = 16; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the SourceCubemap lump factory."); + } + } + int offset = 0; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new SourceCubemap(bytes, type)); + offset += structLength; + } + return lump; + } + + } +} diff --git a/LibBSP/Structs/BSP/SourceDispInfo.cs b/LibBSP/Structs/BSP/SourceDispInfo.cs new file mode 100644 index 0000000..7bea70c --- /dev/null +++ b/LibBSP/Structs/BSP/SourceDispInfo.cs @@ -0,0 +1,128 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Holds all data for a Displacement from Source engine. + /// + public struct SourceDispInfo { + + public Vector3 startPosition { get; private set; } + public int dispVertStart { get; private set; } + //public int dispTriStart { get; private set; } + public int power { get; private set; } + public uint[] allowedVerts { get; private set; } + + /// + /// Creates a new SourceDispInfo object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public SourceDispInfo(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + startPosition = new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)); + dispVertStart = BitConverter.ToInt32(data, 12); + //dispTriStart = BitConverter.ToInt32(in, 16); + power = BitConverter.ToInt32(data, 20); + allowedVerts = new uint[10]; + int offset = 0; + switch (type) { + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + offset = 136; + break; + } + case MapType.Source22: { + offset = 140; + break; + } + case MapType.Source23: { + offset = 144; + break; + } + case MapType.Vindictus: { + offset = 192; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the SourceDispInfo class."); + } + } + for (int i = 0; i < 10; ++i) { + allowedVerts[i] = BitConverter.ToUInt32(data, offset + (i * 4)); + } + } + + /// + /// Factory method to parse a byte array into a List of SourceDispInfo objects. + /// + /// The data to parse + /// The map type + /// A List of SourceDispInfo objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.DMoMaM: { + structLength = 176; + break; + } + case MapType.Source22: { + structLength = 180; + break; + } + case MapType.Source23: { + structLength = 184; + break; + } + case MapType.Vindictus: { + structLength = 232; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the SourceDispInfo lump factory."); + } + } + int offset = 0; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new SourceDispInfo(bytes, type)); + offset += structLength; + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/SourceDispVertex.cs b/LibBSP/Structs/BSP/SourceDispVertex.cs new file mode 100644 index 0000000..6d6e936 --- /dev/null +++ b/LibBSP/Structs/BSP/SourceDispVertex.cs @@ -0,0 +1,53 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Holds all the data for a displacement in a Source map. + /// + public struct SourceDispVertex { + + public Vector3 normal { get; private set; } // The normalized vector direction this vertex points from "flat" + public float dist { get; private set; } // Magnitude of normal, before normalization + public float alpha { get; private set; } // Alpha value of texture at this vertex + + /// + /// Creates a new SourceDispVertex object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + public SourceDispVertex(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + this.normal = new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)); + this.dist = BitConverter.ToSingle(data, 12); + this.alpha = BitConverter.ToSingle(data, 16); + } + + /// + /// Factory method to parse a byte array into a SourceDispVertices object. + /// + /// The data to parse + /// The map type + /// A SourceDispVertices object + /// was null + public static SourceDispVertices LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + return new SourceDispVertices(data, type); + } + } +} diff --git a/LibBSP/Structs/BSP/SourceStaticProp.cs b/LibBSP/Structs/BSP/SourceStaticProp.cs new file mode 100644 index 0000000..bd9691a --- /dev/null +++ b/LibBSP/Structs/BSP/SourceStaticProp.cs @@ -0,0 +1,116 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Handles the data needed for one static prop. + /// + public struct SourceStaticProp { + + public Vector3 origin { get; private set; } + public Vector3 angles { get; private set; } + public short dictionaryEntry { get; private set; } + public byte solidity { get; private set; } + public byte flags { get; private set; } + public int skin { get; private set; } + public float minFadeDist { get; private set; } + public float maxFadeDist { get; private set; } + public float forcedFadeScale { get; private set; } + public string targetname { get; private set; } + + /// + /// Creates a new SourceStaticProp object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public SourceStaticProp(byte[] data, MapType type, int version) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + origin = Vector3.zero; + angles = Vector3.zero; + dictionaryEntry = 0; + solidity = 0; + flags = 0; + skin = 0; + minFadeDist = 0; + maxFadeDist = 0; + forcedFadeScale = 1; + targetname = null; + switch (type) { + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: { + switch (version) { + case 5: { + if (data.Length == 188) { + // This is only for The Ship or Bloody Good Time. + byte[] targetnameBytes = new byte[128]; + Array.Copy(data, 60, targetnameBytes, 0, 128); + targetname = targetnameBytes.ToNullTerminatedString(); + if (targetname.Length == 0) { + targetname = null; + } + } + goto case 6; + } + case 6: + case 7: + case 8: + case 9: + case 10: { + forcedFadeScale = BitConverter.ToSingle(data, 56); + goto case 4; + } + case 4: { + origin = new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)); + origin = new Vector3(BitConverter.ToSingle(data, 12), BitConverter.ToSingle(data, 16), BitConverter.ToSingle(data, 20)); + dictionaryEntry = BitConverter.ToInt16(data, 24); + solidity = data[30]; + flags = data[31]; + skin = BitConverter.ToInt32(data, 32); + minFadeDist = BitConverter.ToSingle(data, 36); + maxFadeDist = BitConverter.ToSingle(data, 40); + break; + } + } + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the SourceStaticProp class."); + } + } + } + + /// + /// Factory method to create a SourceStaticProps object. + /// + /// The data to parse + /// The map type + /// The version of the Static Prop lump + /// A SourceStaticProps object + public static SourceStaticProps LumpFactory(byte[] data, MapType type, int version) { + return new SourceStaticProps(data, type, version); + } + } +} diff --git a/LibBSP/Structs/BSP/SourceTexData.cs b/LibBSP/Structs/BSP/SourceTexData.cs new file mode 100644 index 0000000..849c2de --- /dev/null +++ b/LibBSP/Structs/BSP/SourceTexData.cs @@ -0,0 +1,67 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + + /// + /// Contains all the information for a single SourceTexData object + /// + public struct SourceTexData { + + public Vector3 reflectivity { get; private set; } + public int stringTableIndex { get; private set; } + public int width { get; private set; } + public int height { get; private set; } + public int view_width { get; private set; } + public int view_height { get; private set; } + + /// + /// Creates a new SourceTexData object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + public SourceTexData(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + reflectivity = new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)); + stringTableIndex = BitConverter.ToInt32(data, 12); + width = BitConverter.ToInt32(data, 16); + height = BitConverter.ToInt32(data, 20); + view_width = BitConverter.ToInt32(data, 24); + view_height = BitConverter.ToInt32(data, 28); + } + + /// + /// Factory method to parse a byte array into a List of SourceTexData objects. + /// + /// The data to parse + /// The map type + /// A List of SourceTexData objects + /// was null + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 32; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; i++) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new SourceTexData(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/TexInfo.cs b/LibBSP/Structs/BSP/TexInfo.cs new file mode 100644 index 0000000..408d6bd --- /dev/null +++ b/LibBSP/Structs/BSP/TexInfo.cs @@ -0,0 +1,186 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + + /// + /// This class contains the texture scaling information for certain formats. + /// Some BSP formats lack this lump (or it is contained in a different one) + /// so their cases will be left out. + /// + public class TexInfo { + + public const int S = 0; + public const int T = 1; + public static readonly Vector3[] baseAxes = new Vector3[] { new Vector3(0, 0, 1), new Vector3(1, 0, 0), new Vector3(0, -1, 0), + new Vector3(0, 0, -1), new Vector3(1, 0, 0), new Vector3(0, -1, 0), + new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1), + new Vector3(-1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1), + new Vector3(0, 1, 0), new Vector3(1, 0, 0), new Vector3(0, 0, -1), + new Vector3(0, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 0, -1) }; + + public Vector3[] axes { get; private set; } + public float[] shifts { get; private set; } + public int flags { get; private set; } + public int texture { get; private set; } + + /// + /// Creates a new TexInfo object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public TexInfo(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + axes = new Vector3[2]; + shifts = new float[2]; + axes[S] = new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)); + shifts[S] = BitConverter.ToSingle(data, 12); + axes[T] = new Vector3(BitConverter.ToSingle(data, 16), BitConverter.ToSingle(data, 20), BitConverter.ToSingle(data, 24)); + shifts[T] = BitConverter.ToSingle(data, 28); + switch (type) { + // Excluded engines: Quake 2-based, Quake 3-based + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: { + texture = BitConverter.ToInt32(data, 68); + flags = BitConverter.ToInt32(data, 64); + break; + } + case MapType.DMoMaM: { + texture = BitConverter.ToInt32(data, 92); + flags = BitConverter.ToInt32(data, 88); + break; + } + case MapType.Quake: { + texture = BitConverter.ToInt32(data, 32); + flags = BitConverter.ToInt32(data, 36); + break; + } + case MapType.Nightfire: { + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the TexInfo class."); + } + } + } + + /// + /// Creates a new TexInfo object using the passed data. + /// + /// The S texture axis + /// The texture shift on the S axis + /// The T texture axis + /// The texture shift on the T axis + /// The flags for this TexInfo + /// Index into the texture list for the texture this TexInfo uses + public TexInfo(Vector3 s, float SShift, Vector3 t, float TShift, int flags, int texture) { + axes = new Vector3[2]; + axes[S] = s; + axes[T] = t; + shifts = new float[2]; + shifts[S] = SShift; + shifts[T] = TShift; + this.flags = flags; + this.texture = texture; + } + + /// + /// Adapted from code in the Quake III Arena source code. Stolen without + /// permission because it falls under the terms of the GPL v2 license, because I'm not making + /// any money, just awesome tools. + /// + /// Plane of the surface + /// The best matching texture axes for the given Plane + public static Vector3[] TextureAxisFromPlane(Plane p) { + int bestaxis = 0; + double dot; // Current dot product + double best = 0; // "Best" dot product so far + for (int i = 0; i < 6; ++i) { + // For all possible axes, positive and negative + dot = Vector3.Dot(p.normal, new Vector3(baseAxes[i * 3][0], baseAxes[i * 3][1], baseAxes[i * 3][2])); + if (dot > best) { + best = dot; + bestaxis = i; + } + } + Vector3[] out_Renamed = new Vector3[2]; + out_Renamed[0] = new Vector3(baseAxes[bestaxis * 3 + 1][0], baseAxes[bestaxis * 3 + 1][1], baseAxes[bestaxis * 3 + 1][2]); + out_Renamed[1] = new Vector3(baseAxes[bestaxis * 3 + 2][0], baseAxes[bestaxis * 3 + 2][1], baseAxes[bestaxis * 3 + 2][2]); + return out_Renamed; + } + + /// + /// Factory method to parse a byte array into a List of TexInfo objects. + /// + /// The data to parse + /// The map type + /// A List of TexInfo objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Nightfire: { + structLength = 32; + break; + } + case MapType.Quake: { + structLength = 40; + break; + } + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: { + structLength = 72; + break; + } + case MapType.DMoMaM: { + structLength = 96; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Leaf lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, (i * structLength), bytes, 0, structLength); + lump.Add(new TexInfo(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/BSP/Texture.cs b/LibBSP/Structs/BSP/Texture.cs new file mode 100644 index 0000000..77edaaa --- /dev/null +++ b/LibBSP/Structs/BSP/Texture.cs @@ -0,0 +1,117 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + + /// + /// An all-encompassing class to handle the texture information of any given BSP format. + /// + /// + /// The way texture information is stored varies wildly between versions. As a general + /// rule, this class only handles the lump containing the string of a texture's name, + /// and data from within the lump associated with it. + /// For example, Nightfire's texture lump only contains 64-byte null-padded strings, but + /// Quake 2's has texture scaling included. + /// + public struct Texture { + + public string name { get; private set; } + public string mask { get; private set; } // Only used by MoHAA, "ignore" means it's unused + public int flags { get; private set; } + public int contents { get; private set; } + public TexInfo texAxes { get; private set; } + + /// + /// Creates a new Texture object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Texture(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + name = ""; + mask = "ignore"; + flags = 0; + contents = 0; + texAxes = null; + switch (type) { + case MapType.Quake: + case MapType.Nightfire: { + name = data.ToNullTerminatedString(); + break; + } + case MapType.Quake2: + case MapType.SoF: + case MapType.Daikatana: { + texAxes = new TexInfo(new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)), BitConverter.ToSingle(data, 12), new Vector3(BitConverter.ToSingle(data, 16), BitConverter.ToSingle(data, 20), BitConverter.ToSingle(data, 24)), BitConverter.ToSingle(data, 28), -1, -1); + flags = BitConverter.ToInt32(data, 32); + name = data.ToNullTerminatedString(40, 32); + break; + } + case MapType.MOHAA: { + mask = data.ToNullTerminatedString(76, 64); + goto case MapType.STEF2; + } + case MapType.STEF2: + case MapType.STEF2Demo: + case MapType.Raven: + case MapType.Quake3: + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: + case MapType.FAKK: { + name = data.ToNullTerminatedString(0, 64); + flags = BitConverter.ToInt32(data, 64); + contents = BitConverter.ToInt32(data, 68); + break; + } + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.TacticalIntervention: + case MapType.Vindictus: + case MapType.DMoMaM: { + name = data.ToRawString(); + break; + } + case MapType.SiN: { + texAxes = new TexInfo(new Vector3(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)), BitConverter.ToSingle(data, 12), new Vector3(BitConverter.ToSingle(data, 16), BitConverter.ToSingle(data, 20), BitConverter.ToSingle(data, 24)), BitConverter.ToSingle(data, 28), -1, -1); + flags = BitConverter.ToInt32(data, 32); + name = data.ToNullTerminatedString(36, 64); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Node class."); + } + } + } + + /// + /// Factory method to parse a byte array into a Textures object. + /// + /// The data to parse + /// The map type + /// A Textures object + public static Textures LumpFactory(byte[] data, MapType type) { + return new Textures(data, type); + } + } +} diff --git a/LibBSP/Structs/Common/Entity.cs b/LibBSP/Structs/Common/Entity.cs new file mode 100644 index 0000000..86c638a --- /dev/null +++ b/LibBSP/Structs/Common/Entity.cs @@ -0,0 +1,716 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; + using Vector4 = Vector4d; +#endif + /// + /// Class containing all data for a single Entity, including attributes, Source Entity I/O connections and solids. + /// + [Serializable] public class Entity : Dictionary, IComparable, IComparable { + + public const char ConnectionMemberSeparater = (char)0x1B; + public List connections = new List(); + public List brushes = new List(); + + /// + /// Gets whether this Entity is brush-based or not. + /// + public bool brushBased { get { return brushes.Count > 0 || modelNumber >= 0; } } + + /// + /// Wrapper for the "spawnflags" attribute. + /// + public uint spawnflags { + get { + try { + if (ContainsKey("spawnflags")) { + return System.UInt32.Parse(this["spawnflags"]); + } else { + return 0; + } + } catch { + return 0; + } + } + set { this["spawnflags"] = value.ToString(); } + } + + /// + /// Wrapper for the "origin" attribute. + /// + public Vector3 origin { + get { return GetVector("origin"); } + set { this["origin"] = value.x + " " + value.y + " " + value.z; } + } + + /// + /// Wrapper for the "angles" attribute. + /// + public Vector3 angles { + get { return GetVector("angles"); } + set { this["angles"] = value.x + " " + value.y + " " + value.z; } + } + + /// + /// Wrapper for the "targetname" attribute. + /// + public string name { + get { + if (ContainsKey("targetname")) { + return this["targetname"]; + } else { + return ""; + } + } + set { this["targetname"] = value; } + } + + /// + /// Wrapper for the "classname" attribute. + /// + /// If an entity has no class, it has no behavior. It's either an error or metadata. + public string className { + get { + if (ContainsKey("classname")) { + return this["classname"]; + } else { + return ""; + } + } + set { this["classname"] = value; } + } + + /// + /// If there's a model number in the attributes list, this method fetches it + /// and returns it. If there is no model defined, or it's not a numerical + /// value, then -1 is returned. If it's the worldspawn then a 0 is returned. + /// + public int modelNumber { + get { + try { + if (this["classname"] == "worldspawn") { + return 0; + } else { + if (ContainsKey("model")) { + string st = this["model"]; + if (st[0] == '*') { + try { + return System.Int32.Parse(st.Substring(1)); + } catch (System.FormatException) { + return -1; + } + } else { + return -1; + } + } else { + return -1; + } + } + } catch { + return -1; + } + } + } + + /// + /// Allows an attribute to be accessed easily using Entity[""] notation. + /// If an attribute doesn't exist, it returns the empty string. This emulates the behavior of the engines. + /// + /// + /// It's up to the developer to ensure the empty string doesn't cause problems, rather than returning null! + /// + /// The attribute to retrieve + /// The value of the attribute if it exists, empty string otherwise + public new string this[string key] { + get { + if (ContainsKey(key)) { + return base[key]; + } else { + return string.Empty; + } + } + set { base[key] = value; } + } + + /// + /// Initializes a new instance of an Entity, parsing the given byte array into an Entity structure. + /// + /// Array to parse + public Entity(byte[] data, MapType type) : this(Encoding.ASCII.GetString(data).Split('\n')) { } + + /// + /// Initializes a new instance of an Entity with the given classname. + /// + /// Classname of the new Entity + public Entity(string className) : base(StringComparer.InvariantCultureIgnoreCase) { + Add("classname", className); + } + + /// + /// Initializes a new instance of an Entity object with no initial properties. + /// + public Entity() : base(StringComparer.InvariantCultureIgnoreCase) { } + + /// + /// Initializes a new instance of an Entity object, copying the attributes, connections and brushes of the passed Entity. + /// + /// The Entity to copy + public Entity(Entity copy) : base(copy, StringComparer.InvariantCultureIgnoreCase) { + connections = new List(copy.connections); + brushes = new List(copy.brushes); + } + + /// + /// Initializes a new instance of an Entity, parsing the given string array into an Entity structure. + /// + /// Array of attributes, patches, brushes, displacements etc. to parse + public Entity(string[] lines) : base(StringComparer.InvariantCultureIgnoreCase) { + int braceCount = 0; + + bool inConnections = false; + bool inBrush = false; + + List child = new List(); + + foreach (string line in lines) { + string current = line.Trim(' ', '\t', '\r'); + + // Cull everything after a // + bool inQuotes = false; + + for (int i = 0; i < current.Length; ++i) { + if (current[i] == '\"' && (i == 0 || current[i - 1] != '\\')) { + inQuotes = !inQuotes; + } + + if (!inQuotes && current[i] == '/' && i != 0 && current[i - 1] == '/') { + current = current.Substring(0, i - 1); + } + } + + if (string.IsNullOrEmpty(current)) { + continue; + } + + // Perhaps I should not assume these will always be the first thing on the line + if (current[0] == '{') { + // If we're only one brace deep, and we have no prior information, assume a brush + if (braceCount == 1 && !inBrush && !inConnections) { + inBrush = true; + } + ++braceCount; + } else if (current[0] == '}') { + --braceCount; + // If this is the end of an entity substructure + if (braceCount == 1) { + // If we determined we were inside a brush substructure + if (inBrush) { + child.Add(current); + brushes.Add(new MAPBrush(child.ToArray())); + child = new List(); + } + inBrush = false; + inConnections = false; + } else { + child.Add(current); + } + continue; + } else if (current.Length >= 5 && current.Substring(0, 5) == "solid") { + inBrush = true; + continue; + } else if (current.Length >= 11 && current.Substring(0, 11) == "connections") { + inConnections = true; + continue; + } + + if (inBrush) { + child.Add(current); + continue; + } + + Add(current); + } + } + + /// + /// Factory method to create an Entity from a string "" where + /// "" contains all lines for the entity, including attributes, brushes, etc. + /// + /// + /// This was necessary since the Entity(string) constructor was already used in a different way + /// + /// The data to parse + /// The resulting Entity object + public static Entity FromString(string st) { + return new Entity(st.Split('\n')); + } + + /// + /// Renames the attribute named "" to "". Replaces the old entry if it already exists. + /// + /// Attribute to be renamed + /// New name for this attribute + public void RenameKey(string oldName, string newName) { + if (ContainsKey(oldName)) { + string val = this[oldName]; + Remove(oldName); + if (ContainsKey(newName)) { + Remove(newName); + } + Add(newName, val); + } + } + + /// + /// Parses the input string "" into a key/value pair and adds + /// it as an attribute to this Entity + /// + /// The string to be parsed + public void Add(string st) { + string key = ""; + string val = ""; + bool inQuotes = false; + bool isVal = false; + int numCommas = 0; + for (int i = 0; i < st.Length; ++i) { + // Some entity values in Source can use escape sequenced quotes. Need to make sure not to parse those. + if (st[i] == '\"' && (i == 0 || st[i - 1] != '\\')) { + if (inQuotes) { + if (isVal) { + break; + } + isVal = true; + } + inQuotes = !inQuotes; + } else { + if (inQuotes) { + if (!isVal) { + key += st[i]; + } else { + val += st[i]; + if (st[i] == ',' || st[i] == ConnectionMemberSeparater) { ++numCommas; } + } + } + } + } + val.Replace("\\\"", "\""); + if (key != null && key != "") { + if (numCommas == 4 || numCommas == 6) { + st = st.Replace(',', ConnectionMemberSeparater); + string[] connection = val.Split(','); + if (connection.Length < 5) { + connection = val.Split((char)0x1B); + } + if (connection.Length == 5 || connection.Length == 7) { + connections.Add(new EntityConnection { + name = key, + target = connection[0], + action = connection[1], + param = connection[2], + delay = Double.Parse(connection[3]), + fireOnce = Int32.Parse(connection[4]), + unknown0 = connection.Length > 5 ? connection[5] : "", + unknown1 = connection.Length > 6 ? connection[6] : "", + }); + } + } else { + if (!ContainsKey(key)) { + Add(key, val); + } + } + } + } + + /// + /// Adds the key/value pair to this Entity + /// + /// Name of the attribute to add + /// Value of the attribute to add + public new void Add(string key, string value) { + this[key] = value; + } + + /// + /// Gets a string representation of this Entity. + /// + /// Don't use this to export the entity to a file + /// String representation of this Entity + public override string ToString() { + StringBuilder output = new StringBuilder(); + output.Append("{\n"); + foreach (KeyValuePair pair in this) { + output.Append(string.Format("\"{0}\" \"{1}\"\n", pair.Key, pair.Value)); + } + if (connections.Count > 0) { + output.Append("connections\n{\n"); + foreach (EntityConnection c in connections) { + output.Append(string.Format("\"{0}\" \"{1},{2},{3},{4},{5},{6},{7}\"\n", c.name, c.target, c.action, c.param, c.delay, c.fireOnce, c.unknown0, c.unknown1)); + } + output.Append("}\n"); + } + return output + "}"; + } + + /// + /// Checks if the attribute named "" has the value "" + /// + /// The attribute to check + /// The value to compare + /// true if the values match + public bool ValueIs(string key, string value) { + return value.Equals(this[key], StringComparison.InvariantCultureIgnoreCase); + } + + /// + /// Checks if the bits in "spawnflags" corresponding to the set bits set in are set + /// + /// The bits to compare spawnflags to + /// true if all bits that were set in were set in spawnflags + public bool SpawnflagsSet(uint bits) { + return ((spawnflags & bits) == bits); + } + + /// + /// Toggles the bits in "spawnflags" which are set in + /// + /// Bitmask of bits to toggle + public void ToggleSpawnflags(uint bits) { + this["spawnflags"] = (spawnflags ^ bits).ToString(); + } + + /// + /// Clears the bits in "spawnflags" which are set in + /// Equivalent to spawnflags = ( ^ 0xFFFFFFFF) & spawnflags + /// + /// Bitmask of bits to clear + public void ClearSpawnflags(uint bits) { + ToggleSpawnflags(spawnflags & bits); + } + + /// + /// Sets the bits in "spawnflags" which are set in + /// + /// Bitmask of bits to set + public void SetSpawnflags(uint bits) { + this["spawnflags"] = (spawnflags | bits).ToString(); + } + + /// + /// Tries to determine what Source engine input this entity would perform when "fired". + /// "Firing" an entity is used in practically all other engines for entity I/O, but + /// Source replaced it with the input/output system which, while more powerful, makes + /// my job that much harder. There is no generic "Fire" input, so I need to give a + /// best guess as to the action that will actually be performed. + /// + /// The best match action for what this entity would do when fired in other engines + public string OnFire() { + switch (this["classname"]) { + case "env_shake": { + return "StartShake"; + } + case "env_fade": { + return "Fade"; + } + case "env_sprite": { + return "ToggleSprite"; + } + case "logic_relay": { + return "Trigger"; + } + case "math_counter": { + return "Add,1"; + } + case "func_button": { + return "Press"; + } + case "func_breakable": { + return "Break"; + } + case "env_global": { + switch (this["triggermode"]) { + case "1": { + return "TurnOn"; + } + case "3": { + return "Toggle"; + } + default: { + return "TurnOff"; + } + } + } + case "trigger_changelevel": { + return "ChangeLevel"; + } + case "env_message": { + return "ShowMessage"; + } + case "ambient_generic": { + return "ToggleSound"; + } + case "func_door": + case "func_door_rotating": + case "trigger_hurt": + case "func_brush": + case "light": + case "light_spot": + default: { + return "Toggle"; + } + } + + } + + /// + /// Tries to determine what action in Source Engine's Entity I/O would be equivalent + /// to "enabling" the entity in prior engines + /// + /// The best match action for what this entity would do when enabled in other engines + public string OnEnable() { + switch (this["classname"]) { + case "func_door": + case "func_door_rotating": { + return "Open"; + } + case "ambient_generic": { + return "PlaySound"; + } + case "env_message": { + return "ShowMessage"; + } + case "trigger_changelevel": { + return "ChangeLevel"; + } + case "light": + case "light_spot": { + return "TurnOn"; + } + case "func_breakable": { + return "Break"; + } + case "env_shake": { + return "StartShake"; + } + case "env_fade": { + return "Fade"; + } + case "env_sprite": { + return "ShowSprite"; + } + case "func_button": { + return "PressIn"; + } + case "trigger_hurt": + case "func_brush": + case "logic_relay": + case "math_counter": + default: { + return "Enable"; + } + } + } + + /// + /// Tries to determine what action in Source Engine's Entity I/O would be equivalent + /// to "disabling" the entity in prior engines + /// + /// The best match action for what this entity would do when disabled in other engines + public string OnDisable() { + switch (this["classname"]) { + case "func_door": + case "func_door_rotating": { + return "Close"; + } + case "ambient_generic": { + return "StopSound"; + } + case "env_message": { + return "ShowMessage"; + } + case "trigger_changelevel": { + return "ChangeLevel"; + } + case "light": + case "light_spot": { + return "TurnOff"; + } + case "func_breakable": { + return "Break"; + } + case "env_shake": { + return "StopShake"; + } + case "env_fade": { + return "Fade"; + } + case "env_sprite": { + return "HideSprite"; + } + case "func_button": { + return "PressOut"; + } + case "trigger_hurt": + case "func_brush": + case "logic_relay": + case "math_counter": + default: { + return "Disable"; + } + } + } + + /// + /// Tries to determine which "Output" in Source Engine this Entity would use + /// to "fire" its targets in prior engines + /// + /// The best match "Output" for this Entity + public string FireAction() { + switch (this["classname"]) { + case "func_button": + case "func_rot_button": + case "momentary_rot_button": { + return "OnPressed"; + } + case "logic_auto": { + return "OnNewGame"; + } + case "func_door": + case "func_door_rotating": { + return "OnOpen"; + } + case "func_breakable": { + return "OnBreak"; + } + case "math_counter": { + return "OnHitMax"; + } + case "trigger_multiple": + case "trigger_once": + case "logic_relay": + default: { + return "OnTrigger"; + } + } + } + + /// + /// Gets a numeric value as a float. + /// + /// Name of the attribute to retrieve + /// Value to return if doesn't exist, or couldn't be converted + /// The numeric value of the value corresponding to + public float GetFloat(string key, float? failDefault = null) { + try { + return Single.Parse(this[key]); + } catch (Exception e) { + if (!failDefault.HasValue) { + throw e; + } + return failDefault.Value; + } + } + + /// + /// Gets a numeric value as an int. + /// + /// Name of the attribute to retrieve + /// Value to return if doesn't exist, or couldn't be converted + /// The numeric value of the value corresponding to + public int GetInt(string key, int? failDefault = null) { + try { + return Int32.Parse(this[key]); + } catch (Exception e) { + if (!failDefault.HasValue) { + throw e; + } + return failDefault.Value; + } + } + + /// + /// Gets a Vector value as a Vector4. This will only read as many values as are in the value, and can be + /// implicitly converted to Vector3, Vector2, or Color. + /// + /// Name of the attribute to retrieve + /// Vector representation of the components of the attribute + public Vector4 GetVector(string key) { + float[] results = new float[4]; + if (ContainsKey(key) && !string.IsNullOrEmpty(this[key])) { + string[] nums = this[key].Split(' '); + for (int i = 0; i < results.Length && i < nums.Length; ++i) { + try { + results[i] = System.Single.Parse(nums[i]); + } catch { + results[i] = 0; + } + } + } + return new Vector4(results[0], results[1], results[2], results[3]); + } + + /// + /// Compares this Entity to another object. First "classname" attributes are compared, then "targetname". + /// Attributes are compared alphabetically. Targetnames are only compared if classnames match. + /// + /// Object to compare to + /// Less than zero if this entity is first, 0 if they occur at the same time, greater than zero otherwise + public int CompareTo(object obj) { + if (obj == null) { return 1; } + Entity other = obj as Entity; + if (other == null) { throw new ArgumentException("Object is not an Entity"); } + + int firstTry = className.CompareTo(other.className); + return firstTry != 0 ? firstTry : name.CompareTo(other.name); + } + + /// + /// Compares this Entity to another Entity. First "classname" attributes are compared, then "targetname". + /// Attributes are compared alphabetically. Targetnames are only compared if classnames match. + /// + /// Entity to compare to + /// Less than zero if this entity is first, 0 if they occur at the same time, greater than zero otherwise + public int CompareTo(Entity other) { + if (other == null) { return 1; } + int firstTry = className.CompareTo(other.className); + return firstTry != 0 ? firstTry : name.CompareTo(other.name); + } + + /// + /// Factory method for a Lump. + /// + /// The data to parse + /// The map type + /// An Entities object, which is a List of Entitys + public static Entities LumpFactory(byte[] data, MapType type) { + return new Entities(data, type); + } + + /// + /// Struct containing the fields necessary for Source entity I/O + /// + public struct EntityConnection { + public string name; + public string target; + public string action; + public string param; + public double delay; + public int fireOnce; + // As I recall, these are Dark Messiah only. I have no idea what they are for. + public string unknown0; + public string unknown1; + } + + } +} diff --git a/LibBSP/Structs/Common/Lumps/Entities.cs b/LibBSP/Structs/Common/Lumps/Entities.cs new file mode 100644 index 0000000..1506888 --- /dev/null +++ b/LibBSP/Structs/Common/Lumps/Entities.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Class representing a group of Entity objects. Contains helpful methods to handle Entities in the List + /// + public class Entities : List { + + /// + /// Initializes a new instance of an Entities object copying a passed IEnumerable of Entity objects. + /// + /// Collection of Entity objects to copy + public Entities(IEnumerable data) : base(data) { } + + /// + /// Initializes a new instance of an Entities object with a specified initial capacity. + /// + /// Initial capacity of the List of Entity objects + public Entities(int initialCapacity) : base(initialCapacity) { } + + /// + /// Initializes a new empty Entities object. + /// + public Entities() : base() { } + + /// + /// Initializes a new Entities object, and parses the passed byte array into the List. + /// + /// Bytes read from a file + public Entities(byte[] data, MapType type) : base() { + // Keep track of whether or not we're currently in a set of quotation marks. + // I came across a map where the idiot map maker used { and } within a value. This broke the code before. + bool inQuotes = false; + int braceCount = 0; + + // The current character being read in the file. This is necessary because + // we need to know exactly when the { and } characters occur and capture + // all text between them. + char currentChar; + // This will be the resulting entity, fed into Entity.FromString + System.Text.StringBuilder current = new System.Text.StringBuilder(); + + for (int offset = 0; offset < data.Length; ++offset) { + currentChar = (char)data[offset]; + + // Allow for escape-sequenced quotes to not affect the state machine. + if (currentChar == '\"' && (offset == 0 || (char)data[offset - 1] != '\\')) { + inQuotes = !inQuotes; + } + + if (!inQuotes) { + if (currentChar == '{') { + // Occasionally, texture paths have been known to contain { or }. Since these aren't always contained + // in quotes, we must be a little more precise about how we want to select our delimiters. + // As a general rule, though, making sure we're not in quotes is still very effective at error prevention. + if (offset == 0 || (char)data[offset - 1] == '\n' || (char)data[offset - 1] == '\t' || (char)data[offset - 1] == ' ' || (char)data[offset - 1] == '\r') { + ++braceCount; + } + } + } + + if (braceCount > 0) { + current.Append(currentChar); + } + + if (!inQuotes) { + if (currentChar == '}') { + if (offset == 0 || (char)data[offset - 1] == '\n' || (char)data[offset - 1] == '\t' || (char)data[offset - 1] == ' ' || (char)data[offset - 1] == '\r') { + --braceCount; + if (braceCount == 0) { + this.Add(Entity.FromString(current.ToString())); + // Reset StringBuilder + current.Length = 0; + } + } + } + } + } + + if (braceCount != 0) { + throw new ArgumentException(string.Format("Brace mismatch when parsing entities! Brace level: {0}", braceCount)); + } + } + + /// + /// Deletes all Entity with "" set to "". + /// + /// Attribute to match + /// Desired value of attribute + public void RemoveAllWithAttribute(string key, string value) { + //DeleteEnts(FindAllWithAttribute(key, value)); + this.RemoveAll(entity => { return entity[key] == value; }); + } + + /// + /// Gets a List of all Entitys with "" set to "". + /// + /// Name of the attribute to search for + /// Value of the attribute to search for + /// List(Entity) that have the specified key/value pair + public List GetAllWithAttribute(string key, string value) { + return FindAll(entity => { return entity[key] == value; }); + } + + /// + /// Gets a List of Entitys objects with the specified targetname + /// + /// Targetname attribute to find + /// List(Entity) with the specified targetname + public List GetAllWithName(string targetname) { + return GetAllWithAttribute("targetname", targetname); + } + + /// + /// Gets the first Entity with "" set to "". + /// + /// Name of the attribute to search for + /// Value of the attribute to search for + /// Entity with the specified key/value pair, or null if none exists + public Entity GetWithAttribute(string key, string value) { + return Find(entity => { return entity[key] == value; }); + } + + /// + /// Gets the first Entity with the specified targetname. + /// + /// Targetname attribute to find + /// Entity object with the specified targetname + public Entity GetWithName(string targetname) { + return GetWithAttribute("targetname", targetname); + } + } +} diff --git a/LibBSP/Structs/Common/NumList.cs b/LibBSP/Structs/Common/NumList.cs new file mode 100644 index 0000000..45bf398 --- /dev/null +++ b/LibBSP/Structs/Common/NumList.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// List class for numbers. Can handle any integer data type except ulong. + /// + public class NumList : List { + + public enum DataType : int { + SByte = 0, + Byte = 1, + Int16 = 2, + UInt16 = 3, + Int32 = 4, + UInt32 = 5, + Int64 = 6, + } + + public DataType type { get; private set; } + + /// + /// Creates a new NumList object from a byte array. + /// + /// byte array to parse + /// The type of number to store + /// was null + /// was not a member of the DataType enum + public NumList(byte[] data, DataType type) { + if (data == null) { + throw new ArgumentNullException(); + } + this.type = type; + switch (type) { + case DataType.SByte: { + unchecked { + for (int i = 0; i < data.Length; ++i) { + Add((long)((sbyte)data[i])); + } + } + break; + } + case DataType.Byte: { + for (int i = 0; i < data.Length; ++i) { + Add((long)(data[i])); + } + break; + } + case DataType.Int16: { + for (int i = 0; i < data.Length / 2; ++i) { + Add((long)BitConverter.ToInt16(data, i * 2)); + } + break; + } + case DataType.UInt16: { + for (int i = 0; i < data.Length / 2; ++i) { + Add((long)BitConverter.ToUInt16(data, i * 2)); + } + break; + } + case DataType.Int32: { + for (int i = 0; i < data.Length / 4; ++i) { + Add((long)BitConverter.ToInt32(data, i * 4)); + } + break; + } + case DataType.UInt32: { + for (int i = 0; i < data.Length / 4; ++i) { + Add((long)BitConverter.ToUInt32(data, i * 4)); + } + break; + } + case DataType.Int64: { + for (int i = 0; i < data.Length / 8; ++i) { + Add(BitConverter.ToInt64(data, i * 8)); + } + break; + } + default: { + throw new ArgumentException(type + " isn't a member of the DataType enum."); + } + } + } + } +} diff --git a/LibBSP/Structs/Common/Plane.cs b/LibBSP/Structs/Common/Plane.cs new file mode 100644 index 0000000..e11ec67 --- /dev/null +++ b/LibBSP/Structs/Common/Plane.cs @@ -0,0 +1,423 @@ +#if !(UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +using System; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Holds the data for a plane in 3D space in Hessian Normal Form. + /// + [Serializable] public struct Plane : IEquatable { + + private Vector3d _normal; + public double distance; + public Vector3d normal { + get { return _normal; } + set { + _normal = value; + _normal.Normalize(); + } + } + + /// + /// The a component of this Plane + /// + public double a { + get { + return normal.x; + } + } + + /// + /// The b component of this Plane + /// + public double b { + get { + return normal.y; + } + } + + /// + /// The c component of this Plane + /// + public double c { + get { + return normal.z; + } + } + + /// + /// This Plane, flipped over so the negative side is now the positive side, and vice versa. + /// + public Plane flipped { + get { + return new Plane(-normal, -distance); + } + } + + /// + /// Creates a new Plane object using floats. The first three floats are the normal, and the last one is the distance. + /// + /// Components of this Plane + /// 4 floats were not passed + /// The passed array was null + public Plane(params float[] nums) { + if (nums == null) { + throw new ArgumentNullException(); + } + if (nums.Length != 4) { + throw new ArgumentException("You must provide four numbers to generate a plane!"); + } + _normal = new Vector3d(nums[0], nums[1], nums[2]); + _normal.Normalize(); + distance = Convert.ToDouble(nums[3]); + } + + /// + /// Creates a new Plane object using doubles. The first three doubles are the normal, and the last one is the distance. + /// + /// Components of this Plane + /// 4 doubles were not passed + /// The passed array was null + public Plane(params double[] nums) { + if (nums == null) { + throw new ArgumentNullException(); + } + if (nums.Length != 4) { + throw new ArgumentException("You must provide four numbers to generate a plane!"); + } + _normal = new Vector3d(nums[0], nums[1], nums[2]); + _normal.Normalize(); + distance = nums[3]; + } + + /// + /// Creates a new Plane object using a normal and distance. + /// + /// Normal of this Plane + /// Distance from the origin to this Plane + public Plane(Vector3d normal, double dist) { + this._normal = new Vector3d(normal); + _normal.Normalize(); + this.distance = dist; + } + + /// + /// Creates a new Plane object using a normal and distance. + /// + /// Normal of this Plane + /// Distance from the origin to this Plane + public Plane(Vector3d normal, float dist) : this(normal, Convert.ToDouble(dist)) { } + + /// + /// Creates a new Plane object by copying another Plane. + /// + /// Plane to copy. + public Plane(Plane copy) { + _normal = new Vector3d(copy.normal); + distance = copy.distance; + } + + /// + /// Creates a new Plane object using a normal and a point on the Plane. + /// + /// Normal of this Plane + /// A point on this Plane + public Plane(Vector3d normal, Vector3d point) { + this._normal = normal; + _normal.Normalize(); + this.distance = point * normal; + } + + /// + /// Creates a new Plane object using three points on the Plane. + /// + /// A point on the Plane + /// A point on the Plane + /// A point on the Plane + public Plane(Vector3d point0, Vector3d point1, Vector3d point2) { + _normal = ((point0 - point2) ^ (point0 - point1)); + _normal.Normalize(); + distance = point0 * _normal; + } + + /// + /// Creates a new Plane object from a byte array. + /// + /// byte array to parse + /// The map type + /// + /// This constructor is intended for use with BSP reading. The byte array must have a length of at least 16, all components are read as floats. + /// + /// was null + /// There was not enough bytes in the array + public Plane(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + if (data.Length < 16) { + throw new ArgumentException("Not enough data in provided array"); + } + _normal = new Vector3d(BitConverter.ToSingle(data, 0), BitConverter.ToSingle(data, 4), BitConverter.ToSingle(data, 8)); + _normal.Normalize(); + distance = Convert.ToDouble(BitConverter.ToSingle(data, 12)); + } + + /// + /// Determines whether this Plane is equal to another object. + /// + /// object to compare to + /// Whether is a Plane and is equal to this Plane + public override bool Equals(object obj) { + if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; } + return Equals((Plane)obj); + } + + /// + /// Compares whether two Planes are equal, or approximately equal. + /// + /// The Plane to compare to + /// true if this Plane is parallel to, faces the same direction, and has the same distance as, the given Plane. + public bool Equals(Plane other) { + return (normal == other.normal && distance + 0.001 >= other.distance && distance - 0.001 <= other.distance); + } + + /// + /// Generates a hash code for this instance based on instance data. + /// + /// The hash code for this instance + public override int GetHashCode() { + return _normal.GetHashCode() ^ distance.GetHashCode(); + } + + /// + /// Compares whether two Planes are equal, or approximately equal. + /// + /// The Plane to compare to + /// true if this Plane is parallel to, faces the same direction, and has the same distance as, the given Plane. + public static bool operator ==(Plane p1, Plane p2) { + return p1.Equals(p2); + } + + /// + /// Compares whether two Planes are not equal, or approximately equal. + /// + /// The Plane to compare to + /// false if this Plane is parallel to, faces the same direction, and has the same distance as, the given Plane. + public static bool operator !=(Plane p1, Plane p2) { + return !p1.Equals(p2); + } + + /// + /// Determines whether the given Vector3d is contained in this Plane. + /// + /// Vector + /// true if the Vector3d is contained in this Plane. + public bool Contains(Vector3d v) { + double distanceTo = GetDistanceToPoint(v); + return distanceTo < 0.001 && distanceTo > -0.001; + } + + /// + /// Gets the signed distance from this Plane to a given point. + /// + /// Point to get the distance to + /// Signed distance from this Plane to the given point. + public double GetDistanceToPoint(Vector3d to) { + // Ax + By + Cz - d = DISTANCE = normDOTpoint - d + double normLength = System.Math.Pow(normal.x, 2) + System.Math.Pow(normal.y, 2) + System.Math.Pow(normal.z, 2); + if (System.Math.Abs(normLength - 1.00) > 0.01) { + normLength = System.Math.Sqrt(normLength); + } + return (normal.x * to.x + normal.y * to.y + normal.z * to.z - distance) / normLength; + } + + /// + /// Is on the positive side of this Plane? + /// + /// Point to get the side for + /// true if is on the positive side of this Plane + public bool GetSide(Vector3d v) { + return GetDistanceToPoint(v) > 0; + } + + /// + /// Flips this Plane to face the opposite direction. + /// + public void Flip() { + normal = -normal; + distance = -distance; + } + + /// + /// Flips this Plane to face the opposite direction. + /// + public static Plane operator -(Plane flipMe) { + return new Plane(-flipMe.normal, -flipMe.distance); + } + + /// + /// Gets a nicely formatted string representation of this Plane. + /// + /// A nicely formatted string representation of this Plane + public override string ToString() { + return "(" + normal.ToString() + ") " + distance; + } + + /// + /// Raycasts a Ray against this Plane. + /// + /// Ray to raycast against + /// Out parameter. The distance along where the collision happened + /// + /// true and is positive if Ray intersects this Plane in front of the way, + /// false and is negative if Ray intersects this Plane behind the ray, + /// false and is 0 if the Ray is parallel to this Plane + /// + public bool Raycast(Ray ray, out double enter) { + double denom = (Vector3d.Dot(ray.direction, normal)); + if (denom > -0.005 && denom < 0.005) { + enter = 0; + return false; + } + enter = (-1 * (Vector3d.Dot(ray.origin, normal) + distance)) / denom; + return enter > 0; + } + + /// + /// Generates three points which can be used to define this Plane. + /// + /// Three points which define this Plane + public Vector3d[] GenerateThreePoints() { + //DecompilerThread.OnMessage(this, "Calculating arbitrary plane points"); + double planePointCoef = 32; + Vector3d[] plane = new Vector3d[3]; + // Figure out if the plane is parallel to two of the axes. If so it can be reproduced easily + if (normal.y == 0 && normal.z == 0) { + // parallel to plane YZ + plane[0] = new Vector3d(this.distance / normal.x, -planePointCoef, planePointCoef); + plane[1] = new Vector3d(this.distance / normal.x, 0, 0); + plane[2] = new Vector3d(this.distance / normal.x, planePointCoef, planePointCoef); + if (normal.x > 0) { + Array.Reverse(plane); + } + } else { + if (normal.x == 0 && normal.z == 0) { + // parallel to plane XZ + plane[0] = new Vector3d(planePointCoef, this.distance / normal.y, -planePointCoef); + plane[1] = new Vector3d(0, this.distance / normal.y, 0); + plane[2] = new Vector3d(planePointCoef, this.distance / normal.y, planePointCoef); + if (normal.y > 0) { + Array.Reverse(plane); + } + } else { + if (normal.x == 0 && normal.y == 0) { + // parallel to plane XY + plane[0] = new Vector3d(-planePointCoef, planePointCoef, this.distance / normal.z); + plane[1] = new Vector3d(0, 0, this.distance / normal.z); + plane[2] = new Vector3d(planePointCoef, planePointCoef, this.distance / normal.z); + if (normal.z > 0) { + Array.Reverse(plane); + } + } else { + // If you reach this point the plane is not parallel to any two-axis plane. + if (normal.x == 0) { + // parallel to X axis + plane[0] = new Vector3d(-planePointCoef, planePointCoef * planePointCoef, (-(planePointCoef * planePointCoef * normal.y - this.distance)) / normal.z); + plane[1] = new Vector3d(0, 0, this.distance / normal.z); + plane[2] = new Vector3d(planePointCoef, planePointCoef * planePointCoef, (-(planePointCoef * planePointCoef * normal.y - this.distance)) / normal.z); + if (normal.z > 0) { + Array.Reverse(plane); + } + } else { + if (normal.y == 0) { + // parallel to Y axis + plane[0] = new Vector3d((-(planePointCoef * planePointCoef * normal.z - this.distance)) / normal.x, -planePointCoef, planePointCoef * planePointCoef); + plane[1] = new Vector3d(this.distance / normal.x, 0, 0); + plane[2] = new Vector3d((-(planePointCoef * planePointCoef * normal.z - this.distance)) / normal.x, planePointCoef, planePointCoef * planePointCoef); + if (normal.x > 0) { + Array.Reverse(plane); + } + } else { + if (normal.z == 0) { + // parallel to Z axis + plane[0] = new Vector3d(planePointCoef * planePointCoef, (-(planePointCoef * planePointCoef * normal.x - this.distance)) / normal.y, -planePointCoef); + plane[1] = new Vector3d(0, this.distance / normal.y, 0); + plane[2] = new Vector3d(planePointCoef * planePointCoef, (-(planePointCoef * planePointCoef * normal.x - this.distance)) / normal.y, planePointCoef); + if (normal.y > 0) { + Array.Reverse(plane); + } + } else { + // If you reach this point the plane is not parallel to any axis. Therefore, any two coordinates will give a third. + plane[0] = new Vector3d(-planePointCoef, planePointCoef * planePointCoef, -(-planePointCoef * normal.x + planePointCoef * planePointCoef * normal.y - this.distance) / normal.z); + plane[1] = new Vector3d(0, 0, this.distance / normal.z); + plane[2] = new Vector3d(planePointCoef, planePointCoef * planePointCoef, -(planePointCoef * normal.x + planePointCoef * planePointCoef * normal.y - this.distance) / normal.z); + if (normal.z > 0) { + Array.Reverse(plane); + } + } + } + } + } + } + } + return plane; + } + + /// + /// Factory method to parse a byte array into a List of Plane objects. + /// + /// The data to parse + /// The map type + /// A List of Plane objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Quake: + case MapType.Nightfire: + case MapType.SiN: + case MapType.SoF: + case MapType.Source17: + case MapType.Source18: + case MapType.Source19: + case MapType.Source20: + case MapType.Source21: + case MapType.Source22: + case MapType.Source23: + case MapType.Source27: + case MapType.DMoMaM: + case MapType.Vindictus: + case MapType.Quake2: + case MapType.Daikatana: + case MapType.TacticalIntervention: + structLength = 20; + break; + case MapType.STEF2: + case MapType.MOHAA: + case MapType.STEF2Demo: + case MapType.Raven: + case MapType.Quake3: + case MapType.FAKK: + case MapType.CoD: + case MapType.CoD2: + case MapType.CoD4: + structLength = 16; + break; + default: + throw new ArgumentException("Map type " + type + " isn't supported by the Plane class."); + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Plane(bytes, type)); + } + return lump; + } + } +} +#endif diff --git a/LibBSP/Structs/Common/Ray.cs b/LibBSP/Structs/Common/Ray.cs new file mode 100644 index 0000000..d3ef380 --- /dev/null +++ b/LibBSP/Structs/Common/Ray.cs @@ -0,0 +1,95 @@ +#if !(UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +using System; + +namespace LibBSP { + /// + /// A struct for a Ray defined by a starting point and a direction vector. + /// + public struct Ray : IEquatable { + + public Vector3d origin; + private Vector3d _direction; + + public Vector3d direction { + get { return _direction; } + set { + _direction = value.normalized; + } + } + + /// + /// Creates a new Ray object using the specified origin and direction. + /// + /// Origin point of this Ray + /// Direction vector of this Ray + public Ray(Vector3d origin, Vector3d direction) { + this.origin = origin; + this._direction = direction; + _direction.Normalize(); + } + + /// + /// Gets the point at units along this Ray. + /// + /// Distance of the point to get + /// The point at units along this Ray + public Vector3d GetPoint(double distance) { + return origin + (distance * direction); + } + + /// + /// Gets a nicely formatted string representation of this Ray. + /// + /// A nicely formatted string representation of this Ray + public override string ToString() { + return string.Format("( {0}, {1} )", origin, direction); + } + + /// + /// Determines whether this Ray is equivalent to another. + /// + /// The Ray to compare to + /// true if this Ray and have the same origin and direction + public static bool operator ==(Ray r1, Ray r2) { + return r1.Equals(r2); + } + + /// + /// Determines whether this Ray is not equivalent to another. + /// + /// The Ray to compare to + /// true if this Ray and don't have the same origin and direction + public static bool operator !=(Ray r1, Ray r2) { + return !r1.Equals(r2); + } + + /// + /// Determines whether this Ray is equivalent to another. + /// + /// The Ray to compare to + /// true if this Ray and have the same origin and direction + public bool Equals(Ray other) { + return origin.Equals(other.origin) && direction.Equals(other.direction); + } + + /// + /// Determines whether this Ray is equivalent to another object. + /// + /// The object to compare to + /// true if is a Ray and this Ray and have the same origin and direction + public override bool Equals(object obj) { + if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; } + return Equals((Ray)obj); + } + + /// + /// Generates a hash code for this instance based on instance data. + /// + /// The hash code for this instance + public override int GetHashCode() { + return origin.GetHashCode() ^ direction.GetHashCode(); + } + + } +} +#endif diff --git a/LibBSP/Structs/Common/Rect.cs b/LibBSP/Structs/Common/Rect.cs new file mode 100644 index 0000000..54bdf5b --- /dev/null +++ b/LibBSP/Structs/Common/Rect.cs @@ -0,0 +1,297 @@ +#if !(UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +using System; +using System.Collections.Generic; + +namespace LibBSP { + public struct Rect : IEquatable { + + public Vector2d position; + public Vector2d size; + + /// + /// Gets the center point of this Rect + /// + public Vector2d center { + get { + return position + (size / 2.0); + } + } + + /// + /// Gets the width of this Rect + /// + public double width { + get { + return size.x; + } + set { + size.x = value; + } + } + + /// + /// Gets the height of this Rect + /// + public double height { + get { + return size.y; + } + set { + size.y = value; + } + } + + /// + /// Gets the bottom-left coordinate of this Rect + /// + public Vector2d min { + get { + return new Vector2d(position.x, position.y + size.y); + } + set { + position.x = value.x; + size.y = value.y - position.y; + } + } + + /// + /// Gets the top-right coordinate of this Rect + /// + public Vector2d max { + get { + return new Vector2d(position.x + size.x, position.y); + } + set { + position.y = value.y; + size.x = value.x - position.x; + } + } + + /// + /// Gets the left bound of this Rect + /// + public double x { + get { + return position.x; + } + set { + position.x = value; + } + } + + /// + /// Gets the left bound of this Rect + /// + public double xMin { + get { + return position.x; + } + set { + position.x = value; + } + } + + /// + /// Gets the right bound of this Rect + /// + public double xMax { + get { + return position.x + size.x; + } + set { + size.x = value - position.x; + } + } + + /// + /// Gets the upper bound of this Rect + /// + public double y { + get { + return position.y; + } + set { + position.y = value; + } + } + + /// + /// Gets the upper bound of this Rect + /// + public double yMin { + get { + return position.y; + } + set { + position.y = value; + } + } + + /// + /// Gets the lower bound of this Rect + /// + public double yMax { + get { + return position.y + size.y; + } + set { + size.y = value - position.y; + } + } + + /// + /// Creates a new Rect object. + /// + /// Left bound + /// Upper bound + /// Width + /// Height + public Rect(double left, double top, double width, double height) : this() { + position = new Vector2d(left, top); + size = new Vector2d(width, height); + } + + /// + /// Creates a new Rect object from the specified bounds + /// + /// Left bound + /// Upper bound + /// Right bound + /// Lower bound + /// The resulting Rect + public static Rect MinMaxRect(double left, double top, double right, double bottom) { + return new Rect(left, top, right - left, bottom - top); + } + + /// + /// Gets whether this Rect contains the specified point. + /// + /// The point to check + /// If the Rect's size is negative, should the check allow this? + /// true if the point is contained in the Rect + public bool Contains(Vector2d point, bool allowInverse = false) { + if (allowInverse) { + return point.x > position.x && point.x < position.x + size.x && point.y > position.y && point.y < position.y + size.y; + } else { + double xmax = Math.Max(x, xMax); + double ymax = Math.Max(y, yMax); + + return MinMaxRect(x, y, xmax, ymax).Contains(point, true); + } + } + + /// + /// Gets whether this Rect overlaps the specified Rect. + /// + /// The Rect to check + /// If either Rect's size is negative, should the check allow this? + /// true if the other Rect overlaps this Rect + public bool Overlaps(Rect other, bool allowInverse = false) { + if (allowInverse) { + return Contains(other.position, true) || Contains(other.position + other.size, true) || Contains(other.min, true) || Contains(other.max, true) || + other.Contains(position, true) || other.Contains(position + size, true) || other.Contains(min, true) || other.Contains(max, true); + } else { + double xmax = Math.Max(x, xMax); + double ymax = Math.Max(y, yMax); + double otherXmax = Math.Max(other.x, other.xMax); + double otherYmax = Math.Max(other.y, other.yMax); + + return MinMaxRect(x, y, xmax, ymax).Overlaps(MinMaxRect(other.x, other.y, otherXmax, otherYmax), true); + } + } + + /// + /// Sets this Rect's components + /// + /// The left bound to set + /// The upper bound to set + /// The width to set + /// The height to set + public void Set(double left, double top, double width, double height) { + position.x = left; + position.y = top; + size.x = width; + size.y = height; + } + + /// + /// Given a normalized point, returns a coordinate point inside the Rect. + /// + /// The Rect + /// The normalized coordinates + /// The denormalized coordinates + public static Vector2d NormalizedToPoint(Rect rect, Vector2d normalizedCoordinates) { + return new Vector2d(rect.x + (rect.width * normalizedCoordinates.x), rect.y + (rect.height * normalizedCoordinates.y)); + } + + /// + /// Given a point, returns a normalized point relative to the Rect. + /// + /// The Rect + /// The coordinates + /// The normalized coordinates + public static Vector2d PointToNormalized(Rect rect, Vector2d point) { + if (rect.width != 0 && rect.height != 0) { + return new Vector2d((point.x - rect.x) / rect.width, (point.y - rect.y) / rect.height); + } else { + return new Vector2d(Double.NaN, Double.NaN); + } + } + + /// + /// Gives a nicely formatted string for this Rect. + /// + /// Nicely formatted string for this Rect + public override string ToString() { + return string.Format("(x:{1}, y:{2}, width:{3}, height:{4})", x, y, width, height); + } + + /// + /// Determines whether this Rect equals another + /// + /// The Rect to compare to + /// true if the Rects are equal + public bool Equals(Rect other) { + return position == other.position && size == other.size; + } + + /// + /// Determines whether this Rect equals another object + /// + /// The object to compare to + /// true if the other object is not null and a Rect, and the Rects are equal + public override bool Equals(object obj) { + if (object.ReferenceEquals(obj, null)) { return false; } + if (!(obj is Rect)) { return false; } + return Equals((Rect)obj); + } + + /// + /// Determines whether this Rect equals another + /// + /// The Rect to compare to + /// true if the Rects are equal + public static bool operator ==(Rect r1, Rect r2) { + return r1.Equals(r2); + } + + /// + /// Determines whether this Rect does not equal another + /// + /// The Rect to compare to + /// true if the Rects are not equal + public static bool operator !=(Rect r1, Rect r2) { + return !r1.Equals(r2); + } + + /// + /// Generates a hash code for this instance based on instance data. + /// + /// The hash code for this instance + public override int GetHashCode() { + return position.GetHashCode() ^ size.GetHashCode(); + } + + } +} +#endif diff --git a/LibBSP/Structs/Common/UIVertex.cs b/LibBSP/Structs/Common/UIVertex.cs new file mode 100644 index 0000000..969908c --- /dev/null +++ b/LibBSP/Structs/Common/UIVertex.cs @@ -0,0 +1,45 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif +#if !(UNITY_4_6 || UNITY_5) +#if UNITY +using UnityEngine; +#else +using System.Drawing; +#endif +using System; + +namespace LibBSP { +#if !UNITY + using Color32 = Color; + using Vector2 = Vector2d; + using Vector3 = Vector3d; + using Vector4 = Vector4d; +#endif + /// + /// Replacement UIVertex struct for Unity versions before 4.6, and standalone projects. + /// + /// 4.5 had UIVertex but the implementation was incomplete. + public struct UIVertex { + public Color32 color; + public Vector3 normal; + public Vector3 position; + public Vector4 tangent; + public Vector2 uv0; + public Vector2 uv1; + + public static UIVertex simpleVert { + get { + return new UIVertex { + color = Color32Extensions.FromArgb(255, 255, 255, 255), + normal = new Vector3(0, 0, -1), + position = new Vector3(0, 0, 0), + tangent = new Vector4(1, 0, 0, -1), + uv0 = new Vector2(0, 0), + uv1 = new Vector2(0, 0) + }; + } + } + } +} +#endif diff --git a/LibBSP/Structs/Common/Vector2d.cs b/LibBSP/Structs/Common/Vector2d.cs new file mode 100644 index 0000000..74d3a83 --- /dev/null +++ b/LibBSP/Structs/Common/Vector2d.cs @@ -0,0 +1,449 @@ +#if !(UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +using System; +using System.Collections; +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Holds an array of two doubles representing a 2-dimensional vector. + /// + [Serializable] public struct Vector2d : IEquatable, IEnumerable, IEnumerable { + + /// Returns Vector2d(NaN, NaN, NaN) + public static Vector2d undefined { get { return new Vector2d(System.Double.NaN, System.Double.NaN); } } + /// Returns Vector2d(1, 0) + public static Vector2d right { get { return new Vector2d(1, 0); } } + /// Returns Vector2d(0, 1) + public static Vector2d up { get { return new Vector2d(0, 1); } } + /// Returns Vector2d(-1, 0) + public static Vector2d left { get { return new Vector2d(-1, 0); } } + /// Returns Vector2d(0, -1) + public static Vector2d down { get { return new Vector2d(0, -1); } } + /// Returns Vector2d(0, 0) + public static Vector2d zero { get { return new Vector2d(0, 0); } } + /// Returns Vector2d(1, 1) + public static Vector2d one { get { return new Vector2d(1, 1); } } + + public double x; + public double y; + + /// + /// Gets or sets a component using an indexer, x=0, y=1 + /// + /// Component to get or set + /// Component + /// was negative or greater than 1 + public double this[int index] { + get { + switch (index) { + case 0: { + return x; + } + case 1: { + return y; + } + default: { + throw new IndexOutOfRangeException(); + } + } + } + set { + switch (index) { + case 0: { + x = value; + break; + } + case 1: { + y = value; + break; + } + default: { + throw new IndexOutOfRangeException(); + } + } + } + } + + /// + /// Gets the magnitude of this Vector2d, or its distance from (0, 0). + /// + public double magnitude { get { return Math.Sqrt(sqrMagnitude); } } + + /// + /// Gets the magnitude of this Vector2d squared. This is useful for when you are comparing the lengths of two vectors + /// but don't need to know the exact length, and avoids a square root. + /// + public double sqrMagnitude { get { return System.Math.Pow(x, 2) + System.Math.Pow(y, 2); } } + + /// + /// Gets the normalized version of this Vector2d (unit vector with the same direction). + /// + public Vector2d normalized { + get { + double magnitude = this.magnitude; + return new Vector2d(x / magnitude, y / magnitude); + } + } + + /// + /// Creates a new Vector2d object using elements in the passed array as components + /// + /// Components of the vector + public Vector2d(params float[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + if (point.Length == 2) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + } else if (point.Length == 1) { + x = Convert.ToDouble(point[0]); + } + } + + /// + /// Creates a new Vector2d object using elements in the passed array as components + /// + /// Components of the vector + public Vector2d(params double[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + if (point.Length == 2) { + x = point[0]; + y = point[1]; + } else if (point.Length == 1) { + x = point[0]; + } + } + + /// + /// Creates a new Vector2d object using elements in the passed array as components + /// + /// Components of the vector + public Vector2d(params int[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + if (point.Length == 2) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + } else if (point.Length == 1) { + x = Convert.ToDouble(point[0]); + } + } + + /// + /// Crates a new Vector2d instance using the components from the supplied Vector2d + /// + /// Vector to copy components from + public Vector2d(Vector2d vector) { + x = vector.x; + y = vector.y; + } + + /// + /// Creates a new Vector2d instance by parsing a byte array representing either floats or doubles. + /// + /// Byte array to parse + /// The passed byte array was not 8 or 16 elements long + public Vector2d(params byte[] data) { + if (data == null) { + throw new ArgumentNullException(); + } + if (data.Length == 8) { + x = Convert.ToDouble(BitConverter.ToSingle(data, 0)); + y = Convert.ToDouble(BitConverter.ToSingle(data, 4)); + } else if (data.Length == 24) { + x = BitConverter.ToDouble(data, 0); + y = BitConverter.ToDouble(data, 8); + } else { + throw new System.ArgumentException("Byte array passed to Vector3d Ctor must have 8 or 16 items", "data"); + } + } + + /// + /// Adds two vectors together componentwise. This operation is commutative. + /// + /// First vector to add + /// Second vector to add + /// The resulting vector + public static Vector2d operator +(Vector2d v1, Vector2d v2) { + return Add(v1, v2); + } + + /// + /// Adds two vectors together componentwise. This operation is commutative. + /// + /// First vector to add + /// Second vector to add + /// The resulting vector + public static Vector2d Add(Vector2d v1, Vector2d v2) { + return new Vector2d(v1.x + v2.x, v1.y + v2.y); + } + + /// + /// Subtracts one vector from another. This operation is NOT commutative + /// + /// Vector to subtract from + /// Vector to subtract + /// Difference from to + public static Vector2d operator -(Vector2d v1, Vector2d v2) { + return Subtract(v1, v2); + } + + /// + /// Subtracts one vector from another. This operation is NOT commutative + /// + /// Vector to subtract from + /// Vector to subtract + /// Difference from to + public static Vector2d Subtract(Vector2d v1, Vector2d v2) { + return new Vector2d(v1.x - v2.x, v1.y - v2.y); + } + + /// + /// Returns the negative of this vector. Equivalent to (0, 0) - . + /// + /// Vector to negate + /// with all components negated + public static Vector2d operator -(Vector2d v1) { + return Negate(v1); + } + + /// + /// Returns the negative of this vector. Equivalent to (0, 0) - . + /// + /// Vector to negate + /// with all components negated + public static Vector2d Negate(Vector2d v1) { + return new Vector2d(-v1.x, -v1.y); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Vector to scale + /// Scalar + /// Resulting Vector + public static Vector2d operator *(Vector2d v1, double scalar) { + return Scale(v1, scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Scalar + /// Vector to scale + /// Resulting Vector + public static Vector2d operator *(double scalar, Vector2d v1) { + return Scale(v1, scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Vector to scale + /// Scalar + /// Resulting Vector + public static Vector2d Scale(Vector2d v1, double scalar) { + return new Vector2d(v1.x * scalar, v1.y * scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Scalar + /// Vector to scale + /// Resulting Vector + public static Vector2d Scale(double scalar, Vector2d v1) { + return Scale(v1, scalar); + } + + /// + /// Multiplies two vectors together componentwise. This operation is commutative. + /// + /// First vector + /// Second vector + /// Resulting vector when the passed vectors' components are multiplied + public static Vector2d Scale(Vector2d v1, Vector2d v2) { + return new Vector2d(v1.x * v2.x, v1.y * v2.y); + } + + /// + /// Vector dot product. This operation is commutative. + /// + /// First vector + /// Second vector + /// Dot product of these two vectors + public static double operator *(Vector2d v1, Vector2d v2) { + return Dot(v1, v2); + } + + /// + /// Vector dot product. This operation is commutative. + /// + /// First vector + /// Second vector + /// Dot product of these two vectors + public static double Dot(Vector2d v1, Vector2d v2) { + return v1.x * v2.x + v1.y * v2.y; + } + + /// + /// Scalar division. Divides all components of by and returns the result. + /// + /// Vector to divide + /// Divisor + /// Resulting vector when all components of are divided by . + public static Vector2d operator /(Vector2d v1, double divisor) { + return Scale(v1, 1.0 / divisor); + } + + /// + /// Equivalency. Returns true if the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are equal or approximately equal. + public static bool operator ==(Vector2d v1, Vector2d v2) { + return v1.Equals(v2); + } + + /// + /// Non-Equivalency. Returns true if the components of two vectors are not equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are not equal or approximately equal. + public static bool operator !=(Vector2d v1, Vector2d v2) { + return !v1.Equals(v2); + } + + /// + /// Equivalency. Returns true if the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are equal or approximately equal. + public bool Equals(Vector2d other) { + return (Math.Abs(x - other.x) < 0.001 && Math.Abs(y - other.y) < 0.001); + } + + /// + /// Equivalency. Returns true if the other object is a vector, and the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the other object is a vector, and the components of two vectors are equal or approximately equal. + public override bool Equals(object obj) { + if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; } + return Equals((Vector2d)obj); + } + + /// + /// Generates a hash code for this instance based on instance data. + /// + /// The hash code for this instance + public override int GetHashCode() { + return x.GetHashCode() ^ y.GetHashCode(); + } + + /// + /// Calculates the distance from this vector to another. + /// + /// Vector to calculate distance to + /// Distance from this vector to the passed vector + public double Distance(Vector2d to) { + return (this - to).magnitude; + } + + /// + /// Gets a human-readable string representation of this vector. + /// + /// Human-readable string representation of this vector + public override string ToString() { + return string.Format("( {0} , {1} )", x.ToString(), y.ToString()); + } + + /// + /// Changes this vector to its normalized version (it will have a magnitude of 1). + /// + public void Normalize() { + double magnitude = this.magnitude; + x /= magnitude; + y /= magnitude; + } + + /// + /// Gets the area of the triangle defined by three points using Heron's formula. + /// + /// First vertex of triangle + /// Second vertex of triangle + /// Third vertex of triangle + /// Area of the triangle defined by these three vertices + public static double TriangleArea(Vector3d p1, Vector3d p2, Vector3d p3) { + return Math.Sqrt(SqrTriangleArea(p1, p2, p3)) / 4.0; + } + + /// + /// Gets the square of the area of the triangle defined by three points. This is useful when simply comparing two areas when you don't need to know exactly what the area is. + /// + /// First vertex of triangle + /// Second vertex of triangle + /// Third vertex of triangle + /// Square of the area of the triangle defined by these three vertices + public static double SqrTriangleArea(Vector3d p1, Vector3d p2, Vector3d p3) { + double a = p1.Distance(p2); + double b = p1.Distance(p3); + double c = p2.Distance(p3); + return 4.0 * a * a * b * b - Math.Pow((a * a) + (b * b) - (c * c), 2); + } + + /// + /// Allows enumeration through the components of a Vector2d using a foreach loop. + /// + public IEnumerator GetEnumerator() { + yield return x; + yield return y; + } + + /// + /// Allows enumeration through the components of a Vector2d using a foreach loop, auto-boxed version. + /// + /// + /// This foreach loop will look like foreach(object o in Vector2d). This will auto-box the doubles in System.Double + /// objects, allocating memory on the heap which the garbage collector will have to free later. In general, iterate + /// through doubles rather than objects. + /// + IEnumerator IEnumerable.GetEnumerator() { + yield return x; + yield return y; + } + + /// + /// Implicitly converts this Vector2d into a Vector3d. This will be called when Vector3d v3 = v2 is used. + /// + /// Vector2d to convert + /// The input vector as a Vector3d, Z component set to 0 + public static implicit operator Vector3d(Vector2d v) { + return new Vector3d(v.x, v.y, 0); + } + + /// + /// Implicitly converts this Vector2d into a Vector4d. This will be called when Vector4d v4 = v2 is used. + /// + /// Vector2d to convert + /// The input vector as a Vector4d, Z and W components set to 0 + public static implicit operator Vector4d(Vector2d v) { + return new Vector4d(v.x, v.y, 0, 0); + } + } +} +#endif diff --git a/LibBSP/Structs/Common/Vector3d.cs b/LibBSP/Structs/Common/Vector3d.cs new file mode 100644 index 0000000..2ce51c6 --- /dev/null +++ b/LibBSP/Structs/Common/Vector3d.cs @@ -0,0 +1,513 @@ +#if !(UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; + +namespace LibBSP { + using Color32 = Color; + /// + /// Holds an array of three doubles representing a 3-dimensional vector. + /// + [Serializable] public struct Vector3d : IEquatable, IEnumerable, IEnumerable { + + /// Returns Vector3d(NaN, NaN, NaN) + public static Vector3d undefined { get { return new Vector3d(System.Double.NaN, System.Double.NaN, System.Double.NaN); } } + /// Returns Vector3d(1, 0, 0) + public static Vector3d right { get { return new Vector3d(1, 0, 0); } } + /// Returns Vector3d(0, 1, 0) + public static Vector3d forward { get { return new Vector3d(0, 1, 0); } } + /// Returns Vector3d(0, 0, 1) + public static Vector3d up { get { return new Vector3d(0, 0, 1); } } + /// Returns Vector3d(-1, 0, 0) + public static Vector3d left { get { return new Vector3d(-1, 0, 0); } } + /// Returns Vector3d(0, -1, 0) + public static Vector3d back { get { return new Vector3d(0, -1, 0); } } + /// Returns Vector3d(0, 0, -1) + public static Vector3d down { get { return new Vector3d(0, 0, -1); } } + /// Returns Vector3d(0, 0, 0) + public static Vector3d zero { get { return new Vector3d(0, 0, 0); } } + /// Returns Vector3d(1, 1, 1) + public static Vector3d one { get { return new Vector3d(1, 1, 1); } } + + public double x; + public double y; + public double z; + + /// + /// Gets or sets a component using an indexer, x=0, y=1, z=2 + /// + /// Component to get or set + /// Component + /// was negative or greater than 2 + public double this[int index] { + get { + switch (index) { + case 0: { + return x; + } + case 1: { + return y; + } + case 2: { + return z; + } + default: { + throw new IndexOutOfRangeException(); + } + } + } + set { + switch (index) { + case 0: { + x = value; + break; + } + case 1: { + y = value; + break; + } + case 2: { + z = value; + break; + } + default: { + throw new IndexOutOfRangeException(); + } + } + } + } + + /// + /// Gets the magnitude of this Vector3d, or its distance from (0, 0, 0). + /// + public double magnitude { get { return Math.Sqrt(sqrMagnitude); } } + + /// + /// Gets the magnitude of this Vector3d squared. This is useful for when you are comparing the lengths of two vectors + /// but don't need to know the exact length, and avoids a square root. + /// + public double sqrMagnitude { get { return System.Math.Pow(x, 2) + System.Math.Pow(y, 2) + System.Math.Pow(z, 2); } } + + /// + /// Gets the normalized version of this Vector3d (unit vector with the same direction). + /// + public Vector3d normalized { + get { + double magnitude = this.magnitude; + return new Vector3d(x / magnitude, y / magnitude, z / magnitude); + } + } + + /// + /// Creates a new Vector3d object using elements in the passed array as components + /// + /// Components of the vector + public Vector3d(params float[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + z = 0; + if (point.Length >= 3) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + z = Convert.ToDouble(point[2]); + } else if (point.Length == 2) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + } else if (point.Length == 1) { + x = Convert.ToDouble(point[0]); + } + } + + /// + /// Creates a new Vector3d object using elements in the passed array as components + /// + /// Components of the vector + public Vector3d(params double[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + z = 0; + if (point.Length >= 3) { + x = point[0]; + y = point[1]; + z = point[2]; + } else if (point.Length == 2) { + x = point[0]; + y = point[1]; + } else if (point.Length == 1) { + x = point[0]; + } + } + + /// + /// Creates a new Vector3d object using elements in the passed array as components + /// + /// Components of the vector + public Vector3d(params int[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + z = 0; + if (point.Length >= 3) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + z = Convert.ToDouble(point[2]); + } else if (point.Length == 2) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + } else if (point.Length == 1) { + x = Convert.ToDouble(point[0]); + } + } + + /// + /// Crates a new Vector3d instance using the components from the supplied Vector3d + /// + /// Vector to copy components from + public Vector3d(Vector3d vector) { + x = vector.x; + y = vector.y; + z = vector.z; + } + + /// + /// Creates a new Vector3d instance by parsing a byte array representing either floats or doubles. + /// + /// Byte array to parse + /// The passed byte array was not 12 or 24 elements long + public Vector3d(params byte[] data) { + if (data == null) { + throw new ArgumentNullException(); + } + if (data.Length == 12) { + x = Convert.ToDouble(BitConverter.ToSingle(data, 0)); + y = Convert.ToDouble(BitConverter.ToSingle(data, 4)); + z = Convert.ToDouble(BitConverter.ToSingle(data, 8)); + } else if (data.Length == 24) { + x = BitConverter.ToDouble(data, 0); + y = BitConverter.ToDouble(data, 8); + z = BitConverter.ToDouble(data, 16); + } else { + throw new System.ArgumentException("Byte array passed to Vector3d Ctor must have 12 or 24 items", "data"); + } + } + + /// + /// Adds two vectors together componentwise. This operation is commutative. + /// + /// First vector to add + /// Second vector to add + /// The resulting vector + public static Vector3d operator +(Vector3d v1, Vector3d v2) { + return Add(v1, v2); + } + + /// + /// Adds two vectors together componentwise. This operation is commutative. + /// + /// First vector to add + /// Second vector to add + /// The resulting vector + public static Vector3d Add(Vector3d v1, Vector3d v2) { + return new Vector3d(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); + } + + /// + /// Subtracts one vector from another. This operation is NOT commutative + /// + /// Vector to subtract from + /// Vector to subtract + /// Difference from to + public static Vector3d operator -(Vector3d v1, Vector3d v2) { + return Subtract(v1, v2); + } + + /// + /// Subtracts one vector from another. This operation is NOT commutative + /// + /// Vector to subtract from + /// Vector to subtract + /// Difference from to + public static Vector3d Subtract(Vector3d v1, Vector3d v2) { + return new Vector3d(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + /// + /// Returns the negative of this vector. Equivalent to (0, 0, 0) - . + /// + /// Vector to negate + /// with all components negated + public static Vector3d operator -(Vector3d v1) { + return Negate(v1); + } + + /// + /// Returns the negative of this vector. Equivalent to (0, 0, 0) - . + /// + /// Vector to negate + /// with all components negated + public static Vector3d Negate(Vector3d v1) { + return new Vector3d(-v1.x, -v1.y, -v1.z); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Vector to scale + /// Scalar + /// Resulting Vector + public static Vector3d operator *(Vector3d v1, double scalar) { + return Scale(v1, scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Scalar + /// Vector to scale + /// Resulting Vector + public static Vector3d operator *(double scalar, Vector3d v1) { + return Scale(v1, scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Vector to scale + /// Scalar + /// Resulting Vector + public static Vector3d Scale(Vector3d v1, double scalar) { + return new Vector3d(v1.x * scalar, v1.y * scalar, v1.z * scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Scalar + /// Vector to scale + /// Resulting Vector + public static Vector3d Scale(double scalar, Vector3d v1) { + return Scale(v1, scalar); + } + + /// + /// Multiplies two vectors together componentwise. This operation is commutative. + /// + /// First vector + /// Second vector + /// Resulting vector when the passed vectors' components are multiplied + public static Vector3d Scale(Vector3d v1, Vector3d v2) { + return new Vector3d(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z); + } + + /// + /// Vector dot product. This operation is commutative. + /// + /// First vector + /// Second vector + /// Dot product of these two vectors + public static double operator *(Vector3d v1, Vector3d v2) { + return Dot(v1, v2); + } + + /// + /// Vector dot product. This operation is commutative. + /// + /// First vector + /// Second vector + /// Dot product of these two vectors + public static double Dot(Vector3d v1, Vector3d v2) { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + } + + /// + /// Vector cross product. This operation is NOT commutative. + /// + /// First vector + /// Second vector + /// Cross product of these two vectors. Can be thought of as the normal to the plane defined by these two vectors. + public static Vector3d operator ^(Vector3d v1, Vector3d v2) { + return Cross(v1, v2); + } + + /// + /// Vector cross product. This operation is NOT commutative. + /// + /// First vector + /// Second vector + /// Cross product of these two vectors. Can be thought of as the normal to the plane defined by these two vectors. + public static Vector3d Cross(Vector3d v1, Vector3d v2) { + return new Vector3d(v1.y * v2.z - v2.y * v1.z, v2.x * v1.z - v1.x * v2.z, v1.x * v2.y - v2.x * v1.y); + } + + /// + /// Scalar division. Divides all components of by and returns the result. + /// + /// Vector to divide + /// Divisor + /// Resulting vector when all components of are divided by . + public static Vector3d operator /(Vector3d v1, double divisor) { + return Scale(v1, 1.0 / divisor); + } + + /// + /// Equivalency. Returns true if the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are equal or approximately equal. + public static bool operator ==(Vector3d v1, Vector3d v2) { + return v1.Equals(v2); + } + + /// + /// Non-Equivalency. Returns true if the components of two vectors are not equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are not equal or approximately equal. + public static bool operator !=(Vector3d v1, Vector3d v2) { + return !v1.Equals(v2); + } + + /// + /// Equivalency. Returns true if the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are equal or approximately equal. + public bool Equals(Vector3d other) { + return (Math.Abs(x - other.x) < 0.001 && Math.Abs(y - other.y) < 0.001 && Math.Abs(z - other.z) < 0.001); + } + + /// + /// Equivalency. Returns true if the other object is a vector, and the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the other object is a vector, and the components of two vectors are equal or approximately equal. + public override bool Equals(object obj) { + if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; } + return Equals((Vector3d)obj); + } + + /// + /// Generates a hash code for this instance based on instance data. + /// + /// The hash code for this instance + public override int GetHashCode() { + return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode(); + } + + /// + /// Calculates the distance from this vector to another. + /// + /// Vector to calculate distance to + /// Distance from this vector to the passed vector + public double Distance(Vector3d to) { + return (this - to).magnitude; + } + + /// + /// Gets a human-readable string representation of this vector. + /// + /// Human-readable string representation of this vector + public override string ToString() { + return string.Format("( {0} , {1} , {2} )", x.ToString(), y.ToString(), z.ToString()); + } + + /// + /// Changes this vector to its normalized version (it will have a magnitude of 1). + /// + public void Normalize() { + double magnitude = this.magnitude; + x /= magnitude; + y /= magnitude; + z /= magnitude; + } + + /// + /// Gets the area of the triangle defined by three points using Heron's formula. + /// + /// First vertex of triangle + /// Second vertex of triangle + /// Third vertex of triangle + /// Area of the triangle defined by these three vertices + public static double TriangleArea(Vector3d p1, Vector3d p2, Vector3d p3) { + return Math.Sqrt(SqrTriangleArea(p1, p2, p3)) / 4.0; + } + + /// + /// Gets the square of the area of the triangle defined by three points. This is useful when simply comparing two areas when you don't need to know exactly what the area is. + /// + /// First vertex of triangle + /// Second vertex of triangle + /// Third vertex of triangle + /// Square of the area of the triangle defined by these three vertices + public static double SqrTriangleArea(Vector3d p1, Vector3d p2, Vector3d p3) { + double a = p1.Distance(p2); + double b = p1.Distance(p3); + double c = p2.Distance(p3); + return 4.0 * a * a * b * b - Math.Pow((a * a) + (b * b) - (c * c), 2); + } + + /// + /// Allows enumeration through the components of a Vector3d using a foreach loop. + /// + public IEnumerator GetEnumerator() { + yield return x; + yield return y; + yield return z; + } + + /// + /// Allows enumeration through the components of a Vector3d using a foreach loop, auto-boxed version. + /// + /// + /// This foreach loop will look like foreach(object o in Vector3d). This will auto-box the doubles in System.Double + /// objects, allocating memory on the heap which the garbage collector will have to free later. In general, iterate + /// through doubles rather than objects. + /// + IEnumerator IEnumerable.GetEnumerator() { + yield return x; + yield return y; + yield return z; + } + + /// + /// Implicitly converts this Vector3d into a Vector2d. This will be called when Vector2d v2 = v3 is used. + /// + /// Vector3d to convert + /// The input vector as a Vector2d, Z component discarded + public static implicit operator Vector2d(Vector3d v) { + return new Vector2d(v.x, v.y); + } + + /// + /// Implicitly converts this Vector3d into a Vector4d. This will be called when Vector4d v4 = v3 is used. + /// + /// Vector3d to convert + /// The input vector as a Vector4d, W component set to 0 + public static implicit operator Vector4d(Vector3d v) { + return new Vector4d(v.x, v.y, v.z, 0); + } + + /// + /// Implicitly converts this Vector3d into a Color by interpreting (x, y, z) as (r, g, b) respectively. + /// + /// Vector3d to convert + /// This Vector3d in a Color object interpreted as RGB. + public static implicit operator Color32(Vector3d v) { + return Color32Extensions.FromArgb(255, (int)Math.Max(v.x, 255), (int)Math.Max(v.y, 255), (int)Math.Max(v.z, 255)); + } + } +} +#endif diff --git a/LibBSP/Structs/Common/Vector4d.cs b/LibBSP/Structs/Common/Vector4d.cs new file mode 100644 index 0000000..b975361 --- /dev/null +++ b/LibBSP/Structs/Common/Vector4d.cs @@ -0,0 +1,514 @@ +#if !(UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; + +namespace LibBSP { + using Color32 = Color; + /// + /// Holds an array of four doubles representing a 4-dimensional vector. + /// + [Serializable] public struct Vector4d : IEquatable, IEnumerable, IEnumerable { + + /// Returns Vector4d(NaN, NaN, NaN) + public static Vector4d undefined { get { return new Vector4d(System.Double.NaN, System.Double.NaN, System.Double.NaN, System.Double.NaN); } } + /// Returns Vector4d(0, 0, 0, 0) + public static Vector4d zero { get { return new Vector4d(0, 0, 0, 0); } } + /// Returns Vector4d(1, 1, 1, 1) + public static Vector4d one { get { return new Vector4d(1, 1, 1, 1); } } + + public double x; + public double y; + public double z; + public double w; + + /// + /// Gets or sets a component using an indexer, x=0, y=1, z=2, w=3 + /// + /// Component to get or set + /// Component + /// was negative or greater than 3 + public double this[int index] { + get { + switch (index) { + case 0: { + return x; + } + case 1: { + return y; + } + case 2: { + return z; + } + case 3: { + return w; + } + default: { + throw new IndexOutOfRangeException(); + } + } + } + set { + switch (index) { + case 0: { + x = value; + break; + } + case 1: { + y = value; + break; + } + case 2: { + z = value; + break; + } + case 3: { + w = value; + break; + } + default: { + throw new IndexOutOfRangeException(); + } + } + } + } + + /// + /// Gets the magnitude of this Vector4d, or its distance from (0, 0, 0, 0). + /// + public double magnitude { get { return Math.Sqrt(sqrMagnitude); } } + + /// + /// Gets the magnitude of this Vector4d squared. This is useful for when you are comparing the lengths of two vectors + /// but don't need to know the exact length, and avoids a square root. + /// + public double sqrMagnitude { get { return System.Math.Pow(x, 2) + System.Math.Pow(y, 2) + System.Math.Pow(z, 2) + System.Math.Pow(w, 2); } } + + /// + /// Gets the normalized version of this Vector4d (unit vector with the same direction). + /// + public Vector4d normalized { + get { + double magnitude = this.magnitude; + return new Vector4d(x / magnitude, y / magnitude, z / magnitude, w / magnitude); + } + } + + /// + /// Creates a new Vector4d object using elements in the passed array as components + /// + /// Components of the vector + public Vector4d(params float[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + z = 0; + w = 0; + if (point.Length >= 4) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + z = Convert.ToDouble(point[2]); + w = Convert.ToDouble(point[3]); + } else if (point.Length == 3) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + z = Convert.ToDouble(point[2]); + } else if (point.Length == 2) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + } else if (point.Length == 1) { + x = Convert.ToDouble(point[0]); + } + } + + /// + /// Creates a new Vector4d object using elements in the passed array as components + /// + /// Components of the vector + public Vector4d(params double[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + z = 0; + w = 0; + if (point.Length >= 4) { + x = point[0]; + y = point[1]; + z = point[2]; + w = point[3]; + } else if (point.Length == 3) { + x = point[0]; + y = point[1]; + z = point[2]; + } else if (point.Length == 2) { + x = point[0]; + y = point[1]; + } else if (point.Length == 1) { + x = point[0]; + } + } + + /// + /// Creates a new Vector4d object using elements in the passed array as components + /// + /// Components of the vector + public Vector4d(params int[] point) { + if (point == null) { + throw new ArgumentNullException(); + } + x = 0; + y = 0; + z = 0; + w = 0; + if (point.Length >= 4) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + z = Convert.ToDouble(point[2]); + w = Convert.ToDouble(point[2]); + } else if (point.Length == 3) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + z = Convert.ToDouble(point[2]); + } else if (point.Length == 2) { + x = Convert.ToDouble(point[0]); + y = Convert.ToDouble(point[1]); + } else if (point.Length == 1) { + x = Convert.ToDouble(point[0]); + } + } + + /// + /// Crates a new Vector4d instance using the components from the supplied Vector4d + /// + /// Vector to copy components from + public Vector4d(Vector4d vector) { + x = vector.x; + y = vector.y; + z = vector.z; + w = vector.w; + } + + /// + /// Creates a new Vector4d instance by parsing a byte array representing either floats or doubles. + /// + /// Byte array to parse + /// The passed byte array was not 16 or 32 elements long + public Vector4d(params byte[] data) { + if (data == null) { + throw new ArgumentNullException(); + } + if (data.Length == 16) { + x = Convert.ToDouble(BitConverter.ToSingle(data, 0)); + y = Convert.ToDouble(BitConverter.ToSingle(data, 4)); + z = Convert.ToDouble(BitConverter.ToSingle(data, 8)); + w = Convert.ToDouble(BitConverter.ToSingle(data, 12)); + } else if (data.Length == 32) { + x = BitConverter.ToDouble(data, 0); + y = BitConverter.ToDouble(data, 8); + z = BitConverter.ToDouble(data, 16); + w = BitConverter.ToDouble(data, 24); + } else { + throw new System.ArgumentException("Byte array passed to Vector3d Ctor must have 16 or 32 items", "data"); + } + } + + /// + /// Adds two vectors together componentwise. This operation is commutative. + /// + /// First vector to add + /// Second vector to add + /// The resulting vector + public static Vector4d operator +(Vector4d v1, Vector4d v2) { + return Add(v1, v2); + } + + /// + /// Adds two vectors together componentwise. This operation is commutative. + /// + /// First vector to add + /// Second vector to add + /// The resulting vector + public static Vector4d Add(Vector4d v1, Vector4d v2) { + return new Vector4d(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z, v1.w + v2.w); + } + + /// + /// Subtracts one vector from another. This operation is NOT commutative + /// + /// Vector to subtract from + /// Vector to subtract + /// Difference from to + public static Vector4d operator -(Vector4d v1, Vector4d v2) { + return Subtract(v1, v2); + } + + /// + /// Subtracts one vector from another. This operation is NOT commutative + /// + /// Vector to subtract from + /// Vector to subtract + /// Difference from to + public static Vector4d Subtract(Vector4d v1, Vector4d v2) { + return new Vector4d(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z, v1.w - v2.w); + } + + /// + /// Returns the negative of this vector. Equivalent to (0, 0, 0, 0) - . + /// + /// Vector to negate + /// with all components negated + public static Vector4d operator -(Vector4d v1) { + return Negate(v1); + } + + /// + /// Returns the negative of this vector. Equivalent to (0, 0, 0, 0) - . + /// + /// Vector to negate + /// with all components negated + public static Vector4d Negate(Vector4d v1) { + return new Vector4d(-v1.x, -v1.y, -v1.z, -v1.w); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Vector to scale + /// Scalar + /// Resulting Vector + public static Vector4d operator *(Vector4d v1, double scalar) { + return Scale(v1, scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Scalar + /// Vector to scale + /// Resulting Vector + public static Vector4d operator *(double scalar, Vector4d v1) { + return Scale(v1, scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Vector to scale + /// Scalar + /// Resulting Vector + public static Vector4d Scale(Vector4d v1, double scalar) { + return new Vector4d(v1.x * scalar, v1.y * scalar, v1.z * scalar, v1.w * scalar); + } + + /// + /// Scalar multiplication. Multiplies all components of by and returns the result. + /// + /// Scalar + /// Vector to scale + /// Resulting Vector + public static Vector4d Scale(double scalar, Vector4d v1) { + return Scale(v1, scalar); + } + + /// + /// Multiplies two vectors together componentwise. This operation is commutative. + /// + /// First vector + /// Second vector + /// Resulting vector when the passed vectors' components are multiplied + public static Vector4d Scale(Vector4d v1, Vector4d v2) { + return new Vector4d(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z, v1.w * v2.w); + } + + /// + /// Vector dot product. This operation is commutative. + /// + /// First vector + /// Second vector + /// Dot product of these two vectors + public static double operator *(Vector4d v1, Vector4d v2) { + return Dot(v1, v2); + } + + /// + /// Vector dot product. This operation is commutative. + /// + /// First vector + /// Second vector + /// Dot product of these two vectors + public static double Dot(Vector4d v1, Vector4d v2) { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z + v1.w * v2.w; + } + + /// + /// Scalar division. Divides all components of by and returns the result. + /// + /// Vector to divide + /// Divisor + /// Resulting vector when all components of are divided by . + public static Vector4d operator /(Vector4d v1, double divisor) { + return Scale(v1, 1.0 / divisor); + } + + /// + /// Equivalency. Returns true if the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are equal or approximately equal. + public static bool operator ==(Vector4d v1, Vector4d v2) { + return v1.Equals(v2); + } + + /// + /// Non-Equivalency. Returns true if the components of two vectors are not equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are not equal or approximately equal. + public static bool operator !=(Vector4d v1, Vector4d v2) { + return !v1.Equals(v2); + } + + /// + /// Equivalency. Returns true if the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the components of two vectors are equal or approximately equal. + public bool Equals(Vector4d other) { + return (Math.Abs(x - other.x) < 0.001 && Math.Abs(y - other.y) < 0.001 && Math.Abs(z - other.z) < 0.001 && Math.Abs(w - other.w) < 0.001); + } + + /// + /// Equivalency. Returns true if the other object is a vector, and the components of two vectors are equal or approximately equal. + /// + /// First vector + /// Second vector + /// true if the other object is a vector, and the components of two vectors are equal or approximately equal. + public override bool Equals(object obj) { + if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; } + return Equals((Vector4d)obj); + } + + /// + /// Generates a hash code for this instance based on instance data. + /// + /// The hash code for this instance + public override int GetHashCode() { + return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); + } + + /// + /// Calculates the distance from this vector to another. + /// + /// Vector to calculate distance to + /// Distance from this vector to the passed vector + public double Distance(Vector4d to) { + return (this - to).magnitude; + } + + /// + /// Gets a human-readable string representation of this vector. + /// + /// Human-readable string representation of this vector + public override string ToString() { + return string.Format("( {0} , {1} , {2} , {3} )", x.ToString(), y.ToString(), z.ToString(), w.ToString()); + } + + /// + /// Changes this vector to its normalized version (it will have a magnitude of 1). + /// + public void Normalize() { + double magnitude = this.magnitude; + x /= magnitude; + y /= magnitude; + z /= magnitude; + w /= magnitude; + } + + /// + /// Gets the area of the triangle defined by three points using Heron's formula. + /// + /// First vertex of triangle + /// Second vertex of triangle + /// Third vertex of triangle + /// Area of the triangle defined by these three vertices + public static double TriangleArea(Vector4d p1, Vector4d p2, Vector4d p3) { + return Math.Sqrt(SqrTriangleArea(p1, p2, p3)) / 4.0; + } + + /// + /// Gets the square of the area of the triangle defined by three points. This is useful when simply comparing two areas when you don't need to know exactly what the area is. + /// + /// First vertex of triangle + /// Second vertex of triangle + /// Third vertex of triangle + /// Square of the area of the triangle defined by these three vertices + public static double SqrTriangleArea(Vector4d p1, Vector4d p2, Vector4d p3) { + double a = p1.Distance(p2); + double b = p1.Distance(p3); + double c = p2.Distance(p3); + return 4.0 * a * a * b * b - Math.Pow((a * a) + (b * b) - (c * c), 2); + } + + /// + /// Allows enumeration through the components of a Vector4d using a foreach loop. + /// + public IEnumerator GetEnumerator() { + yield return x; + yield return y; + yield return z; + yield return w; + } + + /// + /// Allows enumeration through the components of a Vector4d using a foreach loop, auto-boxed version. + /// + /// + /// This foreach loop will look like foreach(object o in Vector4d). This will auto-box the doubles in System.Double + /// objects, allocating memory on the heap which the garbage collector will have to free later. In general, iterate + /// through doubles rather than objects. + /// + IEnumerator IEnumerable.GetEnumerator() { + yield return x; + yield return y; + yield return z; + yield return w; + } + + /// + /// Implicitly converts this Vector4d into a Vector2d. This will be called when Vector2d v2 = v4 is used. + /// + /// Vector4d to convert + /// The input vector as a Vector2d, Z and W components discarded + public static implicit operator Vector2d(Vector4d v) { + return new Vector2d(v.x, v.y); + } + + /// + /// Implicitly converts this Vector4d into a Vector3d. This will be called when Vector3d v3 = v4 is used. + /// + /// Vector4d to convert + /// The input vector as a Vector3d, W component discarded + public static implicit operator Vector3d(Vector4d v) { + return new Vector3d(v.x, v.y, v.z); + } + + /// + /// Implicitly converts this Vector4d into a Color by interpreting (x, y, z) as (r, g, b) respectively, and w as alpha. + /// Assumes colors range from 0 to 255. + /// + /// Vector4d to convert + /// This Vector4d in a Color object interpreted as RGBA. + public static implicit operator Color32(Vector4d v) { + return Color32Extensions.FromArgb((int)Math.Max(v.w, 255), (int)Math.Max(v.x, 255), (int)Math.Max(v.y, 255), (int)Math.Max(v.z, 255)); + } + } +} +#endif diff --git a/LibBSP/Structs/Doom/DoomMap.cs b/LibBSP/Structs/Doom/DoomMap.cs new file mode 100644 index 0000000..f316681 --- /dev/null +++ b/LibBSP/Structs/Doom/DoomMap.cs @@ -0,0 +1,72 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP.Doom { + /// + /// Gathers all relevant information from the lumps of a Doom Map. + /// + public class DoomMap { + + // Since all Doom engine maps were incorporated into the WAD, we need to keep + // track of both the location of the WAD file and the internal name of the map. + public string wadPath { get; private set; } + public string mapName { get; private set; } + public MapType version { get; private set; } + + public List things; + public List linedefs; + public List sidedefs; + public List vertices; + public List segs; + public List subsectors; + public List nodes; + public List sectors; + + /// + /// Gets the folder path for the WAD containing this map + /// + public string Folder { + get { + int i; + for (i = 0; i < wadPath.Length; ++i) { + if (wadPath[wadPath.Length - 1 - i] == '\\') { + break; + } + if (wadPath[wadPath.Length - 1 - i] == '/') { + break; + } + } + return wadPath.Substring(0, (wadPath.Length - i) - (0)); + } + } + + /// + /// Gets the name of the WAD file containing this map + /// + public string WadName { + get { + System.IO.FileInfo newFile = new System.IO.FileInfo(wadPath); + return newFile.Name.Substring(0, (newFile.Name.Length - 4) - (0)); + } + } + + /// + /// Creates a new DoomMap instance with no initial data. The Lists in this class must be populated elsewhere. + /// + /// The path to the .WAD file + /// The name of the map within the WAD file + /// The version of the map + public DoomMap(string wadpath, string map, MapType version) { + this.wadPath = wadpath; + this.mapName = map; + this.version = version; + } + } +} diff --git a/LibBSP/Structs/Doom/Linedef.cs b/LibBSP/Structs/Doom/Linedef.cs new file mode 100644 index 0000000..67d186b --- /dev/null +++ b/LibBSP/Structs/Doom/Linedef.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP.Doom { + /// + /// Contains all necessary information for a Doom LINEDEF object. + /// + /// + /// The linedef is a strange animal. It roughly equates to the Planes of other + /// BSP formats, but also defines what sectors are on what side. + /// + public struct Linedef { + + public short start { get; private set; } + public short end { get; private set; } + public short flags { get; private set; } + public short action { get; private set; } + public short tag { get; private set; } + public short right { get; private set; } + public short left { get; private set; } + public byte[] arguments { get; private set; } + + public bool oneSided { + get { + return (right == -1 || left == -1); + } + } + + /// + /// Creates a new Linedef object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Linedef(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + start = BitConverter.ToInt16(data, 0); + end = BitConverter.ToInt16(data, 2); + flags = BitConverter.ToInt16(data, 4); + action = -1; + tag = -1; + right = -1; + left = -1; + arguments = new byte[5]; + switch (type) { + case MapType.Doom: { + action = BitConverter.ToInt16(data, 6); + tag = BitConverter.ToInt16(data, 8); + right = BitConverter.ToInt16(data, 10); + left = BitConverter.ToInt16(data, 12); + break; + } + case MapType.Hexen: { + action = data[6]; + arguments[0] = data[7]; + arguments[1] = data[8]; + arguments[2] = data[9]; + arguments[3] = data[10]; + arguments[4] = data[11]; + right = BitConverter.ToInt16(data, 12); + left = BitConverter.ToInt16(data, 14); + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Linedef class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Linedef objects. + /// + /// The data to parse + /// The map type + /// A List of Linedef objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Doom: { + structLength = 14; + break; + } + case MapType.Hexen: { + structLength = 16; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Linedef lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Linedef(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/Doom/Node.cs b/LibBSP/Structs/Doom/Node.cs new file mode 100644 index 0000000..ebd3456 --- /dev/null +++ b/LibBSP/Structs/Doom/Node.cs @@ -0,0 +1,73 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP.Doom { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Holds all the data for a node in a Doom map. + /// + /// + /// This is the one lump that has a structure similar to future BSPs. + /// + public struct Node { + // This format uses a vector head and tail for partitioning, rather + // than the 3D plane conventionally used by more modern engines. + // The "tail" is actually a change in X and Y, rather than an explicitly defined point. + public Vector3 vecHead { get; private set; } + public Vector3 vecTail { get; private set; } + // These rectangles are defined by + // Top, Bottom, Left, Right. That's YMax, YMin, XMin, XMax, in that order + public Rect RRectangle { get; private set; } + public Rect LRectangle { get; private set; } + public short RChild { get; private set; } + public short LChild { get; private set; } + + /// + /// Creates a new Node object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + public Node(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + vecHead = new Vector3(BitConverter.ToInt16(data, 0), BitConverter.ToInt16(data, 2)); + vecTail = new Vector3(BitConverter.ToInt16(data, 4), BitConverter.ToInt16(data, 6)); + RRectangle = Rect.MinMaxRect(BitConverter.ToInt16(data, 12), BitConverter.ToInt16(data, 8), BitConverter.ToInt16(data, 14), BitConverter.ToInt16(data, 10)); + LRectangle = Rect.MinMaxRect(BitConverter.ToInt16(data, 20), BitConverter.ToInt16(data, 16), BitConverter.ToInt16(data, 22), BitConverter.ToInt16(data, 18)); + this.RChild = BitConverter.ToInt16(data, 24); + this.LChild = BitConverter.ToInt16(data, 26); + } + + /// + /// Factory method to parse a byte array into a List of Node objects. + /// + /// The data to parse + /// The map type + /// A List of Node objects + /// was null + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 28; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Node(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/Doom/Sector.cs b/LibBSP/Structs/Doom/Sector.cs new file mode 100644 index 0000000..8b9eb26 --- /dev/null +++ b/LibBSP/Structs/Doom/Sector.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP.Doom { + /// + /// Contains all necessary information for a Doom SECTOR object. + /// + /// + /// The sector defines an area, the heights of the floor and cieling + /// of the area, the floor and cieling textures, the light level, the + /// type of sector and a tag number. + /// + public struct Sector { + + public short floor { get; private set; } + public short cieling { get; private set; } + + public string floorTexture { get; private set; } + public string cielingTexture { get; private set; } + + public short light { get; private set; } + public short type { get; private set; } + public short tag { get; private set; } + + /// + /// Creates a new Sector object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + public Sector(byte[] data, MapType type) : this() { + this.floor = BitConverter.ToInt16(data, 0); + this.cieling = BitConverter.ToInt16(data, 2); + this.floorTexture = data.ToNullTerminatedString(4, 8); + this.cielingTexture = data.ToNullTerminatedString(12, 8); + this.light = BitConverter.ToInt16(data, 20); + this.type = BitConverter.ToInt16(data, 22); + this.tag = BitConverter.ToInt16(data, 24); + } + + /// + /// Factory method to parse a byte array into a List of Sector objects. + /// + /// The data to parse + /// The map type + /// A List of Sector objects + /// was null + public static List LumpFactory(byte[] data, MapType type) { + int structLength = 26; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Sector(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/Doom/Segment.cs b/LibBSP/Structs/Doom/Segment.cs new file mode 100644 index 0000000..02da87b --- /dev/null +++ b/LibBSP/Structs/Doom/Segment.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace LibBSP.Doom { + /// + /// This class holds data of a single Doom line Segment + /// + public struct Segment { + + public short startVertex { get; private set; } + public short endVertex { get; private set; } + public short angle { get; private set; } + public short lineDef { get; private set; } + // 0 for right side of linedef, 1 for left + public short direction { get; private set; } + public short dist { get; private set; } + + /// + /// Creates a new Segment object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + public Segment(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + this.startVertex = BitConverter.ToInt16(data, 0); + this.endVertex = BitConverter.ToInt16(data, 2); + this.angle = BitConverter.ToInt16(data, 4); + this.lineDef = BitConverter.ToInt16(data, 6); + this.direction = BitConverter.ToInt16(data, 8); + this.dist = BitConverter.ToInt16(data, 10); + } + + /// + /// Factory method to parse a byte array into a List of Segment objects. + /// + /// The data to parse + /// The map type + /// A List of Segment objects + /// was null + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 12; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Segment(bytes, type)); + } + return lump; + } + + } +} diff --git a/LibBSP/Structs/Doom/Sidedef.cs b/LibBSP/Structs/Doom/Sidedef.cs new file mode 100644 index 0000000..4520ae1 --- /dev/null +++ b/LibBSP/Structs/Doom/Sidedef.cs @@ -0,0 +1,70 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP.Doom { +#if !UNITY + using Vector2 = Vector2d; +#endif + + /// + /// Contains all necessary information for a Doom SIDEDEF object. + /// + /// + /// The sidedef is roughly equivalent to the Face (or surface) + /// object in later BSP versions. + /// + public struct Sidedef { + + public Vector2 offsets { get; private set; } + public string highTexture { get; private set; } + public string midTexture { get; private set; } + public string lowTexture { get; private set; } + public short sector { get; private set; } + + /// + /// Creates a new Sidedef object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + public Sidedef(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + offsets = new Vector2(BitConverter.ToInt16(data, 0), BitConverter.ToInt16(data, 2)); + highTexture = data.ToNullTerminatedString(4, 8); + midTexture = data.ToNullTerminatedString(12, 8); + lowTexture = data.ToNullTerminatedString(20, 8); + sector = BitConverter.ToInt16(data, 28); + } + + /// + /// Factory method to parse a byte array into a List of Sidedef objects. + /// + /// The data to parse + /// The map type + /// A List of Sidedef objects + /// was null + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 30; + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Sidedef(bytes, type)); + } + return lump; + } + + } +} diff --git a/LibBSP/Structs/Doom/Thing.cs b/LibBSP/Structs/Doom/Thing.cs new file mode 100644 index 0000000..9df9462 --- /dev/null +++ b/LibBSP/Structs/Doom/Thing.cs @@ -0,0 +1,104 @@ +#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 +#define UNITY +#endif + +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP.Doom { +#if !UNITY + using Vector2 = Vector2d; + using Vector3 = Vector3d; +#endif + /// + /// Contains all necessary information for a Doom THING object. Essentially Doom's entities. + /// + public struct Thing { + + public Vector3 origin { get; private set; } + public short angle { get; private set; } + public short classNum { get; private set; } + public short flags { get; private set; } + + public short id { get; private set; } + public byte action { get; private set; } + public byte[] arguments { get; private set; } + + /// + /// Creates a new Thing object from a byte array. + /// + /// byte array to parse + /// The map type + /// was null + /// This structure is not implemented for the given maptype + public Thing(byte[] data, MapType type) : this() { + if (data == null) { + throw new ArgumentNullException(); + } + switch (type) { + case MapType.Doom: { + origin = new Vector2(BitConverter.ToInt16(data, 0), BitConverter.ToInt16(data, 2)); + this.angle = BitConverter.ToInt16(data, 4); + this.classNum = BitConverter.ToInt16(data, 6); + this.flags = BitConverter.ToInt16(data, 8); + break; + } + case MapType.Hexen: { + id = BitConverter.ToInt16(data, 0); + origin = new Vector3(BitConverter.ToInt16(data, 2), BitConverter.ToInt16(data, 4), BitConverter.ToInt16(data, 6)); + this.angle = BitConverter.ToInt16(data, 8); + this.classNum = BitConverter.ToInt16(data, 10); + this.flags = BitConverter.ToInt16(data, 12); + action = data[14]; + arguments[0] = data[15]; + arguments[1] = data[16]; + arguments[2] = data[17]; + arguments[3] = data[18]; + arguments[4] = data[19]; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Thing class."); + } + } + } + + /// + /// Factory method to parse a byte array into a List of Thing objects. + /// + /// The data to parse + /// The map type + /// A List of Thing objects + /// was null + /// This structure is not implemented for the given maptype + public static List LumpFactory(byte[] data, MapType type) { + if (data == null) { + throw new ArgumentNullException(); + } + int structLength = 0; + switch (type) { + case MapType.Doom: { + structLength = 10; + break; + } + case MapType.Hexen: { + structLength = 20; + break; + } + default: { + throw new ArgumentException("Map type " + type + " isn't supported by the Thing lump factory."); + } + } + List lump = new List(data.Length / structLength); + byte[] bytes = new byte[structLength]; + for (int i = 0; i < data.Length / structLength; ++i) { + Array.Copy(data, i * structLength, bytes, 0, structLength); + lump.Add(new Thing(bytes, type)); + } + return lump; + } + } +} diff --git a/LibBSP/Structs/MAP/MAPBrush.cs b/LibBSP/Structs/MAP/MAPBrush.cs new file mode 100644 index 0000000..9fa997c --- /dev/null +++ b/LibBSP/Structs/MAP/MAPBrush.cs @@ -0,0 +1,73 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +using System.Collections.Generic; + +namespace LibBSP { + /// + /// Class containing all data for a single brush, including side definitions or a patch definition + /// + public class MAPBrush { + + public List sides = new List(6); + public MAPPatch patch; + + public bool isDetail { get; private set; } + + /// + /// Creates a new MAPBrush object using the supplied string array as data + /// + /// Data to parse + public MAPBrush(string[] lines) { + int braceCount = 0; + bool brushDef3 = false; + bool inPatch = false; + List child = new List(); + foreach (string line in lines) { + if (line[0] == '{') { + braceCount++; + if (braceCount == 1 || brushDef3) { continue; } + } else if (line[0] == '}') { + braceCount--; + if (braceCount == 0 || brushDef3) { continue; } + } + + if (braceCount == 1 || brushDef3) { + // Source engine + if (line.Length >= "side".Length && line.Substring(0, "side".Length) == "side") { + continue; + } + // id Tech does this kinda thing + else if (line.Length >= "patch".Length && line.Substring(0, "patch".Length) == "patch") { + inPatch = true; + // Gonna need this line too. We can switch on the type of patch definition, make things much easier. + child.Add(line); + continue; + } else if (inPatch) { + child.Add(line); + inPatch = false; + patch = new MAPPatch(child.ToArray()); + child = new List(); + continue; + } else if (line.Length >= "brushDef3".Length && line.Substring(0, "brushDef3".Length) == "brushDef3") { + brushDef3 = true; + continue; + } else if (line == "\"BRUSHFLAGS\" \"DETAIL\"") { + isDetail = true; + continue; + } else if (line.Length >= "\"id\"".Length && line.Substring(0, "\"id\"".Length) == "\"id\"") { + continue; + } else { + child.Add(line); + sides.Add(new MAPBrushSide(child.ToArray())); + child = new List(); + } + } else if (braceCount > 1) { + child.Add(line); + } + } + } + + } +} diff --git a/LibBSP/Structs/MAP/MAPBrushSide.cs b/LibBSP/Structs/MAP/MAPBrushSide.cs new file mode 100644 index 0000000..7dddfa6 --- /dev/null +++ b/LibBSP/Structs/MAP/MAPBrushSide.cs @@ -0,0 +1,150 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +using System.Collections.Generic; +using System; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Class containing data for a brush side. Please note vertices must be set manually or generated through CSG + /// + public class MAPBrushSide { + + public Vector3[] vertices; + public Plane plane; + public string texture; + public Vector3 textureS; + public double textureShiftS; + public Vector3 textureT; + public double textureShiftT; + public float texRot; + public double texScaleX; + public double texScaleY; + public int flags; + public string material; + public double lgtScale; + public double lgtRot; + public int id; + public MAPDisplacement displacement; + + /// + /// Constructs a MAPBrushSide object using the provided string array as the data. + /// + /// Data to parse + public MAPBrushSide(string[] lines) { + // If lines.Length is 1, then this line contains all data for a brush side + if (lines.Length == 1) { + string[] tokens = lines[0].SplitUnlessInContainer(' ', '\"', StringSplitOptions.RemoveEmptyEntries); + + float dist = 0; + + // If this succeeds, assume brushDef3 + if (Single.TryParse(tokens[4], out dist)) { + plane = new Plane(new Vector3(Single.Parse(tokens[1]), Single.Parse(tokens[2]), Single.Parse(tokens[3])), dist); + textureS = new Vector3(Single.Parse(tokens[8]), Single.Parse(tokens[9]), Single.Parse(tokens[10])); + textureT = new Vector3(Single.Parse(tokens[13]), Single.Parse(tokens[14]), Single.Parse(tokens[15])); + texture = tokens[18]; + } else { + Vector3 v1 = new Vector3(Single.Parse(tokens[1]), Single.Parse(tokens[2]), Single.Parse(tokens[3])); + Vector3 v2 = new Vector3(Single.Parse(tokens[6]), Single.Parse(tokens[7]), Single.Parse(tokens[8])); + Vector3 v3 = new Vector3(Single.Parse(tokens[11]), Single.Parse(tokens[12]), Single.Parse(tokens[13])); + plane = new Plane(v1, v2, v3); + texture = tokens[15]; + // GearCraft + if (tokens[16] == "[") { + textureS = new Vector3(Single.Parse(tokens[17]), Single.Parse(tokens[18]), Single.Parse(tokens[19])); + textureShiftS = Double.Parse(tokens[20]); + textureT = new Vector3(Single.Parse(tokens[23]), Single.Parse(tokens[24]), Single.Parse(tokens[25])); + textureShiftT = Double.Parse(tokens[26]); + texRot = Single.Parse(tokens[28]); + texScaleX = Double.Parse(tokens[29]); + texScaleY = Double.Parse(tokens[30]); + flags = Int32.Parse(tokens[31]); + material = tokens[32]; + } else { + // + textureShiftS = Single.Parse(tokens[16]); + textureShiftT = Single.Parse(tokens[17]); + texRot = Single.Parse(tokens[18]); + texScaleX = Double.Parse(tokens[19]); + texScaleY = Double.Parse(tokens[20]); + flags = Int32.Parse(tokens[22]); + } + } + } else { + bool inDispInfo = false; + int braceCount = 0; + List child = new List(37); + foreach (string line in lines) { + if (line == "{") { + ++braceCount; + } else if (line == "}") { + --braceCount; + if (braceCount == 1) { + child.Add(line); + displacement = new MAPDisplacement(child.ToArray()); + child = new List(37); + inDispInfo = false; + } + } else if (line == "dispinfo") { + inDispInfo = true; + continue; + } + + if (braceCount == 1) { + string[] tokens = line.SplitUnlessInContainer(' ', '\"', StringSplitOptions.RemoveEmptyEntries); + switch (tokens[0]) { + case "material": { + texture = tokens[1]; + break; + } + case "plane": { + string[] points = tokens[1].SplitUnlessBetweenDelimiters(' ', '(', ')', StringSplitOptions.RemoveEmptyEntries); + string[] components = points[0].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + Vector3 v1 = new Vector3(Single.Parse(components[0]), Single.Parse(components[1]), Single.Parse(components[2])); + components = points[1].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + Vector3 v2 = new Vector3(Single.Parse(components[0]), Single.Parse(components[1]), Single.Parse(components[2])); + components = points[2].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + Vector3 v3 = new Vector3(Single.Parse(components[0]), Single.Parse(components[1]), Single.Parse(components[2])); + plane = new Plane(v1, v2, v3); + break; + } + case "uaxis": { + string[] split = tokens[1].SplitUnlessBetweenDelimiters(' ', '[', ']', StringSplitOptions.RemoveEmptyEntries); + texScaleX = Single.Parse(split[1]); + split = split[0].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + textureS = new Vector3(Single.Parse(split[0]), Single.Parse(split[1]), Single.Parse(split[2])); + textureShiftS = Single.Parse(split[3]); + break; + } + case "vaxis": { + string[] split = tokens[1].SplitUnlessBetweenDelimiters(' ', '[', ']', StringSplitOptions.RemoveEmptyEntries); + texScaleY = Single.Parse(split[1]); + split = split[0].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + textureT = new Vector3(Single.Parse(split[0]), Single.Parse(split[1]), Single.Parse(split[2])); + textureShiftT = Single.Parse(split[3]); + break; + } + case "rotation": { + texRot = Single.Parse(tokens[1]); + break; + } + } + } else if (braceCount > 1) { + if (inDispInfo) { + child.Add(line); + } + } + } + } + } + + } +} diff --git a/LibBSP/Structs/MAP/MAPDisplacement.cs b/LibBSP/Structs/MAP/MAPDisplacement.cs new file mode 100644 index 0000000..6ff2edf --- /dev/null +++ b/LibBSP/Structs/MAP/MAPDisplacement.cs @@ -0,0 +1,128 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif + +using System; +using System.Collections.Generic; +using System.Linq; +#if UNITY +using UnityEngine; +#endif + +namespace LibBSP { +#if !UNITY + using Vector3 = Vector3d; +#endif + /// + /// Class containing all data necessary to render a displacement from Source engine. + /// + public struct MAPDisplacement { + + public int power; + public Vector3 start; + public Vector3[][] normals; + public float[][] distances; + public float[][] alphas; + + /// + /// Constructs a MAPDisplacement object using the provided string array as the data. + /// + /// Data to parse + public MAPDisplacement(string[] lines) { + power = 0; + start = new Vector3(Single.NaN, Single.NaN, Single.NaN); + normals = null; + distances = null; + alphas = null; + Dictionary normalsTokens = new Dictionary(5); + Dictionary distancesTokens = new Dictionary(5); + Dictionary alphasTokens = new Dictionary(5); + int braceCount = 0; + bool inNormals = false; + bool inDistances = false; + bool inAlphas = false; + foreach (string line in lines) { + if (line == "{") { + ++braceCount; + continue; + } else if (line == "}") { + --braceCount; + if (braceCount == 1) { + inNormals = false; + inDistances = false; + inAlphas = false; + } + continue; + } else if (line == "normals") { + inNormals = true; + continue; + } else if (line == "distances") { + inDistances = true; + continue; + } else if (line == "alphas") { + inAlphas = true; + continue; + } + + if (braceCount == 1) { + string[] tokens = line.SplitUnlessInContainer(' ', '\"', StringSplitOptions.RemoveEmptyEntries); + switch (tokens[0]) { + case "power": { + power = Int32.Parse(tokens[1]); + int side = (int)Math.Pow(2, power) + 1; + normals = new Vector3[side][]; + distances = new float[side][]; + alphas = new float[side][]; + for (int i = 0; i < side; ++i) { + normals[i] = new Vector3[side]; + distances[i] = new float[side]; + alphas[i] = new float[side]; + } + break; + } + case "startposition": { + string[] point = tokens[1].Substring(1, tokens[1].Length - 2).Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + start = new Vector3(Single.Parse(point[0]), Single.Parse(point[1]), Single.Parse(point[2])); + break; + } + } + } else if (braceCount > 1) { + if (inNormals) { + string[] tokens = line.SplitUnlessInContainer(' ', '\"', StringSplitOptions.RemoveEmptyEntries); + int row = Int32.Parse(tokens[0].Substring(3)); + string[] points = tokens[1].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + normalsTokens[row] = points; + } else if (inDistances) { + string[] tokens = line.SplitUnlessInContainer(' ', '\"', StringSplitOptions.RemoveEmptyEntries); + int row = Int32.Parse(tokens[0].Substring(3)); + string[] nums = tokens[1].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + distancesTokens[row] = nums; + } else if (inAlphas) { + string[] tokens = line.SplitUnlessInContainer(' ', '\"', StringSplitOptions.RemoveEmptyEntries); + int row = Int32.Parse(tokens[0].Substring(3)); + string[] nums = tokens[1].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + alphasTokens[row] = nums; + } + } + } + + if (power == 0) { + throw new ArgumentException("Bad data given to MAPDisplacement, no power specified!"); + } + + if (start.x == Single.NaN) { + throw new ArgumentException("Bad data given to MAPDisplacement, no starting point specified!"); + } + + foreach (int i in normalsTokens.Keys) { + for (int j = 0; j < normalsTokens[i].Length / 3; j++) { + normals[i][j] = new Vector3(Single.Parse(normalsTokens[i][j * 3]), Single.Parse(normalsTokens[i][(j * 3) + 1]), Single.Parse(normalsTokens[i][(j * 3) + 2])); + distances[i][j] = Single.Parse(distancesTokens[i][j]); + alphas[i][j] = Single.Parse(alphasTokens[i][j]); + } + } + + } + + } +} diff --git a/LibBSP/Structs/MAP/MAPPatch.cs b/LibBSP/Structs/MAP/MAPPatch.cs new file mode 100644 index 0000000..e6cd8d1 --- /dev/null +++ b/LibBSP/Structs/MAP/MAPPatch.cs @@ -0,0 +1,82 @@ +#if (UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5) +#define UNITY +#endif +using System; +using System.Collections.Generic; +#if UNITY +using UnityEngine; +#else +using System.Drawing; +#endif + +namespace LibBSP { +#if !UNITY + using Vector2 = Vector2d; + using Vector3 = Vector3d; + using Color32 = Color; +#endif + /// + /// Class containing all data necessary to render a Bezier patch. + /// + public class MAPPatch { + + public UIVertex[] points; + public Vector2 dims; + public string texture; + + /// + /// Constructs a new MAPPatch object using the supplied string array as data. + /// + /// Data to parse + public MAPPatch(string[] lines) { + + texture = lines[2]; + List vertices = new List(9); + + switch (lines[0]) { + case "patchDef3": + case "patchDef2": { + string[] line = lines[3].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + dims = new Vector2(Single.Parse(line[1]), Single.Parse(line[2])); + for (int i = 0; i < dims.x; ++i) { + line = lines[i + 5].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + for (int j = 0; j < dims.y; ++j) { + Vector3 point = new Vector3(Single.Parse(line[2 + (j * 7)]), Single.Parse(line[3 + (j * 7)]), Single.Parse(line[4 + (j * 7)])); + Vector2 uv = new Vector2(Single.Parse(line[5 + (j * 7)]), Single.Parse(line[6 + (j * 7)])); + UIVertex vertex = new UIVertex() { + position = point, + uv0 = uv + }; + vertices.Add(vertex); + } + } + break; + } + case "patchTerrainDef3": { + string[] line = lines[3].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + dims = new Vector2(Single.Parse(line[1]), Single.Parse(line[2])); + for (int i = 0; i < dims.x; ++i) { + line = lines[i + 5].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + for (int j = 0; j < dims.y; ++j) { + Vector3 point = new Vector3(Single.Parse(line[2 + (j * 12)]), Single.Parse(line[3 + (j * 12)]), Single.Parse(line[4 + (j * 12)])); + Vector2 uv = new Vector2(Single.Parse(line[5 + (j * 12)]), Single.Parse(line[6 + (j * 12)])); + Color32 color = Color32Extensions.FromArgb(Byte.Parse(line[7 + (j * 12)]), Byte.Parse(line[8 + (j * 12)]), Byte.Parse(line[9 + (j * 12)]), Byte.Parse(line[10 + (j * 12)])); + UIVertex vertex = new UIVertex() { + position = point, + uv0 = uv, + color = color + }; + vertices.Add(vertex); + } + } + break; + } + default: { + throw new ArgumentException(string.Format("Unknown patch type {0}! Call a scientist! ", lines[0])); + } + } + + } + + } +}