using DatReaderWriter.Enums; namespace AcDream.Core.Meshing; /// /// Retail's textured-vs-untextured surface classification, ported from /// D3DPolyRender::DrawMesh @0x0059d4a0 (named-retail decomp, /// docs/research/named-retail/acclient_2013_pseudo_c.txt ~line 426048): a /// surface subset is TEXTURED — and therefore never subject to either of /// retail's "skip untextured" gates (see /// ) — when /// (surface->type & 6) != 0, i.e. when /// (BASE1_IMAGE, 0x2) or /// (BASE1_CLIPMAP, 0x4) is set /// (docs/research/named-retail/acclient.h:5822-5824). Every other surface — /// including and any surface whose type /// carries neither bit — is UNTEXTURED: a flat-colour ("solid") subset filled /// from Surface.ColorValue rather than a decoded texture. /// /// /// #426 (2026-08-23, the Holtburg windmill axle 0x010010CE): the polygon-side /// StipplingType.NoPos flag ("this side has no texture coordinates", /// acclient.h:7386) is NOT the same fact as "this surface is untextured" — /// every solid-colour polygon carries NoPos (it has no UVs to carry), but /// NoPos says nothing about whether the surface itself is textured. The old /// extraction conflated the two (isSolid = NoPos || Base1Solid) and, /// worse, used NoPos to decide whether to emit the polygon's positive side AT /// ALL — dropping every solid-colour polygon on every object. This type is /// the ONE place that answers "is this surface textured", built from the /// Surface's own Type flags so extraction (isSolid / TextureKey.IsSolid) and /// draw-time skip policy agree by construction. /// public static class RetailUntexturedSurfacePolicy { public static bool IsUntextured(SurfaceType type) => (type & (SurfaceType.Base1Image | SurfaceType.Base1ClipMap)) == 0; } /// /// Retail's draw-time policy for an UNTEXTURED (solid-colour) mesh subset on /// an ordinary , ported from the /// same D3DPolyRender::DrawMesh untextured branch: the subset draws /// unless skipNoTexture != 0 && /// RenderDeviceD3D::ObjBuildingOrBuildingPart != 0. skipNoTexture /// @0x00820e30 is a global initialised to 1 and never cleared, so in /// practice the gate reduces to /// RenderDeviceD3D::ObjBuildingOrBuildingPart == 0. /// RenderDeviceD3D::DrawBuilding (@0x0059f2a0) sets that flag around /// the building-shell draw, so a building shell's own untextured subsets are /// the ONE case where an ordinary WorldEntity skips them — statics, scenery, /// creatures, and items (DrawMeshInternal @0x0059f360 → /// DrawMesh(gfxobj, mesh, arg4: 0)) always draw their untextured /// subsets. /// /// /// EnvCell interiors are retail's OTHER "skip untextured" case /// (RenderDeviceD3D::DrawEnvCell @0x0059f170 calls /// DrawMesh(..., arg4: 1)), but EnvCell/CellStruct geometry never /// reaches this predicate — it draws through EnvCellRenderer / /// MeshExtractor.PrepareCellStructMeshData, which keeps its own /// NoPos-based approximation of the same rule (see /// docs/architecture/retail-divergence-register.md AP-234 and /// CellMesh.cs's matching gate). /// /// ONE shared predicate for WbDrawDispatcher's classic classifier /// (ClassifyBatches), packed classifier (ClassifyPackedBatches), /// and the directional-shadow caster walk (AddDirectionalShadowBatches) /// — Campaign VM VM6 showed that two hand-maintained classifiers computing /// the "same" fact independently drift apart. /// /// public static class RetailUntexturedSubsetPolicy { public static bool Draws(bool isBuildingShell, bool isUntextured) => !isUntextured || !isBuildingShell; }