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.
114///
115/// Caller-owned output covers the finalized actor count and receives sorted, unique IDs.
116pub trait SpatialQuery: Debug + Send {
117    /// Finalized actor-slot capacity required by every caller-owned output slice.
118    fn actor_capacity(&self) -> usize;
119
120    /// Sorted, deduplicated broad-phase actor IDs on one spatial layer.
121    fn candidates_on_layer_into(&self, layer: SpatialLayerId, output: &mut [EnemyIdx]) -> usize;
122
123    fn candidates_in_envelope_into(
124        &self,
125        layer: SpatialLayerId,
126        envelope: SpatialEnvelope,
127        output: &mut [EnemyIdx],
128    ) -> usize;
129
130    fn actors_in_radius_into(
131        &self,
132        query: RadiusQuery,
133        allowed: Option<&[EnemyIdx]>,
134        output: &mut [EnemyIdx],
135    ) -> usize;
136
137    fn actors_in_cone_into(
138        &self,
139        query: ConeQuery,
140        allowed: Option<&[EnemyIdx]>,
141        output: &mut [EnemyIdx],
142    ) -> usize;
143
144    /// Active actors ordered by increasing distance, then ascending actor ID.
145    fn actors_by_distance_into(
146        &self,
147        layer: SpatialLayerId,
148        origin: Position2,
149        allowed: Option<&[EnemyIdx]>,
150        output: &mut [EnemyIdx],
151    ) -> usize;
152
153    /// Whether a same-layer closed segment is unobstructed by static geometry.
154    fn segment_visible(&self, layer: SpatialLayerId, start: Position2, end: Position2) -> bool;
155
156    /// Validates that a transform can be represented by this index.
157    /// # Errors
158    /// Returns an error for non-finite, blocked, or otherwise invalid transforms.
159    fn validate_transform(&self, transform: SpatialTransform) -> Result<(), SpatialQueryError>;
160
161    /// Adds an actor to the index.
162    /// # Errors
163    /// Returns an error when the actor already exists or its transform is invalid.
164    fn insert_actor(&mut self, actor: SpatialActor) -> Result<(), SpatialQueryError>;
165
166    /// Removes an actor from the index.
167    /// # Errors
168    /// Returns an error when the actor is absent or its transform does not match.
169    fn remove_actor(&mut self, actor: SpatialActor) -> Result<(), SpatialQueryError>;
170
171    /// Moves an indexed actor between validated transforms.
172    /// # Errors
173    /// Returns an error when the actor is absent, the source differs, or the destination is invalid.
174    fn relocate_actor(
175        &mut self,
176        actor: EnemyIdx,
177        from: SpatialTransform,
178        to: SpatialTransform,
179    ) -> Result<(), SpatialQueryError>;
180}