// NdcScissorRect.cs // // NDC AABB → framebuffer-pixel scissor box, CONSERVATIVE (outer bound). // The scissor that brackets a landscape/doorway slice is a fallback BOUND on // the slice's view region (AD-17 in the divergence register): it must CONTAIN // every fragment the per-fragment plane clip would keep. Under-inclusion is // the bug class — the #130 doorway top-edge background strip was this box // computed as Floor(origin) + Ceiling(size), whose far edge // floor(min)+ceil(max−min) lands up to one pixel SHORT of the true max edge // at unlucky fractional alignments, scissoring away the aperture's top/right // pixel row for the whole slice (sky, terrain, statics, weather) while the // seal still stamps it — a strip of clear color no later pass can fill. // // Correct outer bound: floor both mins, ceil both maxes, width = difference. // A fragment at pixel (i,j) rasterizes iff its CENTER (i+0.5, j+0.5) lies in // the region ⊆ the NDC box [X0,X1]×[Y0,Y1] (pixel units). Center-inside ⇒ // i ≥ X0−0.5 ⇒ i ≥ floor(X0) and i ≤ X1−0.5 ⇒ i < ceil(X1). So // [floor(X0), ceil(X1)) admits every center-inside pixel, over-including by // at most one pixel per edge — safe per AD-17's doctrine (the wall shell / // plane clip repaints or kills the surplus). using System; using System.Numerics; namespace AcDream.App.Rendering; public static class NdcScissorRect { /// Convert an NDC AABB (minX, minY, maxX, maxY in [-1,1]) to a /// framebuffer-pixel scissor box that CONTAINS it. Inputs are clamped to /// the screen so a region extending past an edge still yields a valid box. /// Width/height are at least 1. public static (int X, int Y, int Width, int Height) ToPixels( Vector4 ndcAabb, int fbWidth, int fbHeight) { float nx0 = Math.Clamp(ndcAabb.X, -1f, 1f); float ny0 = Math.Clamp(ndcAabb.Y, -1f, 1f); float nx1 = Math.Clamp(ndcAabb.Z, -1f, 1f); float ny1 = Math.Clamp(ndcAabb.W, -1f, 1f); int px0 = (int)MathF.Floor((nx0 * 0.5f + 0.5f) * fbWidth); int py0 = (int)MathF.Floor((ny0 * 0.5f + 0.5f) * fbHeight); int px1 = (int)MathF.Ceiling((nx1 * 0.5f + 0.5f) * fbWidth); int py1 = (int)MathF.Ceiling((ny1 * 0.5f + 0.5f) * fbHeight); return (px0, py0, Math.Max(1, px1 - px0), Math.Max(1, py1 - py0)); } }