using AcDream.Core.Meshing; using AcDream.Core.World; namespace AcDream.App.Rendering.Wb; /// /// Campaign VM VM6: the two BatchData.flags bits that mark a world /// draw batch as swaying foliage — bit 0 remains #226's built-mesh marker /// (see mesh_detail.frag's vBatchFlags & 1u check) and is /// never touched here. mesh_atmospheric.vert and the four /// directional_shadow_world_*.vert caster shaders read these bits /// through the shared foliage_wind.glsl include /// (acdreamFoliageDisplace's batchFlags & 0x6u gate); the /// retail mesh_modern pipelines never read them, so setting them does /// not change pack-off output. /// internal static class FoliageWindClassification { /// /// Bit 1: the subset is an alpha-cutout/ClipMap material AND its owning /// entity is procedural scenery (see ). Leaves, /// fronds, bushes, grass tufts. /// internal const uint CutoutFoliageFlag = 0x2u; /// /// Bit 2: the subset is opaque AND its owning entity is procedural /// scenery AND that same entity owns at least one cutout subset — a /// tree trunk or branch, not a rock (rocks have no cutout subset). /// internal const uint TrunkFlag = 0x4u; /// /// Pure classification: no allocation, no scan. /// is expected to be computed once per GfxObj/Setup mesh and cached with /// the mesh record (), /// not recomputed per call. /// internal static uint Classify( uint entityId, bool isExcluded, TranslucencyKind translucency, bool meshHasCutoutSubset) { if (!IsProceduralScenery(entityId) || isExcluded) return 0u; if (translucency == TranslucencyKind.ClipMap) return CutoutFoliageFlag; if (translucency == TranslucencyKind.Opaque && meshHasCutoutSubset) return TrunkFlag; return 0u; } /// /// Campaign VM VM6 review fix round (A4): a Setup composite's parts are /// separate GfxObjs, each with its own independently-cached /// ObjectRenderData.HasCutoutSubset — an opaque trunk part carries /// no cutout batches of its own (the leaves are a DIFFERENT part), so /// classifying it from only its own HasCutoutSubset never gives /// it . Both the world-receiver and /// directional-shadow-caster Setup-part walks, and the packed production /// classifier, call this ONCE per entity per frame (not per batch) to OR /// every currently-resolved part's HasCutoutSubset into one /// entity-scoped value before classifying each part against it. Generic /// over the caller's own part representation so it needs no dependency /// on ObjectRenderData or the mesh adapter — the caller supplies /// to look each part's value up /// however it already does. Short-circuits on the first /// ; a part the caller cannot currently resolve /// simply contributes nothing (the caller marks the entity incomplete /// separately and reclassifies once every part loads). /// /// Campaign VM VM6 review fix round 2 (F3), round 3 (N5): takes /// the would-be-captured value (e.g. the mesh adapter) as an explicit /// argument to a /// lambda instead of letting a caller's lambda /// close over an instance field — a closure-capturing lambda would /// allocate a fresh closure object AND delegate on every call, and this /// runs once per Setup entity per frame from all three production /// classifiers. A lambda with no captures lets /// the C# compiler cache a single delegate instance for the method's /// lifetime instead. Round 3 deleted the single-generic predecessor of /// this overload — every call site (production and test) now goes /// through this one, so there is exactly one /// ComputeEntityHasCutoutSubset to keep correct. /// internal static bool ComputeEntityHasCutoutSubset( IReadOnlyList setupParts, TContext context, Func hasCutoutSubset) { for (int i = 0; i < setupParts.Count; i++) { if (hasCutoutSubset(context, setupParts[i])) return true; } return false; } /// /// The full top-nibble 0x8... test via /// — NOT bit 31 /// alone. Bit 31 alone also matches LandblockStaticEntityIdAllocator's /// 0xC... ids (fences, gates, building shells) and the synthetic /// render ids 0xDA11_D0xx / 0xFFFF_FF01 — using bit 31 /// alone here would sway non-scenery objects that happen to have a /// cutout subset. /// internal static bool IsProceduralScenery(uint entityId) => ProceduralSceneryIdAllocator.IsInNamespace(entityId); }