using DatReaderWriter.Enums; namespace AcDream.Core.Meshing; /// /// Categorizes how a sub-mesh should be composited into the frame. Determined /// from the Surface.Type flags on the AC dat surface that owns the sub-mesh. /// public enum TranslucencyKind { /// Standard opaque. Depth write + test, no blend. Opaque = 0, /// /// Alpha-keyed (clip-map). Treated as opaque for sorting; the fragment /// shader discards low-alpha fragments. Matches the current rendering of /// doors, windows, and vegetation. /// ClipMap = 1, /// /// Standard alpha blend: src*a + dst*(1-a). /// Depth-write off, depth-test on. Used for semi-transparent glass, /// water decals, and flame alpha surfaces. /// AlphaBlend = 2, /// /// Additive blend: src*a + dst. Depth-write off, depth-test on. /// Used for portal swirls, magical glows, and particle effects. /// Additive = 3, /// /// Inverted alpha blend: src*(1-a) + dst*a. Rare but present in /// the AC dat files. /// InvAlpha = 4, } public static class TranslucencyKindExtensions { // Translucent override comes FIRST, then the existing priority chain: // 1. Translucent override — Translucent (0x10) AND (ClipMap OR opaque-base) // → AlphaBlend (matches retail's blend forcing). // 2. Additive — SurfaceType.Additive (0x10000) // 3. InvAlpha — SurfaceType.InvAlpha (0x200) // 4. AlphaBlend — SurfaceType.Alpha (0x100) OR SurfaceType.Translucent (0x10) // 5. ClipMap — SurfaceType.Base1ClipMap (0x04) // 6. Opaque — everything else // // The Translucent override matches retail's D3DPolyRender::SetSurface // at 0x0059c4d0 (decomp lines 425083-425260). Verbatim from the // Translucent branch at 425246: // // if ((curr_surface_type & 0x10) != 0) { // if (skipChk != 0 || ebx == 0 || arg3 == 1) { // edi_2 = BLEND_SRCALPHA; // src // ebp = BLEND_INVSRCALPHA; // dst ← alpha-blend // ebx = 1; arg1 = 1; arg3 = 0; // } // curr_alpha = _ftol2(translucency * 255); // } // // Where `arg3 = 1` after the ClipMap branch and `ebx == 0` happens // in Branch 2 when the surface would otherwise be opaque (no Additive, // Alpha, or InvAlpha bits). So Translucent + ClipMap (e.g. cloud // surface 0x08000023, Type=0x10114) renders ALPHA-BLEND in retail // even though the Additive flag is also set; previously acdream's // priority-Additive-first classification mis-routed it as additive. // Empirically: this is the surface for cloud GfxObj 0x01004C35 in // every Cloudy/Rainy DayGroup. Misclassifying it as additive made // acdream's clouds barely-visible "brightness adders" rather than // the dense alpha-blended sheets retail shows. /// /// Maps a flags value to the correct /// for the two-pass render split. /// public static TranslucencyKind FromSurfaceType(SurfaceType type) { // Step 1: Translucent override — matches retail's branch at // decomp line 425250 where (skipChk || ebx == 0 || arg3 == 1) // forces (SrcAlpha, InvSrcAlpha) regardless of Additive. bool isTranslucent = (type & SurfaceType.Translucent) != 0; bool isClipMap = (type & SurfaceType.Base1ClipMap) != 0; bool wouldBeOpaque = (type & (SurfaceType.Additive | SurfaceType.Alpha | SurfaceType.InvAlpha)) == 0; if (isTranslucent && (isClipMap || wouldBeOpaque)) return TranslucencyKind.AlphaBlend; // Step 2..6: existing priority order for non-overridden surfaces. if ((type & SurfaceType.Additive) != 0) return TranslucencyKind.Additive; if ((type & SurfaceType.InvAlpha) != 0) return TranslucencyKind.InvAlpha; if ((type & (SurfaceType.Alpha | SurfaceType.Translucent)) != 0) return TranslucencyKind.AlphaBlend; if ((type & SurfaceType.Base1ClipMap) != 0) return TranslucencyKind.ClipMap; return TranslucencyKind.Opaque; } /// /// Retail translucency is transparency: 0 = opaque, 1 = invisible. /// CMaterial::SetTranslucencySimple at 0x005396f0 writes material alpha /// as 1 - translucency. /// public static float OpacityFromSurfaceTranslucency(SurfaceType type, float translucency) { if ((type & SurfaceType.Translucent) == 0) return 1f; return Math.Clamp(1f - translucency, 0f, 1f); } /// /// D3DPolyRender::SetSurface at 0x0059c882 disables fixed-function fog /// alpha whenever the raw Additive surface bit is present, even when the /// Translucent+ClipMap branch later forces alpha blending. /// public static bool DisablesFixedFunctionFog(SurfaceType type) => (type & SurfaceType.Additive) != 0; }