1pub use crate::protocol::spatial::*;
18use crate::{client::ClientHandle, fields::FieldAspect, node::NodeResult};
19use stardust_xr::values::*;
20use std::{hash::Hash, sync::Arc};
21
22impl Transform {
23 pub const fn none() -> Self {
24 Transform {
25 translation: None,
26 rotation: None,
27 scale: None,
28 }
29 }
30 pub const fn identity() -> Self {
31 Transform {
32 translation: Some(Vector3 {
33 x: 0.0,
34 y: 0.0,
35 z: 0.0,
36 }),
37 rotation: Some(Quaternion {
38 v: Vector3 {
39 x: 0.0,
40 y: 0.0,
41 z: 0.0,
42 },
43 s: 1.0,
44 }),
45 scale: Some(Vector3 {
46 x: 1.0,
47 y: 1.0,
48 z: 1.0,
49 }),
50 }
51 }
52
53 pub fn from_translation(translation: impl Into<Vector3<f32>>) -> Self {
54 Transform {
55 translation: Some(translation.into()),
56 rotation: None,
57 scale: None,
58 }
59 }
60 pub fn from_rotation(rotation: impl Into<Quaternion>) -> Self {
61 Transform {
62 translation: None,
63 rotation: Some(rotation.into()),
64 scale: None,
65 }
66 }
67 pub fn from_scale(scale: impl Into<Vector3<f32>>) -> Self {
68 Transform {
69 translation: None,
70 rotation: None,
71 scale: Some(scale.into()),
72 }
73 }
74
75 pub fn from_translation_rotation(
76 translation: impl Into<Vector3<f32>>,
77 rotation: impl Into<Quaternion>,
78 ) -> Self {
79 Transform {
80 translation: Some(translation.into()),
81 rotation: Some(rotation.into()),
82 scale: None,
83 }
84 }
85 pub fn from_rotation_scale(
86 rotation: impl Into<Quaternion>,
87 scale: impl Into<Vector3<f32>>,
88 ) -> Self {
89 Transform {
90 translation: None,
91 rotation: Some(rotation.into()),
92 scale: Some(scale.into()),
93 }
94 }
95
96 pub fn from_translation_scale(
97 translation: impl Into<Vector3<f32>>,
98 scale: impl Into<Vector3<f32>>,
99 ) -> Self {
100 Transform {
101 translation: Some(translation.into()),
102 rotation: None,
103 scale: Some(scale.into()),
104 }
105 }
106
107 pub fn from_translation_rotation_scale(
108 translation: impl Into<Vector3<f32>>,
109 rotation: impl Into<Quaternion>,
110 scale: impl Into<Vector3<f32>>,
111 ) -> Self {
112 Transform {
113 translation: Some(translation.into()),
114 rotation: Some(rotation.into()),
115 scale: Some(scale.into()),
116 }
117 }
118}
119impl Copy for Transform {}
120impl Hash for Transform {
121 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
122 if let Some(translation) = &self.translation {
123 translation.x.to_bits().hash(state);
124 translation.y.to_bits().hash(state);
125 translation.z.to_bits().hash(state);
126 }
127 if let Some(rotation) = &self.rotation {
128 rotation.v.x.to_bits().hash(state);
129 rotation.v.y.to_bits().hash(state);
130 rotation.v.z.to_bits().hash(state);
131 rotation.s.to_bits().hash(state);
132 }
133 if let Some(scale) = &self.scale {
134 scale.x.to_bits().hash(state);
135 scale.y.to_bits().hash(state);
136 scale.z.to_bits().hash(state);
137 }
138 }
139}
140
141impl SpatialRef {
142 pub async fn import(client: &Arc<ClientHandle>, uid: u64) -> NodeResult<Self> {
143 import_spatial_ref(client, uid).await
144 }
145}
146
147impl Spatial {
148 pub fn create(
149 spatial_parent: &impl SpatialRefAspect,
150 transform: Transform,
151 zoneable: bool,
152 ) -> NodeResult<Self> {
153 let client = spatial_parent.client();
154 create_spatial(
155 client,
156 client.generate_id(),
157 spatial_parent,
158 transform,
159 zoneable,
160 )
161 }
162}
163
164impl Zone {
165 pub fn create(
166 spatial_parent: &impl SpatialRefAspect,
167 transform: Transform,
168 field: &impl FieldAspect,
169 ) -> NodeResult<Self> {
170 let client = spatial_parent.client();
171 create_zone(
172 client,
173 client.generate_id(),
174 spatial_parent,
175 transform,
176 field,
177 )
178 }
179}
180
181#[tokio::test]
184async fn fusion_spatial() {
185 use crate::Client;
186 let client = Client::connect().await.expect("Couldn't connect");
187 let spatial = Spatial::create(
188 client.get_root(),
189 Transform::from_translation_scale([1.0, 0.5, 0.1], [0.5, 0.5, 0.5]),
190 false,
191 )
192 .unwrap();
193 let bounding_box = spatial
194 .get_relative_bounding_box(client.get_root())
195 .await
196 .unwrap();
197 assert_eq!(bounding_box.center, [1.0, 0.5, 0.1].into());
198 assert_eq!(bounding_box.size, [0.0; 3].into());
199}
200
201#[tokio::test]
202async fn fusion_spatial_import_export() {
203 use crate::Client;
204 let client = Client::connect().await.expect("Couldn't connect");
205 let exported = Spatial::create(
206 client.get_root(),
207 Transform::from_translation_scale([1.0, 0.5, 0.1], [0.5, 0.5, 0.5]),
208 false,
209 )
210 .unwrap();
211 let uid = exported.export_spatial().await.unwrap();
212 let imported = SpatialRef::import(&client.handle(), uid).await.unwrap();
213 let relative_transform = imported.get_transform(&exported).await.unwrap();
214 assert_eq!(relative_transform, Transform::identity());
215}
216
217#[tokio::test]
218async fn fusion_zone() {
219 use crate::node::NodeType;
220 use crate::root::*;
221 let mut client = crate::Client::connect().await.expect("Couldn't connect");
222
223 let root = crate::spatial::Spatial::create(client.get_root(), Transform::none(), true).unwrap();
224
225 let gyro_gem = stardust_xr::values::ResourceID::new_namespaced("fusion", "gyro_gem");
226 let _model = crate::drawable::Model::create(&root, Transform::none(), &gyro_gem).unwrap();
227
228 let field = crate::fields::Field::create(
229 client.get_root(),
230 Transform::identity(),
231 crate::fields::Shape::Sphere(0.1),
232 )
233 .unwrap();
234
235 let mut zone_spatials: rustc_hash::FxHashMap<u64, SpatialRef> = Default::default();
236
237 let zone = Zone::create(client.get_root(), Transform::none(), &field).unwrap();
238
239 let event_loop = client.sync_event_loop(|client, stop| {
240 while let Some(event) = client.get_root().recv_root_event() {
241 match event {
242 RootEvent::Ping { response } => {
243 response.send_ok(());
244 }
245 RootEvent::Frame { info: _ } => zone.update().unwrap(),
246 RootEvent::SaveState { response } => response.send_ok(ClientState::default()),
247 }
248 }
249 while let Some(zone_event) = zone.recv_zone_event() {
250 match zone_event {
251 ZoneEvent::Enter { spatial } => {
252 println!("Spatial {spatial:?} entered zone");
253 zone.capture(&spatial).unwrap();
254 zone_spatials.insert(spatial.id(), spatial);
255 }
256 ZoneEvent::Capture { spatial } => {
257 println!("Spatial {spatial:?} was captured");
258 zone.release(&spatial).unwrap();
259 }
260 ZoneEvent::Release { id } => {
261 println!("Spatial {id} was released");
262 root.set_local_transform(Transform::from_translation([0.0, 1.0, 0.0]))
263 .unwrap();
264 zone.update().unwrap();
265 }
266 ZoneEvent::Leave { id } => {
267 println!("Spatial {id} left zone");
268 stop.stop();
269 }
270 }
271 }
272 });
273 tokio::time::timeout(std::time::Duration::from_secs(1), event_loop)
274 .await
275 .unwrap()
276 .unwrap();
277}