Skip to main content

wowlab_engine_ports/
spatial.rs

1//! Narrow spatial-query boundary used by combat targeting.
2
3use std::fmt::Debug;
4
5use wowlab_types::sim::{EnemyIdx, Position2, SpatialLayerId, SpatialTransform};
6
7/// Axis-aligned broad-phase query envelope in world yards.
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct SpatialEnvelope {
10    pub min: Position2,
11    pub max: Position2,
12}
13
14/// Dynamic actor point consumed by an index implementation.
15#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct SpatialActor {
17    pub id: EnemyIdx,
18    pub transform: SpatialTransform,
19}
20
21/// Centered radius query on one spatial layer.
22#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct RadiusQuery {
24    pub layer: SpatialLayerId,
25    pub center: Position2,
26    pub radius: f64,
27}
28
29/// Directed cone query on one spatial layer.
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct ConeQuery {
32    pub layer: SpatialLayerId,
33    pub apex: Position2,
34    pub heading: f64,
35    pub half_angle: f64,
36    pub range: f64,
37}
38
39wowlab_engine_macros::define_error! {
40/// Typed spatial-index maintenance failure.
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct SpatialQueryError {
43    kind: SpatialQueryErrorKind,
44}
45
46#[derive(Clone, Copy, Debug, thiserror::Error, Eq, PartialEq)]
47enum SpatialQueryErrorKind {
48    #[error("actor {actor:?} is already present in the spatial index")]
49    DuplicateActor { actor: EnemyIdx },
50    #[error("actor {actor:?} is absent from the spatial index")]
51    MissingActor { actor: EnemyIdx },
52    #[error("actor {actor:?} spatial transform does not match the indexed transform")]
53    TransformMismatch { actor: EnemyIdx },
54    #[error("spatial transform is non-finite")]
55    NonFiniteTransform,
56    #[error("spatial transform is inside or on a static blocker")]
57    BlockedTransform,
58    #[error("spatial layer {layer:?} does not exist")]
59    MissingLayer { layer: SpatialLayerId },
60}
61}
62
63impl SpatialQueryError {
64    /// Reports an actor already present in the index.
65    #[must_use]
66    pub const fn duplicate_actor(actor: EnemyIdx) -> Self {
67        Self {
68            kind: SpatialQueryErrorKind::DuplicateActor { actor },
69        }
70    }
71
72    /// Reports an actor absent from the index.
73    #[must_use]
74    pub const fn missing_actor(actor: EnemyIdx) -> Self {
75        Self {
76            kind: SpatialQueryErrorKind::MissingActor { actor },
77        }
78    }
79
80    /// Reports a transform differing from the indexed value.
81    #[must_use]
82    pub const fn transform_mismatch(actor: EnemyIdx) -> Self {
83        Self {
84            kind: SpatialQueryErrorKind::TransformMismatch { actor },
85        }
86    }
87
88    /// Reports a non-finite transform.
89    #[must_use]
90    pub const fn non_finite_transform() -> Self {
91        Self {
92            kind: SpatialQueryErrorKind::NonFiniteTransform,
93        }
94    }
95
96    /// Reports a transform intersecting static geometry.
97    #[must_use]
98    pub const fn blocked_transform() -> Self {
99        Self {
100            kind: SpatialQueryErrorKind::BlockedTransform,
101        }
102    }
103
104    /// Reports a missing spatial layer.
105    #[must_use]
106    pub const fn missing_layer(layer: SpatialLayerId) -> Self {
107        Self {
108            kind: SpatialQueryErrorKind::MissingLayer { layer },
109        }
110    }
111}
112
113/// Deterministic spatial queries over active enemy points and immutable blockers; implementations return sorted, deduplicated IDs.
114pub trait SpatialQuery: Debug + Send {
115    /// Sorted, deduplicated broad-phase actor IDs on one spatial layer.
116    fn candidates_on_layer(&self, layer: SpatialLayerId) -> Vec<EnemyIdx>;
117
118    fn candidates_in_envelope(
119        &self,
120        layer: SpatialLayerId,
121        envelope: SpatialEnvelope,
122    ) -> Vec<EnemyIdx>;
123
124    fn actors_in_radius(&self, query: RadiusQuery, allowed: Option<&[EnemyIdx]>) -> Vec<EnemyIdx>;
125
126    fn actors_in_cone(&self, query: ConeQuery, allowed: Option<&[EnemyIdx]>) -> Vec<EnemyIdx>;
127
128    /// Active actors ordered by increasing distance, then ascending actor ID.
129    fn actors_by_distance(
130        &self,
131        layer: SpatialLayerId,
132        origin: Position2,
133        allowed: Option<&[EnemyIdx]>,
134    ) -> Vec<EnemyIdx>;
135
136    /// Return the nearest actor accepted by `is_eligible` without materializing the layer (increasing-distance then ascending actor-ID order).
137    fn nearest_actor_matching(
138        &self,
139        layer: SpatialLayerId,
140        origin: Position2,
141        is_eligible: &mut dyn FnMut(EnemyIdx) -> bool,
142    ) -> Option<EnemyIdx>;
143
144    /// Whether a same-layer closed segment is unobstructed by static geometry.
145    fn segment_visible(&self, layer: SpatialLayerId, start: Position2, end: Position2) -> bool;
146
147    /// Validates that a transform can be represented by this index.
148    /// # Errors
149    /// Returns an error for non-finite, blocked, or otherwise invalid transforms.
150    fn validate_transform(&self, transform: SpatialTransform) -> Result<(), SpatialQueryError>;
151
152    /// Adds an actor to the index.
153    /// # Errors
154    /// Returns an error when the actor already exists or its transform is invalid.
155    fn insert_actor(&mut self, actor: SpatialActor) -> Result<(), SpatialQueryError>;
156
157    /// Removes an actor from the index.
158    /// # Errors
159    /// Returns an error when the actor is absent or its transform does not match.
160    fn remove_actor(&mut self, actor: SpatialActor) -> Result<(), SpatialQueryError>;
161
162    /// Moves an indexed actor between validated transforms.
163    /// # Errors
164    /// Returns an error when the actor is absent, the source differs, or the destination is invalid.
165    fn relocate_actor(
166        &mut self,
167        actor: EnemyIdx,
168        from: SpatialTransform,
169        to: SpatialTransform,
170    ) -> Result<(), SpatialQueryError>;
171}