Skip to content

Commit

Permalink
Very basic bsp visibility functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
LogicAndTrick committed Dec 29, 2023
1 parent dd7e486 commit 6a9e8c2
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Sledge.Formats.Bsp.Tests/Resources/quake/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.bsp
!aaa.bsp
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
I've ignored these files because including them is questionable in terms of copyright.
put them here so it loads the stuff

e1m3: Quake 1 (original, not remaster)
24 changes: 24 additions & 0 deletions Sledge.Formats.Bsp.Tests/Sledge.Formats.Bsp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,28 @@
<ProjectReference Include="..\Sledge.Formats\Sledge.Formats.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Resources\goldsource\aaa.bsp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\goldsource\ba_hazard6.bsp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\goldsource\c1a0b.bsp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\goldsource\grunts2.bsp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\goldsource\put_these_files_here.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\quake\e1m3.bsp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Resources\quake\e1m3.vis.gz">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
111 changes: 111 additions & 0 deletions Sledge.Formats.Bsp.Tests/TestVisibility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sledge.Formats.Bsp.Lumps;

namespace Sledge.Formats.Bsp.Tests;

[TestClass]
public class TestVisibility
{
private static MemoryStream GetFile(string name)
{
// Walk up the folder tree until we hit Sledge.Formats.Bsp.Tests
var dir = Environment.CurrentDirectory;
while (dir != null && !File.Exists(Path.Combine(dir, "Resources", name)))
{
dir = Path.GetDirectoryName(dir);
}
var file = Path.Combine(dir, "Resources", name);
using var res = File.OpenRead(file);

var ms = new MemoryStream();
res.CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}

private static bool[][] GetVisibility()
{
// file format
/*
maps/e1m3.bsp // file name
1689 // number of leafs
1 0 0 0 0 0 0 0 ... // leaf 0
1 0 0 0 0 0 0 0 ... // leaf 1
................... // etc
*/
using var file = GetFile("quake/e1m3.vis.gz");
using var gz = new GZipStream(file, CompressionMode.Decompress);
using var ms = new MemoryStream();
gz.CopyTo(ms);

var lines = Encoding.ASCII.GetString(ms.ToArray()).Split("\n").Select(x => x.Trim()).ToList();
var mapname = lines[0];
Assert.AreEqual("maps/e1m3.bsp", mapname);

var numleafs = int.Parse(lines[1]);
Assert.AreEqual(1689, numleafs);

var result = new bool[numleafs][];
for (var i = 0; i < numleafs; i++)
{
result[i] = lines[i + 2].Split(' ').Where(x => x == "0" || x == "1").Select(x => x == "1").ToArray();
}
return result;
}

[TestMethod]
public void TestVisibilitySelf()
{
using var file = GetFile("quake/e1m3.bsp");
var bsp = new BspFile(file);
var leaf = bsp.Leaves.First();
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 1, 1));
}

[TestMethod]
public void TestVisibilityBasic()
{
using var file = GetFile("quake/e1m3.bsp");
var bsp = new BspFile(file);
Assert.IsFalse(bsp.Visibility.IsVisible(bsp.Leaves, 4, 1));
Assert.IsFalse(bsp.Visibility.IsVisible(bsp.Leaves, 4, 2));
Assert.IsFalse(bsp.Visibility.IsVisible(bsp.Leaves, 4, 3));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 4));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 5));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 6));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 7));
Assert.IsFalse(bsp.Visibility.IsVisible(bsp.Leaves, 4, 8));
Assert.IsFalse(bsp.Visibility.IsVisible(bsp.Leaves, 4, 9));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 10));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 11));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 12));
Assert.IsTrue(bsp.Visibility.IsVisible(bsp.Leaves, 4, 13));
Assert.IsFalse(bsp.Visibility.IsVisible(bsp.Leaves, 4, 14));
}

[TestMethod] // well, 100 is close to full...sort of
public void TestVisibilityFull()
{
using var file = GetFile("quake/e1m3.bsp");
var bsp = new BspFile(file);
var vis = GetVisibility();
Assert.AreEqual(vis.Length, bsp.Leaves.Count);
for (var i = 1; i < 1000; i++)
{
var list = vis[i];
Assert.AreEqual(list.Length, bsp.Leaves.Count);
var visible = bsp.Visibility.GetVisibleLeaves(bsp.Leaves.Count, bsp.Leaves[i]).ToList();
for (var j = 1; j < bsp.Leaves.Count; j++)
{
Assert.AreEqual(list[j-1], visible.Contains(j), $"{i} x {j}");
}
}
}
}
35 changes: 35 additions & 0 deletions Sledge.Formats.Bsp/Lumps/Visibility.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Sledge.Formats.Bsp.Objects;

namespace Sledge.Formats.Bsp.Lumps
{
Expand Down Expand Up @@ -32,5 +36,36 @@ public int Write(BinaryWriter bw, Version version)
bw.Write(VisData);
return VisData.Length;
}

public bool IsVisible(Leaves lump, int self, int other)
{
var leaf = lump[self];
return GetVisibleLeaves(lump.Count, leaf).Contains(other);
}

public IEnumerable<int> GetVisibleLeaves(int numLeaves, Leaf leaf)
{
var offset = leaf.VisOffset;
for (var i = 1; i < numLeaves; offset++)
{
if (VisData[offset] == 0)
{
// not visible, next byte is the number of leaves to skip
offset++;
i += 8 * VisData[offset];
}
else
{
// at least 1 of the next 8 leaves is visible, look through each bit to find which one
for (byte bit = 1; bit != 0; bit <<= 1, i++)
{
if ((VisData[offset] & bit) != 0)
{
yield return i;
}
}
}
}
}
}
}

0 comments on commit 6a9e8c2

Please sign in to comment.