Skip to main content

sectorsync_core/
interest.rs

1//! Interest query primitives used by replication planning.
2
3use crate::entity::{EntityRecord, EntityTags};
4use crate::ids::ClientId;
5use crate::spatial::{Frustum3, Position3};
6
7/// Viewer-side interest query.
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct ViewerQuery {
10    /// Client requesting visible or interesting entities.
11    pub client_id: ClientId,
12    /// Viewer position.
13    pub position: Position3,
14    /// Primary spherical interest radius.
15    pub radius: f32,
16    /// Optional maximum number of selected entities.
17    pub max_entities: usize,
18}
19
20impl ViewerQuery {
21    /// Returns squared interest radius.
22    pub fn radius_squared(self) -> f32 {
23        self.radius * self.radius
24    }
25}
26
27/// Visibility hook. Embedders can provide frustum or occlusion-aware filters.
28pub trait VisibilityFilter {
29    /// Returns whether an entity is visible enough to be considered.
30    fn is_visible(&self, viewer: &ViewerQuery, entity: &EntityRecord) -> bool;
31}
32
33/// Range-only visibility filter.
34#[derive(Clone, Copy, Debug, Default)]
35pub struct RangeOnlyVisibility;
36
37impl VisibilityFilter for RangeOnlyVisibility {
38    fn is_visible(&self, viewer: &ViewerQuery, entity: &EntityRecord) -> bool {
39        entity.position.distance_squared(viewer.position) <= viewer.radius_squared()
40    }
41}
42
43/// Frustum visibility filter.
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct FrustumVisibility {
46    /// Six-plane visibility volume.
47    pub frustum: Frustum3,
48}
49
50impl FrustumVisibility {
51    /// Creates a frustum visibility filter.
52    pub const fn new(frustum: Frustum3) -> Self {
53        Self { frustum }
54    }
55}
56
57impl VisibilityFilter for FrustumVisibility {
58    fn is_visible(&self, _viewer: &ViewerQuery, entity: &EntityRecord) -> bool {
59        self.frustum
60            .intersects_bounds(entity.position, entity.bounds)
61    }
62}
63
64/// Tag visibility filter using business-defined entity tag bits.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct TagVisibility {
67    /// Tags that must all be present.
68    pub required: EntityTags,
69    /// Tags where any match rejects the entity.
70    pub excluded: EntityTags,
71}
72
73impl TagVisibility {
74    /// Creates a tag filter.
75    pub const fn new(required: EntityTags, excluded: EntityTags) -> Self {
76        Self { required, excluded }
77    }
78
79    /// Creates a tag filter requiring all bits in `required`.
80    pub const fn require(required: EntityTags) -> Self {
81        Self::new(required, EntityTags::EMPTY)
82    }
83
84    /// Creates a tag filter excluding any bit in `excluded`.
85    pub const fn exclude(excluded: EntityTags) -> Self {
86        Self::new(EntityTags::EMPTY, excluded)
87    }
88}
89
90impl VisibilityFilter for TagVisibility {
91    fn is_visible(&self, _viewer: &ViewerQuery, entity: &EntityRecord) -> bool {
92        entity.tags.contains(self.required) && !entity.tags.intersects(self.excluded)
93    }
94}
95
96/// Visibility filter that requires both child filters to pass.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub struct AndVisibility<A, B> {
99    /// First filter.
100    pub left: A,
101    /// Second filter.
102    pub right: B,
103}
104
105impl<A, B> AndVisibility<A, B> {
106    /// Creates a filter that accepts only entities accepted by both filters.
107    pub const fn new(left: A, right: B) -> Self {
108        Self { left, right }
109    }
110}
111
112impl<A, B> VisibilityFilter for AndVisibility<A, B>
113where
114    A: VisibilityFilter,
115    B: VisibilityFilter,
116{
117    fn is_visible(&self, viewer: &ViewerQuery, entity: &EntityRecord) -> bool {
118        self.left.is_visible(viewer, entity) && self.right.is_visible(viewer, entity)
119    }
120}