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);
}