Initial commit; all structs and extensions.

This commit is contained in:
Will
2015-09-15 23:11:43 -06:00
parent 430b5ed9b3
commit 25544952fd
47 changed files with 7197 additions and 0 deletions

22
LibBSP.sln Normal file
View File

@@ -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

View File

@@ -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
/// <summary>
/// Static class containing helper methods for <c>Color32</c> objects.
/// </summary>
public static class Color32Extensions {
/// <summary>
/// Constructs a new <c>Color32</c> from the passed values.
/// </summary>
/// <param name="a">A component</param>
/// <param name="r">R component</param>
/// <param name="g">G component</param>
/// <param name="b">B component</param>
/// <returns>The resulting <c>Color32</c> object</returns>
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
}
}
}

View File

@@ -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
/// <summary>
/// Static class containing helper methods for <c>Plane</c> objects.
/// </summary>
public static class PlaneExtensions {
/// <summary>
/// Intersects three <c>Plane</c>s at a <c>Vector3</c>. Returns NaN for all components if two or more <c>Plane</c>s are parallel.
/// </summary>
/// <param name="p1"><c>Plane</c> to intersect</param>
/// <param name="p2"><c>Plane</c> to intersect</param>
/// <param name="p3"><c>Plane</c> to intersect</param>
/// <returns>Point of intersection if all three <c>Plane</c>s meet at a point, (NaN, NaN, NaN) otherwise</returns>
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);
}
/// <summary>
/// Intersects this <c>Plane</c> with two other <c>Plane</c>s at a <c>Vector3</c>. Returns NaN for all components if two or more <c>Plane</c>s are parallel.
/// </summary>
/// <param name="p1">This <c>Plane</c></param>
/// <param name="p2"><c>Plane</c> to intersect</param>
/// <param name="p3"><c>Plane</c> to intersect</param>
/// <returns>Point of intersection if all three <c>Plane</c>s meet at a point, (NaN, NaN, NaN) otherwise</returns>
public static Vector3 Intersect(this Plane p1, Plane p2, Plane p3) {
return Intersection(p1, p2, p3);
}
/// <summary>
/// Intersects a <c>Plane</c> "<paramref name="p" />" with a <c>Ray</c> "<paramref name="r" />" at a <c>Vector3</c>. Returns NaN for all components if they do not intersect.
/// </summary>
/// <param name="p"><c>Plane</c> to intersect with</param>
/// <param name="r"><c>Ray</c> to intersect</param>
/// <returns>Point of intersection if "<paramref name="r" />" intersects "<paramref name="p" />", (NaN, NaN, NaN) otherwise</returns>
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);
}
}
/// <summary>
/// Intersects this <c>Plane</c> with a <c>Ray</c> "<paramref name="r" />" at a <c>Vector3</c>. Returns NaN for all components if they do not intersect.
/// </summary>
/// <param name="p">This <c>Plane</c></param>
/// <param name="r"><c>Ray</c> to intersect</param>
/// <returns>Point of intersection if "<paramref name="r" />" intersects this <c>Plane</c>, (NaN, NaN, NaN) otherwise</returns>
public static Vector3 Intersect(this Plane p, Ray r) {
return Intersection(p, r);
}
/// <summary>
/// Intersects a <c>Plane</c> "<paramref name="p" />" with this <c>Ray</c> at a <c>Vector3</c>. Returns NaN for all components if they do not intersect.
/// </summary>
/// <param name="r">This <c>Ray</c></param>
/// <param name="p"><c>Plane</c> to intersect with</param>
/// <returns>Point of intersection if this <c>Ray</c> intersects "<paramref name="p" />", (NaN, NaN, NaN) otherwise</returns>
public static Vector3 Intersect(this Ray r, Plane p) {
return Intersection(p, r);
}
/// <summary>
/// Intersects two <c>Plane</c>s at a <c>Ray</c>. Returns NaN for all components of both <c>Vector3</c>s of the <c>Ray</c> if the <c>Plane</c>s are parallel.
/// </summary>
/// <param name="p1"><c>Plane</c> to intersect</param>
/// <param name="p2"><c>Plane</c> to intersect</param>
/// <returns>Line of intersection where "<paramref name="p1" />" intersects "<paramref name="p2" />", ((NaN, NaN, NaN) + p(NaN, NaN, NaN)) otherwise</returns>
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);
}
/// <summary>
/// Intersects this <c>Plane</c> with another <c>Plane</c> at a <c>Ray</c>. Returns NaN for all components of both <c>Vector3</c>s of the <c>Ray</c> if the <c>Plane</c>s are parallel.
/// </summary>
/// <param name="p1">This <c>Plane</c></param>
/// <param name="p2"><c>Plane</c> to intersect</param>
/// <returns>Line of intersection where this <c>Plane</c> intersects "<paramref name="p2" />", ((NaN, NaN, NaN) + p(NaN, NaN, NaN)) otherwise</returns>
public static Ray Intersect(this Plane p1, Plane p2) {
return Intersection(p1, p2);
}
}
}

View File

@@ -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
/// <summary>
/// Static class containing helper methods for <c>Rect</c> objects.
/// </summary>
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
/// <summary>
/// Determines if this <c>Rect</c> is intersected by the line segment defined by <paramref name="head"/> and <paramref name="tail"/>.
/// </summary>
/// <param name="rect">This <c>Rect</c></param>
/// <param name="head">First point defining the line segment</param>
/// <param name="tail">The change and x and y for the coordinates of the tail</param>
/// <returns><c>true</c> if the line segment intersects this <c>Rect</c> at any point</returns>
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;
}
}
}
}
}
}

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LibBSP {
/// <summary>
/// Static class containing helper methods for <c>string</c> objects
/// </summary>
public static class StringExtensions {
/// <summary>
/// Splits a <c>string</c> using a Unicode character, unless that character is between two instances of a container.
/// </summary>
/// <param name="st">The <c>string</c> to split</param>
/// <param name="separator">Unicode character that delimits the substrings in this instance</param>
/// <param name="container">Container character. Any <paramref name="separator"/> characters that occur between two instances of this character will be ignored</param>
/// <returns>Array of <c>string</c> objects that are the resulting substrings</returns>
public static string[] SplitUnlessInContainer(this string st, char separator, char container, StringSplitOptions options = StringSplitOptions.None) {
List<string> results = new List<string>();
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<string>();
}
/// <summary>
/// Splits a <c>string</c> using a Unicode character, unless that character is contained between matching <paramref name="start" /> and <paramref name="end" /> Unicode characters.
/// </summary>
/// <param name="st">The <c>string</c> to split</param>
/// <param name="separator">Unicode character that delimits the substrings in this instance</param>
/// <param name="start">The starting (left) container character. EX: '('</param>
/// <param name="end">The ending (right) container character. EX: ')'</param>
/// <returns>Array of <c>string</c> objects that are the resulting substrings</returns>
public static string[] SplitUnlessBetweenDelimiters(this string st, char separator, char start, char end, StringSplitOptions options = StringSplitOptions.None) {
List<string> results = new List<string>();
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<string>();
}
/// <summary>
/// Parses the <c>bytes</c> in a <c>byte</c> array into an ASCII <c>string</c> up until the first null byte (0x00).
/// </summary>
/// <param name="bytes"><c>Byte</c>s to parse</param>
/// <param name="offset">Position in the array to start copying from</param>
/// <param name="length">Number of bytes to read before stopping. Negative values will read to the end of the array.</param>
/// <returns>The resulting <c>string</c></returns>
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();
}
/// <summary>
/// Parses the <c>bytes</c> in a <c>byte</c> array into an ASCII <c>string</c>.
/// </summary>
/// <param name="bytes"><c>Byte</c>s to parse</param>
/// <returns>The resulting <c>string</c></returns>
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();
}
}
}

View File

@@ -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
/// <summary>
/// Static class containing helper methods for <c>UIVertex</c> objects.
/// </summary>
public static class UIVertexExtensions {
/// <summary>
/// Scales the position of this <c>UIVertex</c> by a number
/// </summary>
/// <param name="v1">This <c>UIVertex</c></param>
/// <param name="scalar">Scalar</param>
/// <returns>The scaled <c>UIVertex</c></returns>
public static UIVertex Scale(this UIVertex v1, float scalar) {
v1.position *= scalar;
return v1;
}
/// <summary>
/// Adds the position of this <c>UIVertex</c> to another <c>UIVertex</c>.
/// </summary>
/// <param name="v1">This <c>UIVertex</c></param>
/// <param name="v2">The other <c>UIVertex</c></param>
/// <returns>The resulting <c>UIVertex</c></returns>
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
};
}
/// <summary>
/// Adds the position of a <c>Vector3</c> to this <c>UIVertex</c>.
/// </summary>
/// <param name="v1">This <c>UIVertex</c></param>
/// <param name="v2">The <c>Vector3</c></param>
/// <returns>The resulting <c>UIVertex</c></returns>
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
};
}
}
}

98
LibBSP/LibBSP.csproj Normal file
View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{33E4FFCA-8D0B-4089-AFD2-527547531578}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>LibBSP</RootNamespace>
<AssemblyName>libBSP</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Extensions\Color32Extensions.cs" />
<Compile Include="Extensions\PlaneExtensions.cs" />
<Compile Include="Extensions\RectExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Extensions\UIVertexExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Structs\BSP\Brush.cs" />
<Compile Include="Structs\BSP\BrushSide.cs" />
<Compile Include="Structs\BSP\BSP.cs" />
<Compile Include="Structs\BSP\Edge.cs" />
<Compile Include="Structs\BSP\Face.cs" />
<Compile Include="Structs\BSP\Leaf.cs" />
<Compile Include="Structs\BSP\Lumps\SourceStaticProps.cs" />
<Compile Include="Structs\BSP\Lumps\Textures.cs" />
<Compile Include="Structs\BSP\Model.cs" />
<Compile Include="Structs\BSP\Node.cs" />
<Compile Include="Structs\BSP\SourceCubemap.cs" />
<Compile Include="Structs\BSP\SourceDispInfo.cs" />
<Compile Include="Structs\BSP\SourceDispVertex.cs" />
<Compile Include="Structs\BSP\Lumps\SourceDispVertices.cs" />
<Compile Include="Structs\BSP\SourceStaticProp.cs" />
<Compile Include="Structs\BSP\SourceTexData.cs" />
<Compile Include="Structs\BSP\TexInfo.cs" />
<Compile Include="Structs\BSP\Texture.cs" />
<Compile Include="Structs\Common\Entity.cs" />
<Compile Include="Structs\Common\Lumps\Entities.cs" />
<Compile Include="Structs\Common\NumList.cs" />
<Compile Include="Structs\Common\Rect.cs" />
<Compile Include="Structs\Doom\Linedef.cs" />
<Compile Include="Structs\Doom\DoomMap.cs" />
<Compile Include="Structs\Doom\Node.cs" />
<Compile Include="Structs\Doom\Sector.cs" />
<Compile Include="Structs\Doom\Segment.cs" />
<Compile Include="Structs\Doom\Sidedef.cs" />
<Compile Include="Structs\Doom\Thing.cs" />
<Compile Include="Structs\MAP\MAPBrush.cs" />
<Compile Include="Structs\MAP\MAPBrushSide.cs" />
<Compile Include="Structs\MAP\MAPDisplacement.cs" />
<Compile Include="Structs\MAP\MAPPatch.cs" />
<Compile Include="Structs\Common\Plane.cs" />
<Compile Include="Structs\Common\Ray.cs" />
<Compile Include="Structs\Common\UIVertex.cs" />
<Compile Include="Structs\Common\Vector2d.cs" />
<Compile Include="Structs\Common\Vector4d.cs" />
<Compile Include="Structs\Common\Vector3d.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -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")]

158
LibBSP/Structs/BSP/BSP.cs Normal file
View File

@@ -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,
}
/// <summary>
/// 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.
/// </summary>
public class BSP {
public MapType version { get; set; }
public string filePath { get; private set; }
// Map structures
// Quake 1/GoldSrc
public Entities entities;
public List<Plane> planes;
public Textures textures;
public List<UIVertex> vertices;
public List<Node> nodes;
public List<TexInfo> texInfo;
public List<Face> faces;
public List<Leaf> leaves;
public NumList markSurfaces;
public List<Edge> edges;
public NumList surfEdges;
public List<Model> models;
public byte[] pvs;
// Quake 2
public List<Brush> brushes;
public List<BrushSide> brushSides;
public NumList markBrushes;
// MOHAA
// public MoHAAStaticProps staticProps;
// Nightfire
public Textures materials;
public NumList indices;
// Source
public List<Face> originalFaces;
public NumList texTable;
public List<SourceTexData> texDatas;
public List<SourceDispInfo> dispInfos;
public SourceDispVertices dispVerts;
public NumList displacementTriangles;
public SourceStaticProps staticProps;
public List<SourceCubemap> cubemaps;
// public SourceOverlays overlays;
/// <summary>
/// Gets the file name of this map.
/// </summary>
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));
}
}
/// <summary>
/// Gets the file name of this map without the ".BSP" extension.
/// </summary>
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));
}
}
/// <summary>
/// Gets the folder path where this map is located.
/// </summary>
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));
}
}
/// <summary>
/// Creates a new <c>BSP</c> instance with no initial data. The <c>List</c>s in this class must be populated elsewhere.
/// </summary>
/// <param name="filePath">The path to the .BSP file</param>
/// <param name="version">The version of the map</param>
public BSP(string filePath, MapType version) {
this.filePath = filePath;
this.version = version;
}
}
}

145
LibBSP/Structs/BSP/Brush.cs Normal file
View File

@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Holds the data used by the brush structures of all formats of BSP
/// </summary>
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; }
/// <summary>
/// Creates a new <c>Brush</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Brush</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Brush</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Brush> 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<Brush> lump = new List<Brush>(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;
}
}
}

View File

@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Holds the data used by the brush side structures of all formats of BSP
/// </summary>
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; }
/// <summary>
/// Creates a new <c>BrushSide</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>BrushSide</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>BrushSide</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<BrushSide> 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<BrushSide> lump = new List<BrushSide>(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;
}
}
}

117
LibBSP/Structs/BSP/Edge.cs Normal file
View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Holds all the data for an edge in a BSP map. Doubles as a subsector class for Doom maps, and has accessors for them.
/// </summary>
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; } }
/// <summary>
/// Creates a new <c>Edge</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Edge</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Edge</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Edge> 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<Edge> lump = new List<Edge>(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;
}
}
}

244
LibBSP/Structs/BSP/Face.cs Normal file
View File

@@ -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
/// <summary>
/// Holds all the data for a face in a BSP map.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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; }
/// <summary>
/// Creates a new <c>Face</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Face</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Face</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Face> 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<Face> lump = new List<Face>(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;
}
}
}

169
LibBSP/Structs/BSP/Leaf.cs Normal file
View File

@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Holds data for a leaf structure in a BSP map
/// </summary>
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; }
/// <summary>
/// Creates a new <c>Leaf</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Leaf</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Leaf</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Leaf> 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<Leaf> lump = new List<Leaf>(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;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Class representing a group of <c>SourceDispVertex</c> objects. Contains helpful methods to handle Displacement Vertices in the <c>List</c>
/// </summary>
public class SourceDispVertices : List<SourceDispVertex> {
/// <summary>
/// Parses the passed <c>byte</c> array into a <c>List</c> of <c>SourceDispVertices</c>
/// </summary>
/// <param name="data">Array of <c>byte</c>s to parse</param>
/// <param name="type">Format identifier</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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));
}
}
/// <summary>
/// Gets enough vertices from the list for a displacement of power <paramref name="power" />, starting at <paramref name="first" />.
/// </summary>
/// <param name="first">The first vertex to get</param>
/// <param name="power">The power of the displacement</param>
/// <returns>Array of <c>SourceDispVertex</c> objects containing all the vertices in this displacement</returns>
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;
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// List of <c>SourceStaticProp</c> objects containing data relevant to Static Props, like the dictionary of actual model paths.
/// </summary>
public class SourceStaticProps : List<SourceStaticProp> {
public string[] dictionary { get; private set; }
/// <summary>
/// Parses the passed <c>byte</c> array into a <c>List</c> of <c>SourceStaticProp</c> objects
/// </summary>
/// <param name="data">Array of <c>byte</c>s to parse</param>
/// <param name="type">Format identifier</param>
/// <param name="version">Version of static prop lump this is</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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;
}
}
}
}
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// <c>List</c>&lt;<c>Texture</c>&gt; with some useful methods for manipulating <c>Texture</c> objects,
/// especially when handling them as a group.
/// </summary>
public class Textures : List<Texture> {
/// <summary>
/// Parses a <c>byte</c> array into this <c>List</c> of <c>Texture</c> objects
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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));
}
}
}
/// <summary>
/// Gets the name of the texture at the specified offset.
/// </summary>
/// <param name="offset">Lump offset of the texture name to find</param>
/// <returns>The name of the texture at offset <paramref name="offset" />, or null if it doesn't exist</returns>
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;
}
/// <summary>
/// Finds the offset of the specified texture name.
/// </summary>
/// <param name="inTexture">The texture name to find in the lump</param>
/// <returns>The offset of the specified texture, or -1 if it wasn't found</returns>
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;
}
}
}

179
LibBSP/Structs/BSP/Model.cs Normal file
View File

@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// An attempt at an all-encompassing model class containing all data needed for any
/// given models lump in any given BSP.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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; }
/// <summary>
/// Creates a new <c>Model</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Model</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Model</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Model> 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<Model> lump = new List<Model>(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;
}
}
}

134
LibBSP/Structs/BSP/Node.cs Normal file
View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Contains all data needed for a node in a BSP tree.
/// </summary>
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.
/// <summary>
/// Creates a new <c>Node</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Node</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Node</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Node> 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<Node> lump = new List<Node>(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;
}
}
}

View File

@@ -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
/// <summary>
/// Holds all data for a Cubemap from Source engine.
/// </summary>
public struct SourceCubemap {
public Vector3 origin { get; private set; }
public int size { get; private set; }
/// <summary>
/// Creates a new <c>SourceCubemap</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>SourceCubemap</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>SourceCubemap</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<SourceCubemap> 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<SourceCubemap> lump = new List<SourceCubemap>(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;
}
}
}

View File

@@ -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
/// <summary>
/// Holds all data for a Displacement from Source engine.
/// </summary>
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; }
/// <summary>
/// Creates a new <c>SourceDispInfo</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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));
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>SourceDispInfo</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>SourceDispInfo</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<SourceDispInfo> 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<SourceDispInfo> lump = new List<SourceDispInfo>(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;
}
}
}

View File

@@ -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
/// <summary>
/// Holds all the data for a displacement in a Source map.
/// </summary>
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
/// <summary>
/// Creates a new <c>SourceDispVertex</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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);
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>SourceDispVertices</c> object.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>SourceDispVertices</c> object</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
public static SourceDispVertices LumpFactory(byte[] data, MapType type) {
if (data == null) {
throw new ArgumentNullException();
}
return new SourceDispVertices(data, type);
}
}
}

View File

@@ -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
/// <summary>
/// Handles the data needed for one static prop.
/// </summary>
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; }
/// <summary>
/// Creates a new <c>SourceStaticProp</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to create a <c>SourceStaticProps</c> object.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <param name="version">The version of the Static Prop lump</param>
/// <returns>A <c>SourceStaticProps</c> object</returns>
public static SourceStaticProps LumpFactory(byte[] data, MapType type, int version) {
return new SourceStaticProps(data, type, version);
}
}
}

View File

@@ -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
/// <summary>
/// Contains all the information for a single SourceTexData object
/// </summary>
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; }
/// <summary>
/// Creates a new <c>SourceTexData</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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);
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>SourceTexData</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>SourceTexData</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
public static List<SourceTexData> LumpFactory(byte[] data, MapType type) {
if (data == null) {
throw new ArgumentNullException();
}
int structLength = 32;
List<SourceTexData> lump = new List<SourceTexData>(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;
}
}
}

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
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; }
/// <summary>
/// Creates a new <c>TexInfo</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Creates a new <c>TexInfo</c> object using the passed data.
/// </summary>
/// <param name="s">The S texture axis</param>
/// <param name="SShift">The texture shift on the S axis</param>
/// <param name="t">The T texture axis</param>
/// <param name="TShift">The texture shift on the T axis</param>
/// <param name="flags">The flags for this <c>TexInfo</c></param>
/// <param name="texture">Index into the texture list for the texture this <c>TexInfo</c> uses</param>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="p"><c>Plane</c> of the surface</param>
/// <returns>The best matching texture axes for the given <c>Plane</c></returns>
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;
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>TexInfo</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>TexInfo</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<TexInfo> 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<TexInfo> lump = new List<TexInfo>(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;
}
}
}

View File

@@ -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
/// <summary>
/// An all-encompassing class to handle the texture information of any given BSP format.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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; }
/// <summary>
/// Creates a new <c>Texture</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>Textures</c> object.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>Textures</c> object</returns>
public static Textures LumpFactory(byte[] data, MapType type) {
return new Textures(data, type);
}
}
}

View File

@@ -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
/// <summary>
/// Class containing all data for a single <c>Entity</c>, including attributes, Source Entity I/O connections and solids.
/// </summary>
[Serializable] public class Entity : Dictionary<string, string>, IComparable, IComparable<Entity> {
public const char ConnectionMemberSeparater = (char)0x1B;
public List<EntityConnection> connections = new List<EntityConnection>();
public List<MAPBrush> brushes = new List<MAPBrush>();
/// <summary>
/// Gets whether this <c>Entity</c> is brush-based or not.
/// </summary>
public bool brushBased { get { return brushes.Count > 0 || modelNumber >= 0; } }
/// <summary>
/// Wrapper for the "spawnflags" attribute.
/// </summary>
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(); }
}
/// <summary>
/// Wrapper for the "origin" attribute.
/// </summary>
public Vector3 origin {
get { return GetVector("origin"); }
set { this["origin"] = value.x + " " + value.y + " " + value.z; }
}
/// <summary>
/// Wrapper for the "angles" attribute.
/// </summary>
public Vector3 angles {
get { return GetVector("angles"); }
set { this["angles"] = value.x + " " + value.y + " " + value.z; }
}
/// <summary>
/// Wrapper for the "targetname" attribute.
/// </summary>
public string name {
get {
if (ContainsKey("targetname")) {
return this["targetname"];
} else {
return "";
}
}
set { this["targetname"] = value; }
}
/// <summary>
/// Wrapper for the "classname" attribute.
/// </summary>
/// <remarks>If an entity has no class, it has no behavior. It's either an error or metadata.</remarks>
public string className {
get {
if (ContainsKey("classname")) {
return this["classname"];
} else {
return "";
}
}
set { this["classname"] = value; }
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
}
/// <summary>
/// Allows an attribute to be accessed easily using <c>Entity</c>["<paramref name="key" />"] notation.
/// If an attribute doesn't exist, it returns the empty string. This emulates the behavior of the engines.
/// </summary>
/// <remarks>
/// It's up to the developer to ensure the empty string doesn't cause problems, rather than returning null!
/// </remarks>
/// <param name="key">The attribute to retrieve</param>
/// <returns>The value of the attribute if it exists, empty <c>string</c> otherwise</returns>
public new string this[string key] {
get {
if (ContainsKey(key)) {
return base[key];
} else {
return string.Empty;
}
}
set { base[key] = value; }
}
/// <summary>
/// Initializes a new instance of an <c>Entity</c>, parsing the given <c>byte</c> array into an <c>Entity</c> structure.
/// </summary>
/// <param name="data">Array to parse</param>
public Entity(byte[] data, MapType type) : this(Encoding.ASCII.GetString(data).Split('\n')) { }
/// <summary>
/// Initializes a new instance of an <c>Entity</c> with the given classname.
/// </summary>
/// <param name="className">Classname of the new <C>Entity</C></param>
public Entity(string className) : base(StringComparer.InvariantCultureIgnoreCase) {
Add("classname", className);
}
/// <summary>
/// Initializes a new instance of an <c>Entity</c> object with no initial properties.
/// </summary>
public Entity() : base(StringComparer.InvariantCultureIgnoreCase) { }
/// <summary>
/// Initializes a new instance of an <c>Entity</c> object, copying the attributes, connections and brushes of the passed <c>Entity</c>.
/// </summary>
/// <param name="copy">The <c>Entity</c> to copy</param>
public Entity(Entity copy) : base(copy, StringComparer.InvariantCultureIgnoreCase) {
connections = new List<EntityConnection>(copy.connections);
brushes = new List<MAPBrush>(copy.brushes);
}
/// <summary>
/// Initializes a new instance of an <c>Entity</c>, parsing the given <c>string</c> array into an <c>Entity</c> structure.
/// </summary>
/// <param name="lines">Array of attributes, patches, brushes, displacements etc. to parse</param>
public Entity(string[] lines) : base(StringComparer.InvariantCultureIgnoreCase) {
int braceCount = 0;
bool inConnections = false;
bool inBrush = false;
List<string> child = new List<string>();
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<string>();
}
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);
}
}
/// <summary>
/// Factory method to create an <c>Entity</c> from a <c>string</c> "<paramref name="st" />" where
/// "<paramref name="st" />" contains all lines for the entity, including attributes, brushes, etc.
/// </summary>
/// <remarks>
/// This was necessary since the <c>Entity</c>(<c>string</c>) constructor was already used in a different way
/// </remarks>
/// <param name="st">The data to parse</param>
/// <returns>The resulting <c>Entity</c> object</returns>
public static Entity FromString(string st) {
return new Entity(st.Split('\n'));
}
/// <summary>
/// Renames the attribute named "<paramref name="oldName" />" to "<paramref name="newName" />". Replaces the old entry if it already exists.
/// </summary>
/// <param name="oldName">Attribute to be renamed</param>
/// <param name="newName">New name for this attribute</param>
public void RenameKey(string oldName, string newName) {
if (ContainsKey(oldName)) {
string val = this[oldName];
Remove(oldName);
if (ContainsKey(newName)) {
Remove(newName);
}
Add(newName, val);
}
}
/// <summary>
/// Parses the input <c>string</c> "<paramref name="st" />" into a key/value pair and adds
/// it as an attribute to this <c>Entity</c>
/// </summary>
/// <param name="st">The <c>string</c> to be parsed</param>
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);
}
}
}
}
/// <summary>
/// Adds the key/value pair to this <c>Entity</c>
/// </summary>
/// <param name="key">Name of the attribute to add</param>
/// <param name="value">Value of the attribute to add</param>
public new void Add(string key, string value) {
this[key] = value;
}
/// <summary>
/// Gets a string representation of this <c>Entity</c>.
/// </summary>
/// <remarks>Don't use this to export the entity to a file</remarks>
/// <returns>String representation of this <c>Entity</c></returns>
public override string ToString() {
StringBuilder output = new StringBuilder();
output.Append("{\n");
foreach (KeyValuePair<string, string> 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 + "}";
}
/// <summary>
/// Checks if the attribute named "<paramref name="key" />" has the value "<paramref name="value" />"
/// </summary>
/// <param name="key">The attribute to check</param>
/// <param name="value">The value to compare</param>
/// <returns><c>true</c> if the values match</returns>
public bool ValueIs(string key, string value) {
return value.Equals(this[key], StringComparison.InvariantCultureIgnoreCase);
}
/// <summary>
/// Checks if the bits in "spawnflags" corresponding to the set bits set in <paramref name="bits" /> are set
/// </summary>
/// <param name="bits">The bits to compare spawnflags to</param>
/// <returns><c>true</c> if all bits that were set in <paramref name="bits" /> were set in spawnflags</returns>
public bool SpawnflagsSet(uint bits) {
return ((spawnflags & bits) == bits);
}
/// <summary>
/// Toggles the bits in "spawnflags" which are set in <paramref name="bits" />
/// </summary>
/// <param name="bits">Bitmask of bits to toggle</param>
public void ToggleSpawnflags(uint bits) {
this["spawnflags"] = (spawnflags ^ bits).ToString();
}
/// <summary>
/// Clears the bits in "spawnflags" which are set in <paramref name="bits" />
/// Equivalent to spawnflags = (<paramref name="bits" /> ^ 0xFFFFFFFF) & spawnflags
/// </summary>
/// <param name="bits">Bitmask of bits to clear</param>
public void ClearSpawnflags(uint bits) {
ToggleSpawnflags(spawnflags & bits);
}
/// <summary>
/// Sets the bits in "spawnflags" which are set in <paramref name="bits" />
/// </summary>
/// <param name="bits">Bitmask of bits to set</param>
public void SetSpawnflags(uint bits) {
this["spawnflags"] = (spawnflags | bits).ToString();
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The best match action for what this entity would do when fired in other engines</returns>
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";
}
}
}
/// <summary>
/// Tries to determine what action in Source Engine's Entity I/O would be equivalent
/// to "enabling" the entity in prior engines
/// </summary>
/// <returns>The best match action for what this entity would do when enabled in other engines</returns>
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";
}
}
}
/// <summary>
/// Tries to determine what action in Source Engine's Entity I/O would be equivalent
/// to "disabling" the entity in prior engines
/// </summary>
/// <returns>The best match action for what this entity would do when disabled in other engines</returns>
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";
}
}
}
/// <summary>
/// Tries to determine which "Output" in Source Engine this <c>Entity</c> would use
/// to "fire" its targets in prior engines
/// </summary>
/// <returns>The best match "Output" for this <c>Entity</c></returns>
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";
}
}
}
/// <summary>
/// Gets a numeric value as a <c>float</c>.
/// </summary>
/// <param name="key">Name of the attribute to retrieve</param>
/// <param name="failDefault">Value to return if <paramref name="key" /> doesn't exist, or couldn't be converted</param>
/// <returns>The numeric value of the value corresponding to <paramref name="key" /></returns>
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;
}
}
/// <summary>
/// Gets a numeric value as an <c>int</c>.
/// </summary>
/// <param name="key">Name of the attribute to retrieve</param>
/// <param name="failDefault">Value to return if <paramref name="key" /> doesn't exist, or couldn't be converted</param>
/// <returns>The numeric value of the value corresponding to <paramref name="key" /></returns>
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;
}
}
/// <summary>
/// Gets a Vector value as a <c>Vector4</c>. This will only read as many values as are in the value, and can be
/// implicitly converted to Vector3, Vector2, or Color.
/// </summary>
/// <param name="key">Name of the attribute to retrieve</param>
/// <returns>Vector representation of the components of the attribute</returns>
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]);
}
/// <summary>
/// Compares this <c>Entity</c> to another object. First "classname" attributes are compared, then "targetname".
/// Attributes are compared alphabetically. Targetnames are only compared if classnames match.
/// </summary>
/// <param name="obj"><c>Object</c> to compare to</param>
/// <returns>Less than zero if this entity is first, 0 if they occur at the same time, greater than zero otherwise</returns>
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);
}
/// <summary>
/// Compares this <c>Entity</c> to another <c>Entity</c>. First "classname" attributes are compared, then "targetname".
/// Attributes are compared alphabetically. Targetnames are only compared if classnames match.
/// </summary>
/// <param name="other"><c>Entity</c> to compare to</param>
/// <returns>Less than zero if this entity is first, 0 if they occur at the same time, greater than zero otherwise</returns>
public int CompareTo(Entity other) {
if (other == null) { return 1; }
int firstTry = className.CompareTo(other.className);
return firstTry != 0 ? firstTry : name.CompareTo(other.name);
}
/// <summary>
/// Factory method for a <c>Lump</c>.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>An <c>Entities</c> object, which is a <c>List</c> of <c>Entity</c>s</returns>
public static Entities LumpFactory(byte[] data, MapType type) {
return new Entities(data, type);
}
/// <summary>
/// Struct containing the fields necessary for Source entity I/O
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// Class representing a group of <c>Entity</c> objects. Contains helpful methods to handle Entities in the <c>List</c>
/// </summary>
public class Entities : List<Entity> {
/// <summary>
/// Initializes a new instance of an <c>Entities</c> object copying a passed <c>IEnumerable</c> of <c>Entity</c> objects.
/// </summary>
/// <param name="data">Collection of <c>Entity</c> objects to copy</param>
public Entities(IEnumerable<Entity> data) : base(data) { }
/// <summary>
/// Initializes a new instance of an <c>Entities</c> object with a specified initial capacity.
/// </summary>
/// <param name="initialCapacity">Initial capacity of the <c>List</c> of <c>Entity</c> objects</param>
public Entities(int initialCapacity) : base(initialCapacity) { }
/// <summary>
/// Initializes a new empty <c>Entities</c> object.
/// </summary>
public Entities() : base() { }
/// <summary>
/// Initializes a new <c>Entities</c> object, and parses the passed <c>byte</c> array into the <c>List</c>.
/// </summary>
/// <param name="data"><c>Byte</c>s read from a file</param>
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));
}
}
/// <summary>
/// Deletes all <c>Entity</c> with "<paramref name="key" />" set to "<paramref name="value" />".
/// </summary>
/// <param name="key">Attribute to match</param>
/// <param name="value">Desired value of attribute</param>
public void RemoveAllWithAttribute(string key, string value) {
//DeleteEnts(FindAllWithAttribute(key, value));
this.RemoveAll(entity => { return entity[key] == value; });
}
/// <summary>
/// Gets a <c>List</c> of all <c>Entity</c>s with "<paramref name="key" />" set to "<paramref name="value" />".
/// </summary>
/// <param name="key">Name of the attribute to search for</param>
/// <param name="value">Value of the attribute to search for</param>
/// <returns><c>List</c>(<c>Entity</c>) that have the specified key/value pair</returns>
public List<Entity> GetAllWithAttribute(string key, string value) {
return FindAll(entity => { return entity[key] == value; });
}
/// <summary>
/// Gets a <c>List</c> of <c>Entity</c>s objects with the specified targetname
/// </summary>
/// <param name="targetname">Targetname attribute to find</param>
/// <returns><c>List</c>(<c>Entity</c>) with the specified targetname</returns>
public List<Entity> GetAllWithName(string targetname) {
return GetAllWithAttribute("targetname", targetname);
}
/// <summary>
/// Gets the first <c>Entity</c> with "<paramref name="key" />" set to "<paramref name="value" />".
/// </summary>
/// <param name="key">Name of the attribute to search for</param>
/// <param name="value">Value of the attribute to search for</param>
/// <returns><c>Entity</c> with the specified key/value pair, or null if none exists</returns>
public Entity GetWithAttribute(string key, string value) {
return Find(entity => { return entity[key] == value; });
}
/// <summary>
/// Gets the first <c>Entity</c> with the specified targetname.
/// </summary>
/// <param name="targetname">Targetname attribute to find</param>
/// <returns>Entity object with the specified targetname</returns>
public Entity GetWithName(string targetname) {
return GetWithAttribute("targetname", targetname);
}
}
}

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace LibBSP {
/// <summary>
/// List class for numbers. Can handle any integer data type except <c>ulong</c>.
/// </summary>
public class NumList : List<long> {
public enum DataType : int {
SByte = 0,
Byte = 1,
Int16 = 2,
UInt16 = 3,
Int32 = 4,
UInt32 = 5,
Int64 = 6,
}
public DataType type { get; private set; }
/// <summary>
/// Creates a new <c>NumList</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The type of number to store</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException"><paramref name="type" /> was not a member of the DataType enum</exception>
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.");
}
}
}
}
}

View File

@@ -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 {
/// <summary>
/// Holds the data for a plane in 3D space in Hessian Normal Form.
/// </summary>
[Serializable] public struct Plane : IEquatable<Plane> {
private Vector3d _normal;
public double distance;
public Vector3d normal {
get { return _normal; }
set {
_normal = value;
_normal.Normalize();
}
}
/// <summary>
/// The a component of this <c>Plane</c>
/// </summary>
public double a {
get {
return normal.x;
}
}
/// <summary>
/// The b component of this <c>Plane</c>
/// </summary>
public double b {
get {
return normal.y;
}
}
/// <summary>
/// The c component of this <c>Plane</c>
/// </summary>
public double c {
get {
return normal.z;
}
}
/// <summary>
/// This <c>Plane</c>, flipped over so the negative side is now the positive side, and vice versa.
/// </summary>
public Plane flipped {
get {
return new Plane(-normal, -distance);
}
}
/// <summary>
/// Creates a new <c>Plane</c> object using <c>float</c>s. The first three <c>float</c>s are the normal, and the last one is the distance.
/// </summary>
/// <param name="nums">Components of this <c>Plane</c></param>
/// <exception cref="ArgumentException">4 <c>float</c>s were not passed</exception>
/// <exception cref="ArgumentNullException">The passed array was null</exception>
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]);
}
/// <summary>
/// Creates a new <c>Plane</c> object using <c>double</c>s. The first three <c>double</c>s are the normal, and the last one is the distance.
/// </summary>
/// <param name="nums">Components of this <c>Plane</c></param>
/// <exception cref="ArgumentException">4 <c>double</c>s were not passed</exception>
/// <exception cref="ArgumentNullException">The passed array was null</exception>
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];
}
/// <summary>
/// Creates a new <c>Plane</c> object using a normal and distance.
/// </summary>
/// <param name="normal">Normal of this <c>Plane</c></param>
/// <param name="dist">Distance from the origin to this <c>Plane</c></param>
public Plane(Vector3d normal, double dist) {
this._normal = new Vector3d(normal);
_normal.Normalize();
this.distance = dist;
}
/// <summary>
/// Creates a new <c>Plane</c> object using a normal and distance.
/// </summary>
/// <param name="normal">Normal of this <c>Plane</c></param>
/// <param name="dist">Distance from the origin to this <c>Plane</c></param>
public Plane(Vector3d normal, float dist) : this(normal, Convert.ToDouble(dist)) { }
/// <summary>
/// Creates a new <c>Plane</c> object by copying another <c>Plane</c>.
/// </summary>
/// <param name="copy"><c>Plane</c> to copy.</param>
public Plane(Plane copy) {
_normal = new Vector3d(copy.normal);
distance = copy.distance;
}
/// <summary>
/// Creates a new <c>Plane</c> object using a normal and a point on the <c>Plane</c>.
/// </summary>
/// <param name="normal">Normal of this <c>Plane</c></param>
/// <param name="point">A point on this <c>Plane</c></param>
public Plane(Vector3d normal, Vector3d point) {
this._normal = normal;
_normal.Normalize();
this.distance = point * normal;
}
/// <summary>
/// Creates a new <c>Plane</c> object using three points on the <c>Plane</c>.
/// </summary>
/// <param name="point0">A point on the <c>Plane</c></param>
/// <param name="point1">A point on the <c>Plane</c></param>
/// <param name="point2">A point on the <c>Plane</c></param>
public Plane(Vector3d point0, Vector3d point1, Vector3d point2) {
_normal = ((point0 - point2) ^ (point0 - point1));
_normal.Normalize();
distance = point0 * _normal;
}
/// <summary>
/// Creates a new <c>Plane</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <remarks>
/// This constructor is intended for use with BSP reading. The <c>byte</c> array must have a length of at least 16, all components are read as <c>float</c>s.
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">There was not enough bytes in the <paramref name="data" /> array</exception>
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));
}
/// <summary>
/// Determines whether this <c>Plane</c> is equal to another <c>object</c>.
/// </summary>
/// <param name="obj"><c>object</c> to compare to</param>
/// <returns>Whether <paramref name="obj" /> is a <c>Plane</c> and is equal to this <c>Plane</c></returns>
public override bool Equals(object obj) {
if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; }
return Equals((Plane)obj);
}
/// <summary>
/// Compares whether two <c>Plane</c>s are equal, or approximately equal.
/// </summary>
/// <param name="other">The <c>Plane</c> to compare to</param>
/// <returns><c>true</c> if this <c>Plane</c> is parallel to, faces the same direction, and has the same distance as, the given <c>Plane</c>.</returns>
public bool Equals(Plane other) {
return (normal == other.normal && distance + 0.001 >= other.distance && distance - 0.001 <= other.distance);
}
/// <summary>
/// Generates a hash code for this instance based on instance data.
/// </summary>
/// <returns>The hash code for this instance</returns>
public override int GetHashCode() {
return _normal.GetHashCode() ^ distance.GetHashCode();
}
/// <summary>
/// Compares whether two <c>Plane</c>s are equal, or approximately equal.
/// </summary>
/// <param name="other">The <c>Plane</c> to compare to</param>
/// <returns><c>true</c> if this <c>Plane</c> is parallel to, faces the same direction, and has the same distance as, the given <c>Plane</c>.</returns>
public static bool operator ==(Plane p1, Plane p2) {
return p1.Equals(p2);
}
/// <summary>
/// Compares whether two <c>Plane</c>s are not equal, or approximately equal.
/// </summary>
/// <param name="other">The <c>Plane</c> to compare to</param>
/// <returns><c>false</c> if this <c>Plane</c> is parallel to, faces the same direction, and has the same distance as, the given <c>Plane</c>.</returns>
public static bool operator !=(Plane p1, Plane p2) {
return !p1.Equals(p2);
}
/// <summary>
/// Determines whether the given <c>Vector3d</c> is contained in this <c>Plane</c>.
/// </summary>
/// <param name="v">Vector</param>
/// <returns><c>true</c> if the <c>Vector3d</c> is contained in this <c>Plane</c>.</returns>
public bool Contains(Vector3d v) {
double distanceTo = GetDistanceToPoint(v);
return distanceTo < 0.001 && distanceTo > -0.001;
}
/// <summary>
/// Gets the signed distance from this <c>Plane</c> to a given point.
/// </summary>
/// <param name="to">Point to get the distance to</param>
/// <returns>Signed distance from this <c>Plane</c> to the given point.</returns>
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;
}
/// <summary>
/// Is <paramref name="v" /> on the positive side of this <c>Plane</c>?
/// </summary>
/// <param name="v">Point to get the side for</param>
/// <returns><c>true</c> if <paramref name="v" /> is on the positive side of this <c>Plane</c></returns>
public bool GetSide(Vector3d v) {
return GetDistanceToPoint(v) > 0;
}
/// <summary>
/// Flips this <c>Plane</c> to face the opposite direction.
/// </summary>
public void Flip() {
normal = -normal;
distance = -distance;
}
/// <summary>
/// Flips this <c>Plane</c> to face the opposite direction.
/// </summary>
public static Plane operator -(Plane flipMe) {
return new Plane(-flipMe.normal, -flipMe.distance);
}
/// <summary>
/// Gets a nicely formatted string representation of this <c>Plane</c>.
/// </summary>
/// <returns>A nicely formatted string representation of this <c>Plane</c></returns>
public override string ToString() {
return "(" + normal.ToString() + ") " + distance;
}
/// <summary>
/// Raycasts a <c>Ray</c> against this <c>Plane</c>.
/// </summary>
/// <param name="ray"><c>Ray</c> to raycast against</param>
/// <param name="enter">Out parameter. The distance along <paramref name="ray" /> where the collision happened</param>
/// <returns>
/// <c>true</c> and <paramref name="enter" /> is positive if <c>Ray</c> intersects this <c>Plane</c> in front of the way,
/// <c>false</c> and <paramref name="enter" /> is negative if <c>Ray</c> intersects this <c>Plane</c> behind the ray,
/// <c>false</c> and <paramref name="enter" /> is 0 if the <c>Ray</c> is parallel to this <c>Plane</c>
/// </returns>
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;
}
/// <summary>
/// Generates three points which can be used to define this <c>Plane</c>.
/// </summary>
/// <returns>Three points which define this <c>Plane</c></returns>
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;
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Plane</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Plane</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Plane> 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<Plane> lump = new List<Plane>(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

View File

@@ -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 {
/// <summary>
/// A struct for a <c>Ray</c> defined by a starting point and a direction vector.
/// </summary>
public struct Ray : IEquatable<Ray> {
public Vector3d origin;
private Vector3d _direction;
public Vector3d direction {
get { return _direction; }
set {
_direction = value.normalized;
}
}
/// <summary>
/// Creates a new <c>Ray</c> object using the specified origin and direction.
/// </summary>
/// <param name="origin">Origin point of this <C>Ray</C></param>
/// <param name="direction">Direction vector of this <c>Ray</c></param>
public Ray(Vector3d origin, Vector3d direction) {
this.origin = origin;
this._direction = direction;
_direction.Normalize();
}
/// <summary>
/// Gets the point at <paramref name="distance" /> units along this <c>Ray</c>.
/// </summary>
/// <param name="distance">Distance of the point to get</param>
/// <returns>The point at <paramref name="distance" /> units along this <c>Ray</c></returns>
public Vector3d GetPoint(double distance) {
return origin + (distance * direction);
}
/// <summary>
/// Gets a nicely formatted string representation of this <c>Ray</c>.
/// </summary>
/// <returns>A nicely formatted string representation of this <c>Ray</c></returns>
public override string ToString() {
return string.Format("( {0}, {1} )", origin, direction);
}
/// <summary>
/// Determines whether this <c>Ray</c> is equivalent to another.
/// </summary>
/// <param name="r1">The <c>Ray</c> to compare to</param>
/// <returns><c>true</c> if this <c>Ray</c> and <paramref name="r1" /> have the same origin and direction</returns>
public static bool operator ==(Ray r1, Ray r2) {
return r1.Equals(r2);
}
/// <summary>
/// Determines whether this <c>Ray</c> is not equivalent to another.
/// </summary>
/// <param name="r1">The <c>Ray</c> to compare to</param>
/// <returns><c>true</c> if this <c>Ray</c> and <paramref name="r1" /> don't have the same origin and direction</returns>
public static bool operator !=(Ray r1, Ray r2) {
return !r1.Equals(r2);
}
/// <summary>
/// Determines whether this <c>Ray</c> is equivalent to another.
/// </summary>
/// <param name="other">The <c>Ray</c> to compare to</param>
/// <returns><c>true</c> if this <c>Ray</c> and <paramref name="other" /> have the same origin and direction</returns>
public bool Equals(Ray other) {
return origin.Equals(other.origin) && direction.Equals(other.direction);
}
/// <summary>
/// Determines whether this <c>Ray</c> is equivalent to another <c>object</c>.
/// </summary>
/// <param name="obj">The <c>object</c> to compare to</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <c>Ray</c> and this <c>Ray</c> and <paramref name="obj" /> have the same origin and direction</returns>
public override bool Equals(object obj) {
if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; }
return Equals((Ray)obj);
}
/// <summary>
/// Generates a hash code for this instance based on instance data.
/// </summary>
/// <returns>The hash code for this instance</returns>
public override int GetHashCode() {
return origin.GetHashCode() ^ direction.GetHashCode();
}
}
}
#endif

View File

@@ -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<Rect> {
public Vector2d position;
public Vector2d size;
/// <summary>
/// Gets the center point of this <c>Rect</c>
/// </summary>
public Vector2d center {
get {
return position + (size / 2.0);
}
}
/// <summary>
/// Gets the width of this <c>Rect</c>
/// </summary>
public double width {
get {
return size.x;
}
set {
size.x = value;
}
}
/// <summary>
/// Gets the height of this <c>Rect</c>
/// </summary>
public double height {
get {
return size.y;
}
set {
size.y = value;
}
}
/// <summary>
/// Gets the bottom-left coordinate of this <c>Rect</c>
/// </summary>
public Vector2d min {
get {
return new Vector2d(position.x, position.y + size.y);
}
set {
position.x = value.x;
size.y = value.y - position.y;
}
}
/// <summary>
/// Gets the top-right coordinate of this <c>Rect</c>
/// </summary>
public Vector2d max {
get {
return new Vector2d(position.x + size.x, position.y);
}
set {
position.y = value.y;
size.x = value.x - position.x;
}
}
/// <summary>
/// Gets the left bound of this <c>Rect</c>
/// </summary>
public double x {
get {
return position.x;
}
set {
position.x = value;
}
}
/// <summary>
/// Gets the left bound of this <c>Rect</c>
/// </summary>
public double xMin {
get {
return position.x;
}
set {
position.x = value;
}
}
/// <summary>
/// Gets the right bound of this <c>Rect</c>
/// </summary>
public double xMax {
get {
return position.x + size.x;
}
set {
size.x = value - position.x;
}
}
/// <summary>
/// Gets the upper bound of this <c>Rect</c>
/// </summary>
public double y {
get {
return position.y;
}
set {
position.y = value;
}
}
/// <summary>
/// Gets the upper bound of this <c>Rect</c>
/// </summary>
public double yMin {
get {
return position.y;
}
set {
position.y = value;
}
}
/// <summary>
/// Gets the lower bound of this <c>Rect</c>
/// </summary>
public double yMax {
get {
return position.y + size.y;
}
set {
size.y = value - position.y;
}
}
/// <summary>
/// Creates a new <c>Rect</c> object.
/// </summary>
/// <param name="left">Left bound</param>
/// <param name="top">Upper bound</param>
/// <param name="width">Width</param>
/// <param name="height">Height</param>
public Rect(double left, double top, double width, double height) : this() {
position = new Vector2d(left, top);
size = new Vector2d(width, height);
}
/// <summary>
/// Creates a new <c>Rect</c> object from the specified bounds
/// </summary>
/// <param name="left">Left bound</param>
/// <param name="top">Upper bound</param>
/// <param name="right">Right bound</param>
/// <param name="bottom">Lower bound</param>
/// <returns>The resulting <c>Rect</c></returns>
public static Rect MinMaxRect(double left, double top, double right, double bottom) {
return new Rect(left, top, right - left, bottom - top);
}
/// <summary>
/// Gets whether this <c>Rect</c> contains the specified point.
/// </summary>
/// <param name="point">The point to check</param>
/// <param name="allowInverse">If the <c>Rect</c>'s size is negative, should the check allow this?</param>
/// <returns><c>true</c> if the point is contained in the <C>Rect</C></returns>
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);
}
}
/// <summary>
/// Gets whether this <c>Rect</c> overlaps the specified <c>Rect</c>.
/// </summary>
/// <param name="other">The <c>Rect</c> to check</param>
/// <param name="allowInverse">If either <c>Rect</c>'s size is negative, should the check allow this?</param>
/// <returns><c>true</c> if the other <c>Rect</c> overlaps this <C>Rect</C></returns>
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);
}
}
/// <summary>
/// Sets this <c>Rect</c>'s components
/// </summary>
/// <param name="left">The left bound to set</param>
/// <param name="top">The upper bound to set</param>
/// <param name="width">The width to set</param>
/// <param name="height">The height to set</param>
public void Set(double left, double top, double width, double height) {
position.x = left;
position.y = top;
size.x = width;
size.y = height;
}
/// <summary>
/// Given a normalized point, returns a coordinate point inside the <c>Rect</c>.
/// </summary>
/// <param name="rect">The <c>Rect</c></param>
/// <param name="normalizedCoordinates">The normalized coordinates</param>
/// <returns>The denormalized coordinates</returns>
public static Vector2d NormalizedToPoint(Rect rect, Vector2d normalizedCoordinates) {
return new Vector2d(rect.x + (rect.width * normalizedCoordinates.x), rect.y + (rect.height * normalizedCoordinates.y));
}
/// <summary>
/// Given a point, returns a normalized point relative to the <c>Rect</c>.
/// </summary>
/// <param name="rect">The <c>Rect</c></param>
/// <param name="point">The coordinates</param>
/// <returns>The normalized coordinates</returns>
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);
}
}
/// <summary>
/// Gives a nicely formatted <c>string</c> for this <c>Rect</c>.
/// </summary>
/// <returns>Nicely formatted <c>string</c> for this <c>Rect</c></returns>
public override string ToString() {
return string.Format("(x:{1}, y:{2}, width:{3}, height:{4})", x, y, width, height);
}
/// <summary>
/// Determines whether this <c>Rect</c> equals another
/// </summary>
/// <param name="other">The <c>Rect</c> to compare to</param>
/// <returns><c>true</c> if the <c>Rect</c>s are equal</returns>
public bool Equals(Rect other) {
return position == other.position && size == other.size;
}
/// <summary>
/// Determines whether this <c>Rect</c> equals another <c>object</c>
/// </summary>
/// <param name="other">The <c>object</c> to compare to</param>
/// <returns><c>true</c> if the other <c>object</c> is not null and a <c>Rect</c>, and the <c>Rect</c>s are equal</returns>
public override bool Equals(object obj) {
if (object.ReferenceEquals(obj, null)) { return false; }
if (!(obj is Rect)) { return false; }
return Equals((Rect)obj);
}
/// <summary>
/// Determines whether this <c>Rect</c> equals another
/// </summary>
/// <param name="other">The <c>Rect</c> to compare to</param>
/// <returns><c>true</c> if the <c>Rect</c>s are equal</returns>
public static bool operator ==(Rect r1, Rect r2) {
return r1.Equals(r2);
}
/// <summary>
/// Determines whether this <c>Rect</c> does not equal another
/// </summary>
/// <param name="other">The <c>Rect</c> to compare to</param>
/// <returns><c>true</c> if the <c>Rect</c>s are not equal</returns>
public static bool operator !=(Rect r1, Rect r2) {
return !r1.Equals(r2);
}
/// <summary>
/// Generates a hash code for this instance based on instance data.
/// </summary>
/// <returns>The hash code for this instance</returns>
public override int GetHashCode() {
return position.GetHashCode() ^ size.GetHashCode();
}
}
}
#endif

View File

@@ -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
/// <summary>
/// Replacement UIVertex struct for Unity versions before 4.6, and standalone projects.
/// </summary>
/// <remarks>4.5 had UIVertex but the implementation was incomplete.</remarks>
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

View File

@@ -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 {
/// <summary>
/// Holds an array of two <c>double</c>s representing a 2-dimensional vector.
/// </summary>
[Serializable] public struct Vector2d : IEquatable<Vector2d>, IEnumerable, IEnumerable<double> {
/// <summary>Returns <c>Vector2d</c>(NaN, NaN, NaN)</summary>
public static Vector2d undefined { get { return new Vector2d(System.Double.NaN, System.Double.NaN); } }
/// <summary>Returns <c>Vector2d</c>(1, 0)</summary>
public static Vector2d right { get { return new Vector2d(1, 0); } }
/// <summary>Returns <c>Vector2d</c>(0, 1)</summary>
public static Vector2d up { get { return new Vector2d(0, 1); } }
/// <summary>Returns <c>Vector2d</c>(-1, 0)</summary>
public static Vector2d left { get { return new Vector2d(-1, 0); } }
/// <summary>Returns <c>Vector2d</c>(0, -1)</summary>
public static Vector2d down { get { return new Vector2d(0, -1); } }
/// <summary>Returns <c>Vector2d</c>(0, 0)</summary>
public static Vector2d zero { get { return new Vector2d(0, 0); } }
/// <summary>Returns <c>Vector2d</c>(1, 1)</summary>
public static Vector2d one { get { return new Vector2d(1, 1); } }
public double x;
public double y;
/// <summary>
/// Gets or sets a component using an indexer, x=0, y=1
/// </summary>
/// <param name="index">Component to get or set</param>
/// <returns>Component</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index" /> was negative or greater than 1</exception>
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();
}
}
}
}
/// <summary>
/// Gets the magnitude of this <c>Vector2d</c>, or its distance from (0, 0).
/// </summary>
public double magnitude { get { return Math.Sqrt(sqrMagnitude); } }
/// <summary>
/// Gets the magnitude of this <c>Vector2d</c> 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.
/// </summary>
public double sqrMagnitude { get { return System.Math.Pow(x, 2) + System.Math.Pow(y, 2); } }
/// <summary>
/// Gets the normalized version of this <c>Vector2d</c> (unit vector with the same direction).
/// </summary>
public Vector2d normalized {
get {
double magnitude = this.magnitude;
return new Vector2d(x / magnitude, y / magnitude);
}
}
/// <summary>
/// Creates a new <c>Vector2d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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]);
}
}
/// <summary>
/// Creates a new <c>Vector2d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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];
}
}
/// <summary>
/// Creates a new <c>Vector2d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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]);
}
}
/// <summary>
/// Crates a new <c>Vector2d</c> instance using the components from the supplied <c>Vector2d</c>
/// </summary>
/// <param name="vector">Vector to copy components from</param>
public Vector2d(Vector2d vector) {
x = vector.x;
y = vector.y;
}
/// <summary>
/// Creates a new <c>Vector2d</c> instance by parsing a <c>byte</c> array representing either <c>float</c>s or <c>double</c>s.
/// </summary>
/// <param name="data"><c>Byte</c> array to parse</param>
/// <exception cref="ArgumentException">The passed byte array was not 8 or 16 elements long</exception>
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");
}
}
/// <summary>
/// Adds two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector to add</param>
/// <param name="v2">Second vector to add</param>
/// <returns>The resulting vector</returns>
public static Vector2d operator +(Vector2d v1, Vector2d v2) {
return Add(v1, v2);
}
/// <summary>
/// Adds two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector to add</param>
/// <param name="v2">Second vector to add</param>
/// <returns>The resulting vector</returns>
public static Vector2d Add(Vector2d v1, Vector2d v2) {
return new Vector2d(v1.x + v2.x, v1.y + v2.y);
}
/// <summary>
/// Subtracts one vector from another. This operation is NOT commutative
/// </summary>
/// <param name="v1">Vector to subtract from</param>
/// <param name="v2">Vector to subtract</param>
/// <returns>Difference from <paramref name="v1" /> to <paramref name="v2" /></returns>
public static Vector2d operator -(Vector2d v1, Vector2d v2) {
return Subtract(v1, v2);
}
/// <summary>
/// Subtracts one vector from another. This operation is NOT commutative
/// </summary>
/// <param name="v1">Vector to subtract from</param>
/// <param name="v2">Vector to subtract</param>
/// <returns>Difference from <paramref name="v1" /> to <paramref name="v2" /></returns>
public static Vector2d Subtract(Vector2d v1, Vector2d v2) {
return new Vector2d(v1.x - v2.x, v1.y - v2.y);
}
/// <summary>
/// Returns the negative of this vector. Equivalent to (0, 0) - <paramref name="v1" />.
/// </summary>
/// <param name="v1">Vector to negate</param>
/// <returns><paramref name="v1" /> with all components negated</returns>
public static Vector2d operator -(Vector2d v1) {
return Negate(v1);
}
/// <summary>
/// Returns the negative of this vector. Equivalent to (0, 0) - <paramref name="v1" />.
/// </summary>
/// <param name="v1">Vector to negate</param>
/// <returns><paramref name="v1" /> with all components negated</returns>
public static Vector2d Negate(Vector2d v1) {
return new Vector2d(-v1.x, -v1.y);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to scale</param>
/// <param name="scalar">Scalar</param>
/// <returns>Resulting Vector</returns>
public static Vector2d operator *(Vector2d v1, double scalar) {
return Scale(v1, scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="scalar">Scalar</param>
/// <param name="v1">Vector to scale</param>
/// <returns>Resulting Vector</returns>
public static Vector2d operator *(double scalar, Vector2d v1) {
return Scale(v1, scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to scale</param>
/// <param name="scalar">Scalar</param>
/// <returns>Resulting Vector</returns>
public static Vector2d Scale(Vector2d v1, double scalar) {
return new Vector2d(v1.x * scalar, v1.y * scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="scalar">Scalar</param>
/// <param name="v1">Vector to scale</param>
/// <returns>Resulting Vector</returns>
public static Vector2d Scale(double scalar, Vector2d v1) {
return Scale(v1, scalar);
}
/// <summary>
/// Multiplies two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Resulting vector when the passed vectors' components are multiplied</returns>
public static Vector2d Scale(Vector2d v1, Vector2d v2) {
return new Vector2d(v1.x * v2.x, v1.y * v2.y);
}
/// <summary>
/// Vector dot product. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Dot product of these two vectors</returns>
public static double operator *(Vector2d v1, Vector2d v2) {
return Dot(v1, v2);
}
/// <summary>
/// Vector dot product. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Dot product of these two vectors</returns>
public static double Dot(Vector2d v1, Vector2d v2) {
return v1.x * v2.x + v1.y * v2.y;
}
/// <summary>
/// Scalar division. Divides all components of <paramref name="v1" /> by <paramref name="divisor" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to divide</param>
/// <param name="divisor">Divisor</param>
/// <returns>Resulting vector when all components of <paramref name="v1" /> are divided by <paramref name="divisor" />.</returns>
public static Vector2d operator /(Vector2d v1, double divisor) {
return Scale(v1, 1.0 / divisor);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are equal or approximately equal.</returns>
public static bool operator ==(Vector2d v1, Vector2d v2) {
return v1.Equals(v2);
}
/// <summary>
/// Non-Equivalency. Returns <c>true</c> if the components of two vectors are not equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are not equal or approximately equal.</returns>
public static bool operator !=(Vector2d v1, Vector2d v2) {
return !v1.Equals(v2);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are equal or approximately equal.</returns>
public bool Equals(Vector2d other) {
return (Math.Abs(x - other.x) < 0.001 && Math.Abs(y - other.y) < 0.001);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the other object is a vector, and the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the other object is a vector, and the components of two vectors are equal or approximately equal.</returns>
public override bool Equals(object obj) {
if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; }
return Equals((Vector2d)obj);
}
/// <summary>
/// Generates a hash code for this instance based on instance data.
/// </summary>
/// <returns>The hash code for this instance</returns>
public override int GetHashCode() {
return x.GetHashCode() ^ y.GetHashCode();
}
/// <summary>
/// Calculates the distance from this vector to another.
/// </summary>
/// <param name="to">Vector to calculate distance to</param>
/// <returns>Distance from this vector to the passed vector</returns>
public double Distance(Vector2d to) {
return (this - to).magnitude;
}
/// <summary>
/// Gets a human-readable string representation of this vector.
/// </summary>
/// <returns>Human-readable string representation of this vector</returns>
public override string ToString() {
return string.Format("( {0} , {1} )", x.ToString(), y.ToString());
}
/// <summary>
/// Changes this vector to its normalized version (it will have a magnitude of 1).
/// </summary>
public void Normalize() {
double magnitude = this.magnitude;
x /= magnitude;
y /= magnitude;
}
/// <summary>
/// Gets the area of the triangle defined by three points using Heron's formula.
/// </summary>
/// <param name="p1">First vertex of triangle</param>
/// <param name="p2">Second vertex of triangle</param>
/// <param name="p3">Third vertex of triangle</param>
/// <returns>Area of the triangle defined by these three vertices</returns>
public static double TriangleArea(Vector3d p1, Vector3d p2, Vector3d p3) {
return Math.Sqrt(SqrTriangleArea(p1, p2, p3)) / 4.0;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="p1">First vertex of triangle</param>
/// <param name="p2">Second vertex of triangle</param>
/// <param name="p3">Third vertex of triangle</param>
/// <returns>Square of the area of the triangle defined by these three vertices</returns>
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);
}
/// <summary>
/// Allows enumeration through the components of a <c>Vector2d</c> using a foreach loop.
/// </summary>
public IEnumerator<double> GetEnumerator() {
yield return x;
yield return y;
}
/// <summary>
/// Allows enumeration through the components of a <c>Vector2d</c> using a foreach loop, auto-boxed version.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
IEnumerator IEnumerable.GetEnumerator() {
yield return x;
yield return y;
}
/// <summary>
/// Implicitly converts this <c>Vector2d</c> into a <c>Vector3d</c>. This will be called when Vector3d v3 = v2 is used.
/// </summary>
/// <param name="v"><c>Vector2d</c> to convert</param>
/// <returns>The input vector as a <c>Vector3d</c>, Z component set to 0</returns>
public static implicit operator Vector3d(Vector2d v) {
return new Vector3d(v.x, v.y, 0);
}
/// <summary>
/// Implicitly converts this <c>Vector2d</c> into a <c>Vector4d</c>. This will be called when Vector4d v4 = v2 is used.
/// </summary>
/// <param name="v"><c>Vector2d</c> to convert</param>
/// <returns>The input vector as a <c>Vector4d</c>, Z and W components set to 0</returns>
public static implicit operator Vector4d(Vector2d v) {
return new Vector4d(v.x, v.y, 0, 0);
}
}
}
#endif

View File

@@ -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;
/// <summary>
/// Holds an array of three <c>double</c>s representing a 3-dimensional vector.
/// </summary>
[Serializable] public struct Vector3d : IEquatable<Vector3d>, IEnumerable, IEnumerable<double> {
/// <summary>Returns <c>Vector3d</c>(NaN, NaN, NaN)</summary>
public static Vector3d undefined { get { return new Vector3d(System.Double.NaN, System.Double.NaN, System.Double.NaN); } }
/// <summary>Returns <c>Vector3d</c>(1, 0, 0)</summary>
public static Vector3d right { get { return new Vector3d(1, 0, 0); } }
/// <summary>Returns <c>Vector3d</c>(0, 1, 0)</summary>
public static Vector3d forward { get { return new Vector3d(0, 1, 0); } }
/// <summary>Returns <c>Vector3d</c>(0, 0, 1)</summary>
public static Vector3d up { get { return new Vector3d(0, 0, 1); } }
/// <summary>Returns <c>Vector3d</c>(-1, 0, 0)</summary>
public static Vector3d left { get { return new Vector3d(-1, 0, 0); } }
/// <summary>Returns <c>Vector3d</c>(0, -1, 0)</summary>
public static Vector3d back { get { return new Vector3d(0, -1, 0); } }
/// <summary>Returns <c>Vector3d</c>(0, 0, -1)</summary>
public static Vector3d down { get { return new Vector3d(0, 0, -1); } }
/// <summary>Returns <c>Vector3d</c>(0, 0, 0)</summary>
public static Vector3d zero { get { return new Vector3d(0, 0, 0); } }
/// <summary>Returns <c>Vector3d</c>(1, 1, 1)</summary>
public static Vector3d one { get { return new Vector3d(1, 1, 1); } }
public double x;
public double y;
public double z;
/// <summary>
/// Gets or sets a component using an indexer, x=0, y=1, z=2
/// </summary>
/// <param name="index">Component to get or set</param>
/// <returns>Component</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index" /> was negative or greater than 2</exception>
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();
}
}
}
}
/// <summary>
/// Gets the magnitude of this <c>Vector3d</c>, or its distance from (0, 0, 0).
/// </summary>
public double magnitude { get { return Math.Sqrt(sqrMagnitude); } }
/// <summary>
/// Gets the magnitude of this <c>Vector3d</c> 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.
/// </summary>
public double sqrMagnitude { get { return System.Math.Pow(x, 2) + System.Math.Pow(y, 2) + System.Math.Pow(z, 2); } }
/// <summary>
/// Gets the normalized version of this <c>Vector3d</c> (unit vector with the same direction).
/// </summary>
public Vector3d normalized {
get {
double magnitude = this.magnitude;
return new Vector3d(x / magnitude, y / magnitude, z / magnitude);
}
}
/// <summary>
/// Creates a new <c>Vector3d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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]);
}
}
/// <summary>
/// Creates a new <c>Vector3d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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];
}
}
/// <summary>
/// Creates a new <c>Vector3d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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]);
}
}
/// <summary>
/// Crates a new <c>Vector3d</c> instance using the components from the supplied <c>Vector3d</c>
/// </summary>
/// <param name="vector">Vector to copy components from</param>
public Vector3d(Vector3d vector) {
x = vector.x;
y = vector.y;
z = vector.z;
}
/// <summary>
/// Creates a new <c>Vector3d</c> instance by parsing a <c>byte</c> array representing either <c>float</c>s or <c>double</c>s.
/// </summary>
/// <param name="data"><c>Byte</c> array to parse</param>
/// <exception cref="ArgumentException">The passed byte array was not 12 or 24 elements long</exception>
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");
}
}
/// <summary>
/// Adds two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector to add</param>
/// <param name="v2">Second vector to add</param>
/// <returns>The resulting vector</returns>
public static Vector3d operator +(Vector3d v1, Vector3d v2) {
return Add(v1, v2);
}
/// <summary>
/// Adds two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector to add</param>
/// <param name="v2">Second vector to add</param>
/// <returns>The resulting vector</returns>
public static Vector3d Add(Vector3d v1, Vector3d v2) {
return new Vector3d(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
}
/// <summary>
/// Subtracts one vector from another. This operation is NOT commutative
/// </summary>
/// <param name="v1">Vector to subtract from</param>
/// <param name="v2">Vector to subtract</param>
/// <returns>Difference from <paramref name="v1" /> to <paramref name="v2" /></returns>
public static Vector3d operator -(Vector3d v1, Vector3d v2) {
return Subtract(v1, v2);
}
/// <summary>
/// Subtracts one vector from another. This operation is NOT commutative
/// </summary>
/// <param name="v1">Vector to subtract from</param>
/// <param name="v2">Vector to subtract</param>
/// <returns>Difference from <paramref name="v1" /> to <paramref name="v2" /></returns>
public static Vector3d Subtract(Vector3d v1, Vector3d v2) {
return new Vector3d(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
}
/// <summary>
/// Returns the negative of this vector. Equivalent to (0, 0, 0) - <paramref name="v1" />.
/// </summary>
/// <param name="v1">Vector to negate</param>
/// <returns><paramref name="v1" /> with all components negated</returns>
public static Vector3d operator -(Vector3d v1) {
return Negate(v1);
}
/// <summary>
/// Returns the negative of this vector. Equivalent to (0, 0, 0) - <paramref name="v1" />.
/// </summary>
/// <param name="v1">Vector to negate</param>
/// <returns><paramref name="v1" /> with all components negated</returns>
public static Vector3d Negate(Vector3d v1) {
return new Vector3d(-v1.x, -v1.y, -v1.z);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to scale</param>
/// <param name="scalar">Scalar</param>
/// <returns>Resulting Vector</returns>
public static Vector3d operator *(Vector3d v1, double scalar) {
return Scale(v1, scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="scalar">Scalar</param>
/// <param name="v1">Vector to scale</param>
/// <returns>Resulting Vector</returns>
public static Vector3d operator *(double scalar, Vector3d v1) {
return Scale(v1, scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to scale</param>
/// <param name="scalar">Scalar</param>
/// <returns>Resulting Vector</returns>
public static Vector3d Scale(Vector3d v1, double scalar) {
return new Vector3d(v1.x * scalar, v1.y * scalar, v1.z * scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="scalar">Scalar</param>
/// <param name="v1">Vector to scale</param>
/// <returns>Resulting Vector</returns>
public static Vector3d Scale(double scalar, Vector3d v1) {
return Scale(v1, scalar);
}
/// <summary>
/// Multiplies two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Resulting vector when the passed vectors' components are multiplied</returns>
public static Vector3d Scale(Vector3d v1, Vector3d v2) {
return new Vector3d(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z);
}
/// <summary>
/// Vector dot product. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Dot product of these two vectors</returns>
public static double operator *(Vector3d v1, Vector3d v2) {
return Dot(v1, v2);
}
/// <summary>
/// Vector dot product. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Dot product of these two vectors</returns>
public static double Dot(Vector3d v1, Vector3d v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
/// <summary>
/// Vector cross product. This operation is NOT commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Cross product of these two vectors. Can be thought of as the normal to the plane defined by these two vectors.</returns>
public static Vector3d operator ^(Vector3d v1, Vector3d v2) {
return Cross(v1, v2);
}
/// <summary>
/// Vector cross product. This operation is NOT commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Cross product of these two vectors. Can be thought of as the normal to the plane defined by these two vectors.</returns>
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);
}
/// <summary>
/// Scalar division. Divides all components of <paramref name="v1" /> by <paramref name="divisor" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to divide</param>
/// <param name="divisor">Divisor</param>
/// <returns>Resulting vector when all components of <paramref name="v1" /> are divided by <paramref name="divisor" />.</returns>
public static Vector3d operator /(Vector3d v1, double divisor) {
return Scale(v1, 1.0 / divisor);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are equal or approximately equal.</returns>
public static bool operator ==(Vector3d v1, Vector3d v2) {
return v1.Equals(v2);
}
/// <summary>
/// Non-Equivalency. Returns <c>true</c> if the components of two vectors are not equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are not equal or approximately equal.</returns>
public static bool operator !=(Vector3d v1, Vector3d v2) {
return !v1.Equals(v2);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are equal or approximately equal.</returns>
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);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the other object is a vector, and the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the other object is a vector, and the components of two vectors are equal or approximately equal.</returns>
public override bool Equals(object obj) {
if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; }
return Equals((Vector3d)obj);
}
/// <summary>
/// Generates a hash code for this instance based on instance data.
/// </summary>
/// <returns>The hash code for this instance</returns>
public override int GetHashCode() {
return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
}
/// <summary>
/// Calculates the distance from this vector to another.
/// </summary>
/// <param name="to">Vector to calculate distance to</param>
/// <returns>Distance from this vector to the passed vector</returns>
public double Distance(Vector3d to) {
return (this - to).magnitude;
}
/// <summary>
/// Gets a human-readable string representation of this vector.
/// </summary>
/// <returns>Human-readable string representation of this vector</returns>
public override string ToString() {
return string.Format("( {0} , {1} , {2} )", x.ToString(), y.ToString(), z.ToString());
}
/// <summary>
/// Changes this vector to its normalized version (it will have a magnitude of 1).
/// </summary>
public void Normalize() {
double magnitude = this.magnitude;
x /= magnitude;
y /= magnitude;
z /= magnitude;
}
/// <summary>
/// Gets the area of the triangle defined by three points using Heron's formula.
/// </summary>
/// <param name="p1">First vertex of triangle</param>
/// <param name="p2">Second vertex of triangle</param>
/// <param name="p3">Third vertex of triangle</param>
/// <returns>Area of the triangle defined by these three vertices</returns>
public static double TriangleArea(Vector3d p1, Vector3d p2, Vector3d p3) {
return Math.Sqrt(SqrTriangleArea(p1, p2, p3)) / 4.0;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="p1">First vertex of triangle</param>
/// <param name="p2">Second vertex of triangle</param>
/// <param name="p3">Third vertex of triangle</param>
/// <returns>Square of the area of the triangle defined by these three vertices</returns>
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);
}
/// <summary>
/// Allows enumeration through the components of a <c>Vector3d</c> using a foreach loop.
/// </summary>
public IEnumerator<double> GetEnumerator() {
yield return x;
yield return y;
yield return z;
}
/// <summary>
/// Allows enumeration through the components of a <c>Vector3d</c> using a foreach loop, auto-boxed version.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
IEnumerator IEnumerable.GetEnumerator() {
yield return x;
yield return y;
yield return z;
}
/// <summary>
/// Implicitly converts this <c>Vector3d</c> into a <c>Vector2d</c>. This will be called when Vector2d v2 = v3 is used.
/// </summary>
/// <param name="v"><c>Vector3d</c> to convert</param>
/// <returns>The input vector as a <c>Vector2d</c>, Z component discarded</returns>
public static implicit operator Vector2d(Vector3d v) {
return new Vector2d(v.x, v.y);
}
/// <summary>
/// Implicitly converts this <c>Vector3d</c> into a <c>Vector4d</c>. This will be called when Vector4d v4 = v3 is used.
/// </summary>
/// <param name="v"><c>Vector3d</c> to convert</param>
/// <returns>The input vector as a <c>Vector4d</c>, W component set to 0</returns>
public static implicit operator Vector4d(Vector3d v) {
return new Vector4d(v.x, v.y, v.z, 0);
}
/// <summary>
/// Implicitly converts this <c>Vector3d</c> into a <c>Color</c> by interpreting (x, y, z) as (r, g, b) respectively.
/// </summary>
/// <param name="v"><c>Vector3d</c> to convert</param>
/// <returns>This <c>Vector3d</c> in a <c>Color</c> object interpreted as RGB.</returns>
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

View File

@@ -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;
/// <summary>
/// Holds an array of four <c>double</c>s representing a 4-dimensional vector.
/// </summary>
[Serializable] public struct Vector4d : IEquatable<Vector4d>, IEnumerable, IEnumerable<double> {
/// <summary>Returns <c>Vector4d</c>(NaN, NaN, NaN)</summary>
public static Vector4d undefined { get { return new Vector4d(System.Double.NaN, System.Double.NaN, System.Double.NaN, System.Double.NaN); } }
/// <summary>Returns <c>Vector4d</c>(0, 0, 0, 0)</summary>
public static Vector4d zero { get { return new Vector4d(0, 0, 0, 0); } }
/// <summary>Returns <c>Vector4d</c>(1, 1, 1, 1)</summary>
public static Vector4d one { get { return new Vector4d(1, 1, 1, 1); } }
public double x;
public double y;
public double z;
public double w;
/// <summary>
/// Gets or sets a component using an indexer, x=0, y=1, z=2, w=3
/// </summary>
/// <param name="index">Component to get or set</param>
/// <returns>Component</returns>
/// <exception cref="IndexOutOfRangeException"><paramref name="index" /> was negative or greater than 3</exception>
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();
}
}
}
}
/// <summary>
/// Gets the magnitude of this <c>Vector4d</c>, or its distance from (0, 0, 0, 0).
/// </summary>
public double magnitude { get { return Math.Sqrt(sqrMagnitude); } }
/// <summary>
/// Gets the magnitude of this <c>Vector4d</c> 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.
/// </summary>
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); } }
/// <summary>
/// Gets the normalized version of this <c>Vector4d</c> (unit vector with the same direction).
/// </summary>
public Vector4d normalized {
get {
double magnitude = this.magnitude;
return new Vector4d(x / magnitude, y / magnitude, z / magnitude, w / magnitude);
}
}
/// <summary>
/// Creates a new <c>Vector4d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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]);
}
}
/// <summary>
/// Creates a new <c>Vector4d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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];
}
}
/// <summary>
/// Creates a new <c>Vector4d</c> object using elements in the passed array as components
/// </summary>
/// <param name="point">Components of the vector</param>
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]);
}
}
/// <summary>
/// Crates a new <c>Vector4d</c> instance using the components from the supplied <c>Vector4d</c>
/// </summary>
/// <param name="vector">Vector to copy components from</param>
public Vector4d(Vector4d vector) {
x = vector.x;
y = vector.y;
z = vector.z;
w = vector.w;
}
/// <summary>
/// Creates a new <c>Vector4d</c> instance by parsing a <c>byte</c> array representing either <c>float</c>s or <c>double</c>s.
/// </summary>
/// <param name="data"><c>Byte</c> array to parse</param>
/// <exception cref="ArgumentException">The passed byte array was not 16 or 32 elements long</exception>
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");
}
}
/// <summary>
/// Adds two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector to add</param>
/// <param name="v2">Second vector to add</param>
/// <returns>The resulting vector</returns>
public static Vector4d operator +(Vector4d v1, Vector4d v2) {
return Add(v1, v2);
}
/// <summary>
/// Adds two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector to add</param>
/// <param name="v2">Second vector to add</param>
/// <returns>The resulting vector</returns>
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);
}
/// <summary>
/// Subtracts one vector from another. This operation is NOT commutative
/// </summary>
/// <param name="v1">Vector to subtract from</param>
/// <param name="v2">Vector to subtract</param>
/// <returns>Difference from <paramref name="v1" /> to <paramref name="v2" /></returns>
public static Vector4d operator -(Vector4d v1, Vector4d v2) {
return Subtract(v1, v2);
}
/// <summary>
/// Subtracts one vector from another. This operation is NOT commutative
/// </summary>
/// <param name="v1">Vector to subtract from</param>
/// <param name="v2">Vector to subtract</param>
/// <returns>Difference from <paramref name="v1" /> to <paramref name="v2" /></returns>
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);
}
/// <summary>
/// Returns the negative of this vector. Equivalent to (0, 0, 0, 0) - <paramref name="v1" />.
/// </summary>
/// <param name="v1">Vector to negate</param>
/// <returns><paramref name="v1" /> with all components negated</returns>
public static Vector4d operator -(Vector4d v1) {
return Negate(v1);
}
/// <summary>
/// Returns the negative of this vector. Equivalent to (0, 0, 0, 0) - <paramref name="v1" />.
/// </summary>
/// <param name="v1">Vector to negate</param>
/// <returns><paramref name="v1" /> with all components negated</returns>
public static Vector4d Negate(Vector4d v1) {
return new Vector4d(-v1.x, -v1.y, -v1.z, -v1.w);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to scale</param>
/// <param name="scalar">Scalar</param>
/// <returns>Resulting Vector</returns>
public static Vector4d operator *(Vector4d v1, double scalar) {
return Scale(v1, scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="scalar">Scalar</param>
/// <param name="v1">Vector to scale</param>
/// <returns>Resulting Vector</returns>
public static Vector4d operator *(double scalar, Vector4d v1) {
return Scale(v1, scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to scale</param>
/// <param name="scalar">Scalar</param>
/// <returns>Resulting Vector</returns>
public static Vector4d Scale(Vector4d v1, double scalar) {
return new Vector4d(v1.x * scalar, v1.y * scalar, v1.z * scalar, v1.w * scalar);
}
/// <summary>
/// Scalar multiplication. Multiplies all components of <paramref name="v1" /> by <paramref name="scalar" /> and returns the result.
/// </summary>
/// <param name="scalar">Scalar</param>
/// <param name="v1">Vector to scale</param>
/// <returns>Resulting Vector</returns>
public static Vector4d Scale(double scalar, Vector4d v1) {
return Scale(v1, scalar);
}
/// <summary>
/// Multiplies two vectors together componentwise. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Resulting vector when the passed vectors' components are multiplied</returns>
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);
}
/// <summary>
/// Vector dot product. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Dot product of these two vectors</returns>
public static double operator *(Vector4d v1, Vector4d v2) {
return Dot(v1, v2);
}
/// <summary>
/// Vector dot product. This operation is commutative.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns>Dot product of these two vectors</returns>
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;
}
/// <summary>
/// Scalar division. Divides all components of <paramref name="v1" /> by <paramref name="divisor" /> and returns the result.
/// </summary>
/// <param name="v1">Vector to divide</param>
/// <param name="divisor">Divisor</param>
/// <returns>Resulting vector when all components of <paramref name="v1" /> are divided by <paramref name="divisor" />.</returns>
public static Vector4d operator /(Vector4d v1, double divisor) {
return Scale(v1, 1.0 / divisor);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are equal or approximately equal.</returns>
public static bool operator ==(Vector4d v1, Vector4d v2) {
return v1.Equals(v2);
}
/// <summary>
/// Non-Equivalency. Returns <c>true</c> if the components of two vectors are not equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are not equal or approximately equal.</returns>
public static bool operator !=(Vector4d v1, Vector4d v2) {
return !v1.Equals(v2);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the components of two vectors are equal or approximately equal.</returns>
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);
}
/// <summary>
/// Equivalency. Returns <c>true</c> if the other object is a vector, and the components of two vectors are equal or approximately equal.
/// </summary>
/// <param name="v1">First vector</param>
/// <param name="v2">Second vector</param>
/// <returns><c>true</c> if the other object is a vector, and the components of two vectors are equal or approximately equal.</returns>
public override bool Equals(object obj) {
if (object.ReferenceEquals(obj, null) || !GetType().IsAssignableFrom(obj.GetType())) { return false; }
return Equals((Vector4d)obj);
}
/// <summary>
/// Generates a hash code for this instance based on instance data.
/// </summary>
/// <returns>The hash code for this instance</returns>
public override int GetHashCode() {
return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode();
}
/// <summary>
/// Calculates the distance from this vector to another.
/// </summary>
/// <param name="to">Vector to calculate distance to</param>
/// <returns>Distance from this vector to the passed vector</returns>
public double Distance(Vector4d to) {
return (this - to).magnitude;
}
/// <summary>
/// Gets a human-readable string representation of this vector.
/// </summary>
/// <returns>Human-readable string representation of this vector</returns>
public override string ToString() {
return string.Format("( {0} , {1} , {2} , {3} )", x.ToString(), y.ToString(), z.ToString(), w.ToString());
}
/// <summary>
/// Changes this vector to its normalized version (it will have a magnitude of 1).
/// </summary>
public void Normalize() {
double magnitude = this.magnitude;
x /= magnitude;
y /= magnitude;
z /= magnitude;
w /= magnitude;
}
/// <summary>
/// Gets the area of the triangle defined by three points using Heron's formula.
/// </summary>
/// <param name="p1">First vertex of triangle</param>
/// <param name="p2">Second vertex of triangle</param>
/// <param name="p3">Third vertex of triangle</param>
/// <returns>Area of the triangle defined by these three vertices</returns>
public static double TriangleArea(Vector4d p1, Vector4d p2, Vector4d p3) {
return Math.Sqrt(SqrTriangleArea(p1, p2, p3)) / 4.0;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="p1">First vertex of triangle</param>
/// <param name="p2">Second vertex of triangle</param>
/// <param name="p3">Third vertex of triangle</param>
/// <returns>Square of the area of the triangle defined by these three vertices</returns>
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);
}
/// <summary>
/// Allows enumeration through the components of a <c>Vector4d</c> using a foreach loop.
/// </summary>
public IEnumerator<double> GetEnumerator() {
yield return x;
yield return y;
yield return z;
yield return w;
}
/// <summary>
/// Allows enumeration through the components of a <c>Vector4d</c> using a foreach loop, auto-boxed version.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
IEnumerator IEnumerable.GetEnumerator() {
yield return x;
yield return y;
yield return z;
yield return w;
}
/// <summary>
/// Implicitly converts this <c>Vector4d</c> into a <c>Vector2d</c>. This will be called when Vector2d v2 = v4 is used.
/// </summary>
/// <param name="v"><c>Vector4d</c> to convert</param>
/// <returns>The input vector as a <c>Vector2d</c>, Z and W components discarded</returns>
public static implicit operator Vector2d(Vector4d v) {
return new Vector2d(v.x, v.y);
}
/// <summary>
/// Implicitly converts this <c>Vector4d</c> into a <c>Vector3d</c>. This will be called when Vector3d v3 = v4 is used.
/// </summary>
/// <param name="v"><c>Vector4d</c> to convert</param>
/// <returns>The input vector as a <c>Vector3d</c>, W component discarded</returns>
public static implicit operator Vector3d(Vector4d v) {
return new Vector3d(v.x, v.y, v.z);
}
/// <summary>
/// Implicitly converts this <c>Vector4d</c> into a <c>Color</c> by interpreting (x, y, z) as (r, g, b) respectively, and w as alpha.
/// Assumes colors range from 0 to 255.
/// </summary>
/// <param name="v"><c>Vector4d</c> to convert</param>
/// <returns>This <c>Vector4d</c> in a <c>Color</c> object interpreted as RGBA.</returns>
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

View File

@@ -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 {
/// <summary>
/// Gathers all relevant information from the lumps of a Doom Map.
/// </summary>
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<Thing> things;
public List<Linedef> linedefs;
public List<Sidedef> sidedefs;
public List<UIVertex> vertices;
public List<Segment> segs;
public List<Edge> subsectors;
public List<Node> nodes;
public List<Sector> sectors;
/// <summary>
/// Gets the folder path for the WAD containing this map
/// </summary>
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));
}
}
/// <summary>
/// Gets the name of the WAD file containing this map
/// </summary>
public string WadName {
get {
System.IO.FileInfo newFile = new System.IO.FileInfo(wadPath);
return newFile.Name.Substring(0, (newFile.Name.Length - 4) - (0));
}
}
/// <summary>
/// Creates a new <c>DoomMap</c> instance with no initial data. The <c>List</c>s in this class must be populated elsewhere.
/// </summary>
/// <param name="wadpath">The path to the .WAD file</param>
/// <param name="map">The name of the map within the WAD file</param>
/// <param name="version">The version of the map</param>
public DoomMap(string wadpath, string map, MapType version) {
this.wadPath = wadpath;
this.mapName = map;
this.version = version;
}
}
}

View File

@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
namespace LibBSP.Doom {
/// <summary>
/// Contains all necessary information for a Doom LINEDEF object.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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);
}
}
/// <summary>
/// Creates a new <c>Linedef</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Linedef</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Linedef</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Linedef> 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<Linedef> lump = new List<Linedef>(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;
}
}
}

View File

@@ -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
/// <summary>
/// Holds all the data for a node in a Doom map.
/// </summary>
/// <remarks>
/// This is the one lump that has a structure similar to future BSPs.
/// </remarks>
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; }
/// <summary>
/// Creates a new <c>Node</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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);
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Node</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Node</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
public static List<Node> LumpFactory(byte[] data, MapType type) {
if (data == null) {
throw new ArgumentNullException();
}
int structLength = 28;
List<Node> lump = new List<Node>(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;
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
namespace LibBSP.Doom {
/// <summary>
/// Contains all necessary information for a Doom SECTOR object.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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; }
/// <summary>
/// Creates a new <c>Sector</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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);
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Sector</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Sector</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
public static List<Sector> LumpFactory(byte[] data, MapType type) {
int structLength = 26;
List<Sector> lump = new List<Sector>(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;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
namespace LibBSP.Doom {
/// <summary>
/// This class holds data of a single Doom line Segment
/// </summary>
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; }
/// <summary>
/// Creates a new <c>Segment</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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);
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Segment</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Segment</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
public static List<Segment> LumpFactory(byte[] data, MapType type) {
if (data == null) {
throw new ArgumentNullException();
}
int structLength = 12;
List<Segment> lump = new List<Segment>(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;
}
}
}

View File

@@ -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
/// <summary>
/// Contains all necessary information for a Doom SIDEDEF object.
/// </summary>
/// <remarks>
/// The sidedef is roughly equivalent to the Face (or surface)
/// object in later BSP versions.
/// </remarks>
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; }
/// <summary>
/// Creates a new <c>Sidedef</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
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);
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Sidedef</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Sidedef</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
public static List<Sidedef> LumpFactory(byte[] data, MapType type) {
if (data == null) {
throw new ArgumentNullException();
}
int structLength = 30;
List<Sidedef> lump = new List<Sidedef>(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;
}
}
}

View File

@@ -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
/// <summary>
/// Contains all necessary information for a Doom THING object. Essentially Doom's entities.
/// </summary>
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; }
/// <summary>
/// Creates a new <c>Thing</c> object from a <c>byte</c> array.
/// </summary>
/// <param name="data"><c>byte</c> array to parse</param>
/// <param name="type">The map type</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
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.");
}
}
}
/// <summary>
/// Factory method to parse a <c>byte</c> array into a <c>List</c> of <c>Thing</c> objects.
/// </summary>
/// <param name="data">The data to parse</param>
/// <param name="type">The map type</param>
/// <returns>A <c>List</c> of <c>Thing</c> objects</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype</exception>
public static List<Thing> 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<Thing> lump = new List<Thing>(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;
}
}
}

View File

@@ -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 {
/// <summary>
/// Class containing all data for a single brush, including side definitions or a patch definition
/// </summary>
public class MAPBrush {
public List<MAPBrushSide> sides = new List<MAPBrushSide>(6);
public MAPPatch patch;
public bool isDetail { get; private set; }
/// <summary>
/// Creates a new <c>MAPBrush</c> object using the supplied <c>string</c> array as data
/// </summary>
/// <param name="lines">Data to parse</param>
public MAPBrush(string[] lines) {
int braceCount = 0;
bool brushDef3 = false;
bool inPatch = false;
List<string> child = new List<string>();
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<string>();
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<string>();
}
} else if (braceCount > 1) {
child.Add(line);
}
}
}
}
}

View File

@@ -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
/// <summary>
/// Class containing data for a brush side. Please note vertices must be set manually or generated through CSG
/// </summary>
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;
/// <summary>
/// Constructs a <c>MAPBrushSide</c> object using the provided <c>string</c> array as the data.
/// </summary>
/// <param name="lines">Data to parse</param>
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 {
//<x_shift> <y_shift> <rotation> <x_scale> <y_scale> <content_flags> <surface_flags> <value>
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<string> child = new List<string>(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<string>(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);
}
}
}
}
}
}
}

View File

@@ -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
/// <summary>
/// Class containing all data necessary to render a displacement from Source engine.
/// </summary>
public struct MAPDisplacement {
public int power;
public Vector3 start;
public Vector3[][] normals;
public float[][] distances;
public float[][] alphas;
/// <summary>
/// Constructs a <c>MAPDisplacement</c> object using the provided <c>string</c> array as the data.
/// </summary>
/// <param name="lines">Data to parse</param>
public MAPDisplacement(string[] lines) {
power = 0;
start = new Vector3(Single.NaN, Single.NaN, Single.NaN);
normals = null;
distances = null;
alphas = null;
Dictionary<int, string[]> normalsTokens = new Dictionary<int, string[]>(5);
Dictionary<int, string[]> distancesTokens = new Dictionary<int, string[]>(5);
Dictionary<int, string[]> alphasTokens = new Dictionary<int, string[]>(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]);
}
}
}
}
}

View File

@@ -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
/// <summary>
/// Class containing all data necessary to render a Bezier patch.
/// </summary>
public class MAPPatch {
public UIVertex[] points;
public Vector2 dims;
public string texture;
/// <summary>
/// Constructs a new <c>MAPPatch</c> object using the supplied string array as data.
/// </summary>
/// <param name="lines">Data to parse</param>
public MAPPatch(string[] lines) {
texture = lines[2];
List<UIVertex> vertices = new List<UIVertex>(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]));
}
}
}
}
}