End of Phase O extraction. Final cleanup: - Dropped <ProjectReference> entries to WorldBuilder.Shared and Chorizite.OpenGLSDLBackend from both AcDream.App.csproj and AcDream.Core.csproj. - Added Chorizite.Core NuGet PackageReference to AcDream.Core.csproj (needed by Core.Rendering.Wb.TextureHelpers for TextureFormat enum; previously transitive through the WB project ref). - Added BCnEncoder.Net.ImageSharp (1.1.2) + SixLabors.ImageSharp (3.1.12) as direct PackageReferences to AcDream.App.csproj — previously transitive via Chorizite.OpenGLSDLBackend project; used directly by ObjectMeshManager. Item A (BaseObjectRenderManager static fields): - Inlined CurrentAtlas/CurrentVAO/CurrentIBO into a new RenderStateCache.cs static class (AcDream.App.Rendering.Wb namespace) — the 4 consumers (ManagedGLIndexBuffer, ManagedGLTexture, ManagedGLTextureArray, ParticleBatcher) all reference RenderStateCache.* instead of BaseObjectRenderManager.*. - Dropped using Chorizite.OpenGLSDLBackend.Lib from all 4 consumers and from WbDrawDispatcher (which had it only as a dead import). Item B (ActiveParticleEmitter.ObjectLandblock): - ObjectLandblock? erased to object?; WorldBuilder.Shared.Models.ObjectId? erased to ulong? — both fields are stored but never read by any consumer in our codebase. - Dropped both WB using directives from ActiveParticleEmitter.cs. Item C (IDatReaderWriter / IDatDatabase): - Verbatim copy of both interfaces into IDatReaderWriter.cs in AcDream.App.Rendering.Wb namespace — DatCollectionAdapter and ObjectMeshManager already live in that namespace, so no using changes needed. - Dropped using WorldBuilder.Shared.Services from DatCollectionAdapter.cs and ObjectMeshManager.cs. Additional extractions required by the reference drop: - GeometryUtils.cs: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils (float-precision overloads only; Vector3d double-precision overloads omitted — ObjectMeshManager uses only the float versions). - Dropped using WorldBuilder.Shared.Lib from ObjectMeshManager.cs. WbMeshAdapter.cs cleanup (spec O-D12): - Deleted _wbDats (DefaultDatReaderWriter) field + ctor init + Dispose call. - Deleted the [indoor-upload] NULL_RESULT diagnostic block (lines ~205-262) — its Phase 2 cell-resolution investigation is complete; its _wbDats.ResolveId dependency goes with this commit. - Deleted _pendingEnvCellRequests field + isPendingEnvCell tracking in Tick(). - Simplified Tick() to a clean drain loop. Deleted SplitFormulaDivergenceTest.cs — one-time N.5b data-collection sweep; job done. Verified acceptance criteria: - Zero <ProjectReference> to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* in any csproj. - Zero 'using WorldBuilder.*' / 'using Chorizite.OpenGLSDLBackend.*' in src/. - DefaultDatReaderWriter referenced in zero places in src/ (comments only). Build green (0 warnings, 0 errors). Tests: 1154 total (-1 from deleted SplitFormulaDivergenceTest), 1146 pass, 8 pre-existing failures (unchanged from baseline — physics/input tests unrelated to this change). Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
147 lines
5 KiB
C#
147 lines
5 KiB
C#
using System.Numerics;
|
|
|
|
// Phase O-T7: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils into
|
|
// the AcDream.App.Rendering.Wb namespace so the WorldBuilder.Shared project
|
|
// reference can be dropped. Only the float-precision overloads are used by
|
|
// ObjectMeshManager (RayIntersectsSphere + RayIntersectsTriangle). The
|
|
// double-precision overloads are kept verbatim for completeness.
|
|
|
|
namespace AcDream.App.Rendering.Wb;
|
|
|
|
public static class GeometryUtils {
|
|
|
|
public static bool RayIntersectsBox(Vector3 rayOrigin, Vector3 rayDirection, Vector3 min, Vector3 max, out float distance) {
|
|
distance = 0;
|
|
float tmin = 0.0f;
|
|
float tmax = float.MaxValue;
|
|
|
|
if (Math.Abs(rayDirection.X) < 1e-7f) {
|
|
if (rayOrigin.X < min.X || rayOrigin.X > max.X) return false;
|
|
}
|
|
else {
|
|
float invD = 1.0f / rayDirection.X;
|
|
float t0 = (min.X - rayOrigin.X) * invD;
|
|
float t1 = (max.X - rayOrigin.X) * invD;
|
|
if (t0 > t1) (t0, t1) = (t1, t0);
|
|
tmin = Math.Max(tmin, t0);
|
|
tmax = Math.Min(tmax, t1);
|
|
if (tmin > tmax) return false;
|
|
}
|
|
|
|
if (Math.Abs(rayDirection.Y) < 1e-7f) {
|
|
if (rayOrigin.Y < min.Y || rayOrigin.Y > max.Y) return false;
|
|
}
|
|
else {
|
|
float invD = 1.0f / rayDirection.Y;
|
|
float t0 = (min.Y - rayOrigin.Y) * invD;
|
|
float t1 = (max.Y - rayOrigin.Y) * invD;
|
|
if (t0 > t1) (t0, t1) = (t1, t0);
|
|
tmin = Math.Max(tmin, t0);
|
|
tmax = Math.Min(tmax, t1);
|
|
if (tmin > tmax) return false;
|
|
}
|
|
|
|
if (Math.Abs(rayDirection.Z) < 1e-7f) {
|
|
if (rayOrigin.Z < min.Z || rayOrigin.Z > max.Z) return false;
|
|
}
|
|
else {
|
|
float invD = 1.0f / rayDirection.Z;
|
|
float t0 = (min.Z - rayOrigin.Z) * invD;
|
|
float t1 = (max.Z - rayOrigin.Z) * invD;
|
|
if (t0 > t1) (t0, t1) = (t1, t0);
|
|
tmin = Math.Max(tmin, t0);
|
|
tmax = Math.Min(tmax, t1);
|
|
if (tmin > tmax) return false;
|
|
}
|
|
|
|
distance = tmin;
|
|
return true;
|
|
}
|
|
|
|
public static bool RayIntersectsTriangle(Vector3 origin, Vector3 direction, Vector3 v0, Vector3 v1, Vector3 v2, out float t) {
|
|
t = 0;
|
|
Vector3 edge1 = v1 - v0;
|
|
Vector3 edge2 = v2 - v0;
|
|
Vector3 h = Vector3.Cross(direction, edge2);
|
|
float a = Vector3.Dot(edge1, h);
|
|
|
|
if (a > -0.00001f && a < 0.00001f) return false;
|
|
|
|
float f = 1.0f / a;
|
|
Vector3 s = origin - v0;
|
|
float u = f * Vector3.Dot(s, h);
|
|
|
|
if (u < 0.0f || u > 1.0f) return false;
|
|
|
|
Vector3 q = Vector3.Cross(s, edge1);
|
|
float v = f * Vector3.Dot(direction, q);
|
|
|
|
if (v < 0.0f || u + v > 1.0f) return false;
|
|
|
|
t = f * Vector3.Dot(edge2, q);
|
|
return t > 0.00001f;
|
|
}
|
|
|
|
public static bool RayIntersectsSphere(Vector3 rayOrigin, Vector3 rayDirection, Vector3 sphereOrigin, float sphereRadius, out float distance) {
|
|
distance = 0;
|
|
Vector3 l = sphereOrigin - rayOrigin;
|
|
float tca = Vector3.Dot(l, rayDirection);
|
|
if (tca < 0) return false;
|
|
float d2 = Vector3.Dot(l, l) - tca * tca;
|
|
float r2 = sphereRadius * sphereRadius;
|
|
if (d2 > r2) return false;
|
|
float thc = MathF.Sqrt(r2 - d2);
|
|
distance = tca - thc;
|
|
return true;
|
|
}
|
|
|
|
public static ushort PackKey(int x, int y) => (ushort)((x << 8) | y);
|
|
|
|
/// <summary>
|
|
/// Converts a quaternion to Euler angles (in degrees) using the ZYX convention.
|
|
/// </summary>
|
|
public static Vector3 QuaternionToEuler(Quaternion q) {
|
|
float x = q.X, y = q.Y, z = q.Z, w = q.W;
|
|
float roll, pitch, yaw;
|
|
|
|
float sinr_cosp = 2 * (w * x + y * z);
|
|
float cosr_cosp = 1 - 2 * (x * x + y * y);
|
|
roll = (float)Math.Atan2(sinr_cosp, cosr_cosp);
|
|
|
|
float sinp = 2 * (w * y - z * x);
|
|
if (Math.Abs(sinp) >= 1)
|
|
pitch = (float)Math.CopySign(Math.PI / 2, sinp);
|
|
else
|
|
pitch = (float)Math.Asin(sinp);
|
|
|
|
float siny_cosp = 2 * (w * z + x * y);
|
|
float cosy_cosp = 1 - 2 * (y * y + z * z);
|
|
yaw = (float)Math.Atan2(siny_cosp, cosy_cosp);
|
|
|
|
return new Vector3(roll, pitch, yaw) * (180.0f / (float)Math.PI);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts Euler angles (in degrees) to a quaternion using the ZYX convention.
|
|
/// </summary>
|
|
public static Quaternion EulerToQuaternion(Vector3 euler) {
|
|
Vector3 rads = euler * (MathF.PI / 180.0f);
|
|
float roll = rads.X;
|
|
float pitch = rads.Y;
|
|
float yaw = rads.Z;
|
|
|
|
float cr = MathF.Cos(roll * 0.5f);
|
|
float sr = MathF.Sin(roll * 0.5f);
|
|
float cp = MathF.Cos(pitch * 0.5f);
|
|
float sp = MathF.Sin(pitch * 0.5f);
|
|
float cy = MathF.Cos(yaw * 0.5f);
|
|
float sy = MathF.Sin(yaw * 0.5f);
|
|
|
|
return new Quaternion(
|
|
sr * cp * cy - cr * sp * sy,
|
|
cr * sp * cy + sr * cp * sy,
|
|
cr * cp * sy - sr * sp * cy,
|
|
cr * cp * cy + sr * sp * sy
|
|
);
|
|
}
|
|
}
|