#pragma once #include #include #include #include #include #include #include #include #include namespace faset::render { struct Bounds { Vec3 min{}; Vec3 max{}; }; // Mesh bounds are computed from all vertices; world bounds transform all eight // corners of the local AABB, including rotation, reflection and nonuniform scale. // Empty or nonfinite geometry/matrices are rejected instead of being culled. Bounds local_bounds(const Mesh& mesh); Bounds transformed_bounds(const Mesh& mesh, const Mat4& model); struct InstanceUpdate { std::uint32_t slot{}; std::uint64_t generation{}; Mat4 previous_model{identity}; Bounds previous_bounds{}; bool previous_valid{}; }; // One update per logical instance per rendered frame. finish_frame() promotes the // current state to *rendered* history and retires keys absent from that frame. // Reordering the input never renumbers live instances. Mesh changes retain the // slot but advance its generation; slot reuse also advances its generation. class InstanceTracker { public: // Prefer this overload for engine-owned meshes. Retaining the owner prevents // allocator address reuse from impersonating the previously rendered mesh. InstanceUpdate update(std::string_view key, std::shared_ptr mesh_identity, const Mat4& model, const Bounds& world_bounds, std::string_view view_id); // Non-owning overload for meshes whose lifetime is guaranteed by the caller. InstanceUpdate update(std::string_view key, const Mesh* mesh_identity, const Mat4& model, const Bounds& world_bounds, std::string_view view_id); void finish_frame(); void invalidate_view(std::string_view view_id); private: struct Record { std::uint32_t slot{}; std::uint64_t generation{}; const Mesh* mesh{}; std::shared_ptr mesh_owner{}; Mat4 current_model{identity}, previous_model{identity}; Bounds current_bounds{}, previous_bounds{}; std::string current_view, previous_view; std::uint64_t seen_frame{}; bool committed{}; }; std::unordered_map records_; std::vector free_slots_; std::vector slot_generations_; std::unordered_set invalid_views_; std::uint64_t frame_{1}; }; // thresholds[i] is the projected-pixel boundary between levels i and i+1, // ordered strictly high-to-low. Moving toward a coarser level requires size < // threshold*(1-hysteresis); toward a finer level requires size > // threshold*(1+hysteresis). SIZE_MAX means no prior level and uses raw thresholds. // available is optional; when given it has thresholds.size()+1 entries, with 0 // marking missing prepared geometry. Nearest available wins; ties prefer finer. std::size_t select_lod(float projected_pixels, std::size_t previous_level, std::span thresholds, float hysteresis, std::span available = {}); } // namespace faset::render