Five tasks shipped together (interdependent at build time): Task 1: WbRenderPass enum — verbatim port of WB RenderPass.cs:1-22 Task 2: WbFrustum + WbBoundingBox + FrustumTestResult — verbatim port of WB Frustum.cs (98 LOC) with namespace + BoundingBox-type adaptations. +7 unit tests. Task 3: EnvCellSceneryInstance + EnvCellLandblock — verbatim port of WB SceneryInstance.cs:1-161, renamed scope-narrow. Dropped editor-only fields (DisqualificationReason, ParticleEmitters, IsQueuedForUpload, InstanceBufferOffset, InstanceCount, MdiCommands, IsTransformOnlyUpdate) + InstanceId narrowed uint (we don't use ObjectId's editor methods). +5 unit tests. Task 4: EnvCellVisibilitySnapshot — direct port of WB VisibilitySnapshot narrowed to BatchedByCell + VisibleLandblocks only. Task 7: IndoorCellStencilPipeline.RenderBuildingStencilMask — new low-level WB-faithful entry mirroring PortalRenderManager:471-484. No surrounding GL state setup (caller's responsibility). Probe fields LastStencilVertexCount / LastStencilWasFarPunch / LastStencilBuildingId for the [stencil] probe emitter in Task 9. Build green, 18 tests pass (7 new Frustum + 5 new SceneryInstance + 6 existing stencil pipeline). Ready for Wave 2 (EnvCellRenderer port). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
143 lines
4.5 KiB
C#
143 lines
4.5 KiB
C#
// Ported from references/WorldBuilder/Chorizite.OpenGLSDLBackend/Frustum.cs
|
|
// Phase A8 extraction (2026-05-28). Verbatim algorithm; adaptations:
|
|
// - Namespace: AcDream.App.Rendering.Wb
|
|
// - Class renamed Frustum -> WbFrustum
|
|
// - BoundingBox -> WbBoundingBox (inlined below; no new project dep)
|
|
// - FrustumTestResult kept as-is (inlined below)
|
|
// - WbPlane inlined (was an inner type of Frustum.cs in WB)
|
|
|
|
using System.Numerics;
|
|
|
|
namespace AcDream.App.Rendering.Wb;
|
|
|
|
public struct WbBoundingBox
|
|
{
|
|
public Vector3 Min;
|
|
public Vector3 Max;
|
|
|
|
public WbBoundingBox(Vector3 min, Vector3 max)
|
|
{
|
|
Min = min;
|
|
Max = max;
|
|
}
|
|
|
|
public static WbBoundingBox Union(WbBoundingBox a, WbBoundingBox b) =>
|
|
new WbBoundingBox(
|
|
Vector3.Min(a.Min, b.Min),
|
|
Vector3.Max(a.Max, b.Max));
|
|
}
|
|
|
|
public enum FrustumTestResult
|
|
{
|
|
Outside,
|
|
Inside,
|
|
Intersecting
|
|
}
|
|
|
|
/// <summary>
|
|
/// View-frustum helper extracted from WorldBuilder's Frustum class.
|
|
/// Source: references/WorldBuilder/Chorizite.OpenGLSDLBackend/Frustum.cs
|
|
/// Phase A8 extraction (2026-05-28).
|
|
/// </summary>
|
|
public sealed class WbFrustum
|
|
{
|
|
private struct Plane
|
|
{
|
|
public Vector3 Normal;
|
|
public float D;
|
|
|
|
public Plane(float a, float b, float c, float d)
|
|
{
|
|
Normal = new Vector3(a, b, c);
|
|
float length = Normal.Length();
|
|
Normal /= length;
|
|
D = d / length;
|
|
}
|
|
|
|
public float Dot(Vector3 point) => Vector3.Dot(Normal, point) + D;
|
|
}
|
|
|
|
private readonly Plane[] _planes = new Plane[6];
|
|
private readonly object _lock = new();
|
|
|
|
public void Update(Matrix4x4 matrix)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
// Left plane
|
|
_planes[0] = new Plane(matrix.M14 + matrix.M11, matrix.M24 + matrix.M21, matrix.M34 + matrix.M31, matrix.M44 + matrix.M41);
|
|
// Right plane
|
|
_planes[1] = new Plane(matrix.M14 - matrix.M11, matrix.M24 - matrix.M21, matrix.M34 - matrix.M31, matrix.M44 - matrix.M41);
|
|
// Bottom plane
|
|
_planes[2] = new Plane(matrix.M14 + matrix.M12, matrix.M24 + matrix.M22, matrix.M34 + matrix.M32, matrix.M44 + matrix.M42);
|
|
// Top plane
|
|
_planes[3] = new Plane(matrix.M14 - matrix.M12, matrix.M24 - matrix.M22, matrix.M34 - matrix.M32, matrix.M44 - matrix.M42);
|
|
// Near plane
|
|
_planes[4] = new Plane(matrix.M14 + matrix.M13, matrix.M24 + matrix.M23, matrix.M34 + matrix.M33, matrix.M44 + matrix.M43);
|
|
// Far plane
|
|
_planes[5] = new Plane(matrix.M14 - matrix.M13, matrix.M24 - matrix.M23, matrix.M34 - matrix.M33, matrix.M44 - matrix.M43);
|
|
}
|
|
}
|
|
|
|
public bool Intersects(WbBoundingBox box, bool ignoreNearPlane = false)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
if (ignoreNearPlane && i == 4) continue;
|
|
|
|
Vector3 positive = box.Min;
|
|
if (_planes[i].Normal.X >= 0) positive.X = box.Max.X;
|
|
if (_planes[i].Normal.Y >= 0) positive.Y = box.Max.Y;
|
|
if (_planes[i].Normal.Z >= 0) positive.Z = box.Max.Z;
|
|
|
|
if (_planes[i].Dot(positive) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public FrustumTestResult TestBox(WbBoundingBox box, bool ignoreNearPlane = false)
|
|
{
|
|
var result = FrustumTestResult.Inside;
|
|
lock (_lock)
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
if (ignoreNearPlane && i == 4) continue;
|
|
|
|
Vector3 positive = box.Min;
|
|
Vector3 negative = box.Max;
|
|
if (_planes[i].Normal.X >= 0)
|
|
{
|
|
positive.X = box.Max.X;
|
|
negative.X = box.Min.X;
|
|
}
|
|
if (_planes[i].Normal.Y >= 0)
|
|
{
|
|
positive.Y = box.Max.Y;
|
|
negative.Y = box.Min.Y;
|
|
}
|
|
if (_planes[i].Normal.Z >= 0)
|
|
{
|
|
positive.Z = box.Max.Z;
|
|
negative.Z = box.Min.Z;
|
|
}
|
|
|
|
if (_planes[i].Dot(positive) < 0)
|
|
{
|
|
return FrustumTestResult.Outside;
|
|
}
|
|
if (_planes[i].Dot(negative) < 0)
|
|
{
|
|
result = FrustumTestResult.Intersecting;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|