stardust_xr_fusion/
drawable.rs

1//! Anything the user can see such as lines, models and text.
2
3use std::hash::Hash;
4
5use crate::{
6	node::{NodeResult, NodeType},
7	spatial::{SpatialRefAspect, Transform},
8};
9use stardust_xr::values::*;
10
11pub use crate::protocol::drawable::*;
12
13impl Lines {
14	pub fn create(
15		spatial_parent: &impl SpatialRefAspect,
16		transform: Transform,
17		lines: &[Line],
18	) -> NodeResult<Self> {
19		let client = spatial_parent.client();
20		create_lines(
21			client,
22			client.generate_id(),
23			spatial_parent,
24			transform,
25			lines,
26		)
27	}
28}
29impl Default for LinePoint {
30	fn default() -> Self {
31		Self {
32			point: [0.0; 3].into(),
33			thickness: 0.01,
34			color: color::rgba_linear!(1.0, 1.0, 1.0, 1.0),
35		}
36	}
37}
38impl Copy for LinePoint {}
39impl Hash for LinePoint {
40	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
41		self.color.c.r.to_bits().hash(state);
42		self.color.c.g.to_bits().hash(state);
43		self.color.c.b.to_bits().hash(state);
44		self.color.a.to_bits().hash(state);
45
46		self.point.x.to_bits().hash(state);
47		self.point.y.to_bits().hash(state);
48		self.point.z.to_bits().hash(state);
49
50		self.thickness.to_bits().hash(state);
51	}
52}
53impl Default for Line {
54	fn default() -> Self {
55		Self {
56			points: Default::default(),
57			cyclic: Default::default(),
58		}
59	}
60}
61impl Hash for Line {
62	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
63		self.cyclic.hash(state);
64		self.points.hash(state);
65	}
66}
67
68impl Model {
69	pub fn create(
70		spatial_parent: &impl SpatialRefAspect,
71		transform: Transform,
72		model: &ResourceID,
73	) -> NodeResult<Self> {
74		let client = spatial_parent.client();
75		load_model(
76			client,
77			client.generate_id(),
78			spatial_parent,
79			transform,
80			model,
81		)
82	}
83	pub fn part(&self, relative_path: &str) -> NodeResult<ModelPart> {
84		let client = self.client();
85		self.bind_model_part(client.generate_id(), relative_path)
86	}
87}
88impl Text {
89	pub fn create(
90		spatial_parent: &impl SpatialRefAspect,
91		transform: Transform,
92		text: &str,
93		style: TextStyle,
94	) -> NodeResult<Self> {
95		let client = spatial_parent.client();
96		create_text(
97			client,
98			client.generate_id(),
99			spatial_parent,
100			transform,
101			text,
102			style,
103		)
104	}
105}
106impl Default for TextStyle {
107	fn default() -> Self {
108		Self {
109			character_height: 0.01,
110			color: color::rgba_linear!(1.0, 1.0, 1.0, 1.0),
111			font: Default::default(),
112			text_align_x: XAlign::Left,
113			text_align_y: YAlign::Top,
114			bounds: Default::default(),
115		}
116	}
117}
118impl Hash for TextBounds {
119	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
120		self.bounds.x.to_bits().hash(state);
121		self.bounds.y.to_bits().hash(state);
122
123		self.fit.hash(state);
124		self.anchor_align_x.hash(state);
125		self.anchor_align_y.hash(state);
126	}
127}
128impl Hash for TextStyle {
129	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
130		self.character_height.to_bits().hash(state);
131
132		self.color.c.r.to_bits().hash(state);
133		self.color.c.g.to_bits().hash(state);
134		self.color.c.b.to_bits().hash(state);
135		self.color.a.to_bits().hash(state);
136
137		self.font.hash(state);
138		self.text_align_x.hash(state);
139		self.text_align_y.hash(state);
140		self.bounds.hash(state);
141	}
142}
143
144#[tokio::test]
145async fn fusion_lines() {
146	let mut client = crate::Client::connect().await.unwrap();
147
148	let points = vec![
149		LinePoint {
150			point: Vector3 {
151				x: 1.0,
152				y: 0.0,
153				z: 0.0,
154			},
155			thickness: 0.0025,
156			..Default::default()
157		},
158		LinePoint {
159			thickness: 0.0025,
160			..Default::default()
161		},
162		LinePoint {
163			point: Vector3 {
164				x: 0.0,
165				y: 1.0,
166				z: 0.0,
167			},
168			thickness: 0.0025,
169			..Default::default()
170		},
171	];
172	let line = Line {
173		points,
174		cyclic: true,
175	};
176	let _lines = Lines::create(client.get_root(), Transform::none(), &[line]).unwrap();
177
178	client.flush().await.unwrap();
179	tokio::time::sleep(core::time::Duration::from_secs(60)).await;
180}
181
182#[tokio::test]
183async fn fusion_model() {
184	let mut client = crate::Client::connect().await.unwrap();
185
186	let gyro_resource = ResourceID::new_namespaced("fusion", "gyro");
187	let gyro_model = Model::create(client.get_root(), Transform::none(), &gyro_resource).unwrap();
188	gyro_model
189		.part("Gem")
190		.unwrap()
191		.set_material_parameter(
192			"color",
193			MaterialParameter::Color(color::rgba_linear!(0.0, 1.0, 0.5, 0.75)),
194		)
195		.unwrap();
196
197	let spike_resource = ResourceID::new_namespaced("fusion", "cursor_spike");
198	let spike_model = Model::create(
199		client.get_root(),
200		Transform::from_translation_scale([0.0, 0.1, 0.0], [0.1; 3]),
201		&spike_resource,
202	)
203	.unwrap();
204	spike_model
205		.part("Cone")
206		.unwrap()
207		.apply_holdout_material()
208		.unwrap();
209
210	client.flush().await.unwrap();
211	tokio::time::sleep(core::time::Duration::from_secs(60)).await;
212}
213#[tokio::test]
214async fn fusion_text() {
215	let mut client = crate::Client::connect().await.unwrap();
216
217	let style: TextStyle = TextStyle {
218		font: Some(stardust_xr::values::ResourceID::new_namespaced(
219			"fusion",
220			"common_case",
221		)),
222		..Default::default()
223	};
224	let text = Text::create(client.get_root(), Transform::none(), "Test Text", style).unwrap();
225	text.set_character_height(0.05).unwrap();
226	text.set_text("Test Text: Changed").unwrap();
227
228	client.flush().await.unwrap();
229	tokio::time::sleep(core::time::Duration::from_secs(60)).await;
230}
231
232#[tokio::test]
233async fn fusion_sky() {
234	let client = crate::Client::connect().await.expect("Couldn't connect");
235	client
236		.setup_resources(&[&crate::project_local_resources!("res")])
237		.unwrap();
238	let client_handle = client.handle();
239	let _event_loop = client.async_event_loop();
240	let sky_resource = stardust_xr::values::ResourceID::new_namespaced("fusion", "sky");
241
242	set_sky_light(&client_handle, Some(&sky_resource)).unwrap();
243	set_sky_tex(&client_handle, Some(&sky_resource)).unwrap();
244
245	tokio::time::sleep(core::time::Duration::from_secs(5)).await;
246
247	set_sky_light(&client_handle, None).unwrap();
248	set_sky_tex(&client_handle, None).unwrap();
249	tokio::time::sleep(core::time::Duration::from_secs(1)).await;
250}