1#![cfg_attr(feature = "nightly", doc(cfg(feature = "toolbelt")))]
2use nsi_core as nsi;
10use ultraviolet as uv;
11#[doc(hidden)]
16#[cfg(debug_assertions)]
17pub fn generate_or_use_handle(
18 handle: Option<&str>,
19 prefix: Option<&str>,
20) -> String {
21 match handle {
22 Some(handle) => handle.to_string(),
23 None => {
24 if let Some(prefix) = prefix {
25 String::from(prefix) + "_" + &petname::petname(3, "_")
26 } else {
27 petname::petname(3, "_")
28 }
29 }
30 }
31}
32
33#[doc(hidden)]
34#[cfg(not(debug_assertions))]
35pub fn generate_or_use_handle(
36 handle: Option<&str>,
37 _prefix: Option<&str>,
38) -> String {
39 match handle {
40 Some(handle) => handle.to_string(),
41 None => {
42 use rand::{
43 distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng,
44 };
45 use std::iter;
46 let mut rng = SmallRng::from_entropy();
47
48 iter::repeat(())
49 .map(|()| rng.sample(Alphanumeric) as char)
50 .take(20)
51 .collect()
52 }
53 }
54}
55
56#[inline]
91pub fn append<'a, 'b, 'c>(
92 ctx: &'a nsi::Context,
93 to: &'b str,
94 slot: Option<&str>,
95 handle: &'c str,
96) -> (&'b str, &'c str)
97where
98 'a: 'b,
99 'a: 'c,
100{
101 ctx.connect(handle, None, to, slot.unwrap_or("objects"), None);
102
103 (to, handle)
104}
105
106#[inline]
139pub fn insert<'a, 'b, 'c>(
140 ctx: &'a nsi::Context,
141 to: &'b str,
142 to_slot: Option<&str>,
143 handle: &'c str,
144 handle_slot: Option<&str>,
145 from: &str,
146) -> (&'b str, &'c str)
147where
148 'a: 'b,
149 'a: 'c,
150{
151 append(ctx, handle, handle_slot, from);
152 append(ctx, to, to_slot, handle)
153}
154
155#[inline]
162pub fn node<'a>(
163 ctx: &nsi::Context<'a>,
164 handle: Option<&str>,
165 node_type: &str,
166 args: Option<&nsi::ArgSlice<'_, 'a>>,
167) -> String {
168 let handle = generate_or_use_handle(handle, Some(node_type));
169
170 ctx.create(handle.as_str(), node_type, None);
171
172 if let Some(args) = args {
173 ctx.set_attribute(handle.as_str(), args);
174 }
175
176 handle
177}
178
179#[inline]
185pub fn scaling(
186 ctx: &nsi::Context,
187 handle: Option<&str>,
188 scale: &[f64; 3],
189) -> String {
190 let handle = generate_or_use_handle(handle, Some("scaling"));
191 ctx.create(handle.as_str(), nsi::node::TRANSFORM, None);
192
193 ctx.set_attribute(
194 handle.as_str(),
195 &[nsi::double_matrix!(
196 "transformationmatrix",
197 uv::DMat4::from_nonuniform_scale(uv::DVec3::from(scale)).as_array()
198 )],
199 );
200
201 handle
202}
203
204#[inline]
210pub fn translation(
211 ctx: &nsi::Context,
212 handle: Option<&str>,
213 translate: &[f64; 3],
214) -> String {
215 let handle = generate_or_use_handle(handle, Some("translation"));
216 ctx.create(handle.as_str(), nsi::node::TRANSFORM, None);
217
218 ctx.set_attribute(
219 handle.as_str(),
220 &[nsi::double_matrix!(
221 "transformationmatrix",
222 uv::DMat4::from_translation(uv::DVec3::from(translate)).as_array()
223 )],
224 );
225
226 handle
227}
228
229pub fn rotation(
237 ctx: &nsi::Context,
238 handle: Option<&str>,
239 angle: f64,
240 axis: &[f64; 3],
241) -> String {
242 let handle = generate_or_use_handle(handle, Some("rotation"));
243 ctx.create(handle.as_str(), nsi::node::TRANSFORM, None);
244
245 ctx.set_attribute(
246 handle.as_str(),
247 &[nsi::double_matrix!(
248 "transformationmatrix",
249 uv::DMat4::from_angle_plane(
250 (angle * core::f64::consts::TAU / 90.0) as _,
251 uv::DBivec3::from_normalized_axis(
252 uv::DVec3::from(axis).normalized()
253 )
254 )
255 .transposed()
256 .as_array()
257 )],
258 );
259
260 handle
261}
262
263pub fn look_at_camera(
265 ctx: &nsi::Context,
266 handle: Option<&str>,
267 eye: &[f64; 3],
268 to: &[f64; 3],
269 up: &[f64; 3],
270) {
271 let handle = generate_or_use_handle(handle, Some("look_at"));
272 ctx.create(handle.as_str(), nsi::node::TRANSFORM, None);
273
274 ctx.set_attribute(
275 handle.as_str(),
276 &[nsi::double_matrix!(
277 "transformationmatrix",
278 uv::DMat4::look_at(
279 uv::DVec3::from(eye),
280 uv::DVec3::from(to),
281 uv::DVec3::from(up),
282 )
283 .inversed()
284 .as_array()
285 )],
286 );
287}
288
289pub fn look_at_bounding_box_perspective_camera(
300 ctx: &nsi::Context,
301 handle: Option<&str>,
302 direction: &[f64; 3],
303 up: &[f64; 3],
304 vertical_fov: f32,
305 aspect_ratio: Option<f32>,
306 bounding_box: &[f64; 6],
307) -> String {
308 let vertical_fov = if let Some(aspect_ratio) = aspect_ratio {
311 if aspect_ratio < 1.0 {
312 2.0 * (aspect_ratio
314 * (0.5 * vertical_fov * core::f32::consts::PI / 180.0).tan())
315 .atan()
316 } else {
317 vertical_fov * core::f32::consts::PI / 180.0
318 }
319 } else {
320 vertical_fov * core::f32::consts::PI / 180.0
321 } as f64;
322
323 let cube = [
327 uv::DVec3::new(bounding_box[0], bounding_box[1], bounding_box[2]),
328 uv::DVec3::new(bounding_box[0], bounding_box[4], bounding_box[2]),
329 uv::DVec3::new(bounding_box[0], bounding_box[1], bounding_box[5]),
330 uv::DVec3::new(bounding_box[3], bounding_box[4], bounding_box[5]),
331 uv::DVec3::new(bounding_box[3], bounding_box[1], bounding_box[5]),
332 uv::DVec3::new(bounding_box[3], bounding_box[4], bounding_box[2]),
333 ];
334
335 let bounding_box_center = 0.5 * (cube[0] + cube[3]);
336
337 let bounding_sphere_radius = cube
340 .iter()
341 .fold(0.0f64, |max, point| {
342 max.max((bounding_box_center - *point).mag_sq())
343 })
344 .sqrt();
345
346 let distance = bounding_sphere_radius / (vertical_fov * 0.5).sin();
347
348 let handle = generate_or_use_handle(handle, Some("look_at"));
351
352 ctx.create(handle.as_str(), nsi::node::TRANSFORM, None);
353
354 ctx.set_attribute(
355 handle.as_str(),
356 &[nsi::double_matrix!(
357 "transformationmatrix",
358 uv::DMat4::look_at(
359 bounding_box_center
360 - distance * uv::DVec3::from(direction).normalized(),
361 bounding_box_center,
362 uv::DVec3::from(up)
363 )
364 .inversed()
365 .as_array()
366 )],
367 );
368
369 handle
370}