├── .gitignore ├── .travis.yml ├── LICENSE-APACHE ├── Cargo.toml ├── src ├── util.rs ├── stream │ ├── mod.rs │ ├── frame │ │ ├── mod.rs │ │ └── opt.rs │ └── raw.rs ├── lerp.rs ├── point.rs ├── lib.rs └── ffi.rs ├── LICENSE-MIT ├── README.md └── examples └── ffi.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - beta 5 | - stable 6 | script: 7 | - cargo build -v 8 | - cargo test -v 9 | - cargo doc -v 10 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2019 nannou-org 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nannou_laser" 3 | version = "0.3.0" 4 | authors = ["mitchmindtree "] 5 | description = "A cross-platform laser DAC detection and streaming API." 6 | edition = "2018" 7 | keywords = ["laser", "dac", "stream", "frame", "ether-dream"] 8 | license = "MIT OR Apache-2.0" 9 | repository = "https://github.com/nannou-org/nannou_laser.git" 10 | homepage = "https://github.com/nannou-org/nannou_laser" 11 | 12 | [lib] 13 | name = "nannou_laser" 14 | crate-type = ["rlib", "staticlib", "cdylib"] 15 | 16 | [dependencies] 17 | derive_more = "0.14" 18 | failure = "0.1" 19 | hashbrown = "0.2" 20 | ether-dream = "0.2" 21 | petgraph = "0.4" 22 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! A small suite of utility functions used throughout the crate. 2 | 3 | /// Clamp a value between some range. 4 | pub fn clamp(n: T, start: T, end: T) -> T 5 | where 6 | T: PartialOrd, 7 | { 8 | let (min, max) = if start <= end { 9 | (start, end) 10 | } else { 11 | (end, start) 12 | }; 13 | if n < min { 14 | min 15 | } else if n > max { 16 | max 17 | } else { 18 | n 19 | } 20 | } 21 | 22 | /// Map a value from a given range to a new given range. 23 | pub fn map_range(val: A, in_min: A, in_max: A, out_min: B, out_max: B) -> B 24 | where 25 | A: Into, 26 | B: Into + From, 27 | { 28 | let val_f: f64 = val.into(); 29 | let in_min_f: f64 = in_min.into(); 30 | let in_max_f: f64 = in_max.into(); 31 | let out_min_f: f64 = out_min.into(); 32 | let out_max_f: f64 = out_max.into(); 33 | ((val_f - in_min_f) / (in_max_f - in_min_f) * (out_max_f - out_min_f) + out_min_f).into() 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 nannou-org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/stream/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod frame; 2 | pub mod raw; 3 | 4 | /// The default rate at which the DAC should request points per second. 5 | pub const DEFAULT_POINT_HZ: u32 = 10_000; 6 | 7 | /// The default rate at which the DAC will yield frames of points. 8 | pub const DEFAULT_FRAME_HZ: u32 = 60; 9 | 10 | /// Builder parameters shared between the `raw` and `frame` signals. 11 | #[derive(Clone, Debug, Default)] 12 | pub struct Builder { 13 | /// The DAC with which the stream should be established. 14 | pub dac: Option, 15 | /// The initial rate at which the DAC should process points per second. 16 | /// 17 | /// By default this value is `stream::DEFAULT_POINT_HZ`. 18 | pub point_hz: Option, 19 | /// The maximum latency specified as a number of points. 20 | /// 21 | /// Each time the laser indicates its "fullness", the raw stream will request enough points 22 | /// from the render function to fill the DAC buffer up to `latency_points`. 23 | pub latency_points: Option, 24 | } 25 | 26 | /// Given a DAC point rate and a desired frame rate, determine how many points to generate per 27 | /// frame. 28 | pub fn points_per_frame(point_hz: u32, frame_hz: u32) -> u32 { 29 | point_hz / frame_hz 30 | } 31 | -------------------------------------------------------------------------------- /src/lerp.rs: -------------------------------------------------------------------------------- 1 | //! A basic implementation of linear interpolation for use with laser points. 2 | 3 | /// Types that can be linearly interpolated. 4 | pub trait Lerp { 5 | /// The type used to describe the amount of interpolation. 6 | type Scalar; 7 | /// Linearly interpolate from `self` to `dest` by the given `amt`. 8 | fn lerp(&self, dest: &Self, amt: Self::Scalar) -> Self; 9 | } 10 | 11 | impl Lerp for f32 { 12 | type Scalar = Self; 13 | fn lerp(&self, dest: &Self, amt: Self::Scalar) -> Self { 14 | *self + ((*dest - *self) * amt) 15 | } 16 | } 17 | 18 | impl Lerp for f64 { 19 | type Scalar = Self; 20 | fn lerp(&self, dest: &Self, amt: Self::Scalar) -> Self { 21 | *self + ((*dest - *self) * amt) 22 | } 23 | } 24 | 25 | // A macro for generating fixed-size array `Lerp` implementations. 26 | macro_rules! impl_lerp_for_array { 27 | ($($N:expr),*) => { 28 | $( 29 | impl Lerp for [T; $N] 30 | where 31 | T: Default + Lerp, 32 | T::Scalar: Clone, 33 | { 34 | type Scalar = T::Scalar; 35 | fn lerp(&self, dest: &Self, amt: Self::Scalar) -> Self { 36 | let mut arr: [T; $N] = Default::default(); 37 | for ((src, dst), out) in self.iter().zip(dest).zip(&mut arr) { 38 | *out = src.lerp(dst, amt.clone()); 39 | } 40 | arr 41 | } 42 | } 43 | )* 44 | }; 45 | } 46 | 47 | impl_lerp_for_array!( 48 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 49 | 26, 27, 28, 29, 30, 31, 32 50 | ); 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTICE: nannou_laser has been moved [here](https://github.com/nannou-org/nannou/tree/master/nannou_laser). 2 | 3 | --- 4 | 5 | # nannou_laser [![Build Status](https://travis-ci.org/nannou-org/nannou_laser.svg?branch=master)](https://travis-ci.org/nannou-org/nannou_laser) [![Crates.io](https://img.shields.io/crates/v/nannou_laser.svg)](https://crates.io/crates/nannou_laser) [![Crates.io](https://img.shields.io/crates/l/nannou_laser.svg)](https://github.com/nannou-org/nannou_laser/blob/master/LICENSE-MIT) [![docs.rs](https://docs.rs/nannou_laser/badge.svg)](https://docs.rs/nannou_laser/) 6 | 7 | A cross-platform laser DAC detection and streaming API. 8 | 9 | **nannou_laser** aims to be a higher-level API around a variety of laser protocols 10 | providing a unified interface for detecting DACs and streaming data to them. 11 | 12 | ## Features 13 | 14 | - **DAC Detection**: Detect all DACs available to the system. 15 | - **Specify maximum latency**: Choose how much latency you wish to allow for 16 | achieving the right balance between stream stability and low-latency to suit 17 | the DAC. 18 | - **Frame Streams**: Stream data to the DAC as a sequence of 2D vector images 19 | without worrying about details like path optimisation, etc. 20 | - **Raw Streams**: While frame streams are convenient, sometimes direct access 21 | to the lower-level raw DAC stream is required (e.g. when visualising a raw 22 | audio stream). This can be accessed via the **RawStream** API. 23 | - **Frame Optimisation**: **nannou_laser** implements the full suite of 24 | optimisations covered in *Accurate and Efficient Drawing Method for Laser 25 | Projection* by Purkhet Abderyim et al. These include Graph optimisation, draw 26 | order optimisation, blanking delays and sharp angle delays. See [the 27 | paper](https://art-science.org/journal/v7n4/v7n4pp155/artsci-v7n4pp155.pdf) 28 | for more details. 29 | - **Custom frame rate**: Choose the rate at which you wish to present frames. 30 | **nannou_laser** will determine the number of points used to draw each frame 31 | using the connected DAC's points-per-second. 32 | 33 | *Note: Higher level features like pattern generators and frame graphs are out of 34 | scope for nannou_laser, though could be built downstream. The priority for this 35 | crate is easy laser DAC detection and high-quality, high-performance data 36 | streams.* 37 | 38 | ## Supported Protocols 39 | 40 | Currently, **nannou_laser** only supports the open source [Ether Dream 41 | DAC](https://ether-dream.com/) protocol. The plan is to progressively add 42 | support for more protocols as they are needed by ourselves and users throughout 43 | the lifetime of the project. 44 | 45 | ## License 46 | 47 | Licensed under either of 48 | 49 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 50 | http://www.apache.org/licenses/LICENSE-2.0) 51 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 52 | http://opensource.org/licenses/MIT) 53 | 54 | at your option. 55 | 56 | **Contributions** 57 | 58 | Unless you explicitly state otherwise, any contribution intentionally submitted 59 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 60 | dual licensed as above, without any additional terms or conditions. 61 | -------------------------------------------------------------------------------- /src/point.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the general laser point type used throughout the crate. 2 | 3 | use crate::lerp::Lerp; 4 | 5 | /// A position in 2D space represented by x and y coordinates. 6 | pub type Position = [f32; 2]; 7 | 8 | /// Red, green and blue channels of a single colour. 9 | pub type Rgb = [f32; 3]; 10 | 11 | /// The point type used within the laser frame stream API. 12 | /// 13 | /// The point represents the location to which the scanner should point and the colour that the 14 | /// scanner should be at this point. 15 | /// 16 | /// If two consecutive points have two different colours, the `color` values will be linearly 17 | /// interpolated. 18 | #[repr(C)] 19 | #[derive(Copy, Clone, Debug, PartialEq)] 20 | pub struct Point { 21 | /// The position of the point. `-1` represents the minimum value along the axis and `1` 22 | /// represents the maximum. 23 | pub position: Position, 24 | /// The color of the point. 25 | pub color: Rgb, 26 | /// The minimum number of extra times this point should be drawn. 27 | /// 28 | /// `0` is the default used for drawing sequences of smooth line segments. 29 | /// 30 | /// Values greater than `0` are useful for accenting individual points. 31 | pub weight: u32, 32 | } 33 | 34 | /// The **Point** type used for describing raw laser streams. 35 | /// 36 | /// The point represents the location to which the scanner should point and the colour that the 37 | /// scanner should be at this point. 38 | /// 39 | /// If two consecutive points have two different colours, the `color` values will be linearly 40 | /// interpolated. 41 | #[repr(C)] 42 | #[derive(Copy, Clone, Debug, PartialEq)] 43 | pub struct RawPoint { 44 | /// The position of the point. `-1` represents the minimum value along the axis and `1` 45 | /// represents the maximum. 46 | pub position: Position, 47 | /// The color of the point. 48 | pub color: Rgb, 49 | } 50 | 51 | impl Point { 52 | /// The default weight for points used to draw lines. 53 | pub const DEFAULT_LINE_POINT_WEIGHT: u32 = 0; 54 | 55 | /// Create a **Point** at the given position with the given colour with a default weight. 56 | pub fn new(position: Position, color: Rgb) -> Self { 57 | Point::with_weight(position, color, Self::DEFAULT_LINE_POINT_WEIGHT) 58 | } 59 | 60 | /// The same as `Point::new` but allows for specifying the weight of the point. 61 | pub fn with_weight(position: Position, color: Rgb, weight: u32) -> Self { 62 | Point { position, color, weight } 63 | } 64 | 65 | /// Create a blank point at `[0, 0]`. 66 | pub fn centered_blank() -> Self { 67 | Point::new([0.0, 0.0], [0.0, 0.0, 0.0]) 68 | } 69 | 70 | /// Returns a point with the same position as `self` but with a black (blank) color. 71 | pub fn blanked(&self) -> Self { 72 | let mut blanked = *self; 73 | blanked.color = [0.0, 0.0, 0.0]; 74 | blanked 75 | } 76 | 77 | /// Whether or not the point is blank. 78 | /// 79 | /// A point is considered blank if the colour is black. 80 | pub fn is_blank(&self) -> bool { 81 | color_is_blank(self.color) 82 | } 83 | 84 | /// Converts to a single raw point with the same position and color. 85 | pub fn to_raw(&self) -> RawPoint { 86 | RawPoint::new(self.position, self.color) 87 | } 88 | 89 | /// Converts to `weight` number of raw points with the same position and color. 90 | pub fn to_raw_weighted(&self) -> impl Iterator { 91 | let Point { position, color, weight } = *self; 92 | (0..weight).map(move |_| RawPoint::new(position, color)) 93 | } 94 | } 95 | 96 | impl RawPoint { 97 | /// Create a **Point** at the given position with the given colour. 98 | pub fn new(position: Position, color: Rgb) -> Self { 99 | RawPoint { position, color } 100 | } 101 | 102 | /// Convert to a point compatible with a laser *frame* stream with the given weight. 103 | pub fn with_weight(&self, weight: u32) -> Point { 104 | Point::with_weight(self.position, self.color, weight) 105 | } 106 | 107 | /// Create a blank point at `[0, 0]`. 108 | pub fn centered_blank() -> Self { 109 | RawPoint::new([0.0, 0.0], [0.0, 0.0, 0.0]) 110 | } 111 | 112 | /// Returns a point with the same position as `self` but with a black (blank) color. 113 | pub fn blanked(&self) -> Self { 114 | let mut blanked = *self; 115 | blanked.color = [0.0, 0.0, 0.0]; 116 | blanked 117 | } 118 | 119 | /// Whether or not the point is blank. 120 | /// 121 | /// A point is considered blank if the colour is black. 122 | pub fn is_blank(&self) -> bool { 123 | color_is_blank(self.color) 124 | } 125 | } 126 | 127 | impl Lerp for RawPoint { 128 | type Scalar = f32; 129 | fn lerp(&self, other: &Self, amt: f32) -> Self { 130 | RawPoint::new(self.position.lerp(&other.position, amt), self.color.lerp(&other.color, amt)) 131 | } 132 | } 133 | 134 | /// Whether or not the given point is blank (black). 135 | pub fn color_is_blank([r, g, b]: Rgb) -> bool { 136 | r == 0.0 && g == 0.0 && b == 0.0 137 | } 138 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A cross-platform laser DAC detection and streaming API. 2 | 3 | pub extern crate ether_dream; 4 | 5 | pub mod ffi; 6 | pub mod lerp; 7 | pub mod point; 8 | pub mod stream; 9 | pub mod util; 10 | 11 | pub use lerp::Lerp; 12 | pub use point::{Point, RawPoint}; 13 | pub use stream::raw::Buffer; 14 | pub use stream::raw::Stream as RawStream; 15 | pub use stream::frame::Frame; 16 | pub use stream::frame::Stream as FrameStream; 17 | 18 | use std::io; 19 | use std::sync::Arc; 20 | 21 | /// A general API that allows for detecting and enumerating laser DACs on a network and 22 | /// establishing new streams of communication with them. 23 | pub struct Api { 24 | inner: Arc, 25 | } 26 | 27 | // The inner state of the `Api` that can be easily shared between laser threads in an `Arc`. 28 | // 29 | // This is useful for allowing streams to re-scan and find their associated DAC in the case it 30 | // drops out for some reason. 31 | pub(crate) struct Inner; 32 | 33 | /// An iterator yielding laser DACs available on the system as they are discovered. 34 | pub struct DetectDacs { 35 | dac_broadcasts: ether_dream::RecvDacBroadcasts, 36 | } 37 | 38 | /// An available DAC detected on the system. 39 | #[derive(Clone, Debug)] 40 | pub enum DetectedDac { 41 | /// An ether dream laser DAC discovered via the ether dream protocol broadcast message. 42 | EtherDream { 43 | broadcast: ether_dream::protocol::DacBroadcast, 44 | source_addr: std::net::SocketAddr, 45 | }, 46 | } 47 | 48 | /// A persistent, unique identifier associated with a DAC (like a MAC address). 49 | /// 50 | /// It should be possible to use this to uniquely identify the same DAC on different occasions. 51 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 52 | pub enum DacId { 53 | EtherDream { 54 | mac_address: [u8; 6], 55 | } 56 | } 57 | 58 | impl Api { 59 | /// Instantiate the laser API. 60 | pub fn new() -> Self { 61 | Api { inner: Arc::new(Inner) } 62 | } 63 | 64 | /// An iterator yielding laser DACs available on the system as they are discovered. 65 | /// 66 | /// Currently, the only laser protocol supported is the ether dream protocol. Thus, this 67 | /// enumerates ether dream DACs that are discovered on the LAN. 68 | /// 69 | /// **Note** that the produced iterator will iterate forever and never terminate, so you may 70 | /// only want to check a certain number of entries or run this iterator on some other thread. 71 | pub fn detect_dacs(&self) -> io::Result { 72 | self.inner.detect_dacs() 73 | } 74 | 75 | /// Block and wait until the DAC with the given `Id` is detected. 76 | pub fn detect_dac(&self, id: DacId) -> io::Result { 77 | self.inner.detect_dac(id) 78 | } 79 | 80 | /// Begin building a new laser frame stream. 81 | /// 82 | /// The stream will call the `render` function each time new points are needed to feed the 83 | /// laser DAC buffer. The rate at which this will be called depends on the `point_hz`, 84 | /// `frame_hz` and the `latency_points`. 85 | pub fn new_frame_stream(&self, model: M, render: F) -> stream::frame::Builder 86 | where 87 | F: stream::frame::RenderFn, 88 | { 89 | let api_inner = self.inner.clone(); 90 | let builder = Default::default(); 91 | let frame_hz = None; 92 | let interpolation_conf = Default::default(); 93 | let process_raw = stream::frame::default_process_raw_fn; 94 | stream::frame::Builder { 95 | api_inner, 96 | builder, 97 | model, 98 | render, 99 | process_raw, 100 | frame_hz, 101 | interpolation_conf, 102 | } 103 | } 104 | 105 | /// Begin building a new laser raw stream. 106 | /// 107 | /// The raw stream will call the given `render` function with a request for as many points as 108 | /// the DAC currently might need to fill the buffer based on the stream latency. 109 | pub fn new_raw_stream(&self, model: M, render: F) -> stream::raw::Builder 110 | where 111 | F: stream::raw::RenderFn, 112 | { 113 | let api_inner = self.inner.clone(); 114 | let builder = Default::default(); 115 | stream::raw::Builder { api_inner, builder, model, render } 116 | } 117 | } 118 | 119 | impl Inner { 120 | // See the `Api::detect_dacs` docs. 121 | pub(crate) fn detect_dacs(&self) -> io::Result { 122 | let dac_broadcasts = ether_dream::recv_dac_broadcasts()?; 123 | Ok(DetectDacs { dac_broadcasts }) 124 | } 125 | 126 | // Block and wait until the DAC with the given `Id` is detected. 127 | pub(crate) fn detect_dac(&self, id: DacId) -> io::Result { 128 | for res in self.detect_dacs()? { 129 | let dac = res?; 130 | if dac.id() == id { 131 | return Ok(dac); 132 | } 133 | } 134 | unreachable!("DAC detection iterator should never return `None`") 135 | } 136 | } 137 | 138 | impl DetectedDac { 139 | /// The maximum point rate allowed by the DAC. 140 | pub fn max_point_hz(&self) -> u32 { 141 | match self { 142 | DetectedDac::EtherDream { ref broadcast, .. } => broadcast.max_point_rate as _, 143 | } 144 | } 145 | 146 | /// The number of points that can be stored within the buffer. 147 | pub fn buffer_capacity(&self) -> u32 { 148 | match self { 149 | DetectedDac::EtherDream { ref broadcast, .. } => broadcast.buffer_capacity as _, 150 | } 151 | } 152 | 153 | /// A persistent, unique identifier associated with the DAC (like a MAC address). 154 | /// 155 | /// It should be possible to use this to uniquely identify the same DAC on different occasions. 156 | pub fn id(&self) -> DacId { 157 | match self { 158 | DetectedDac::EtherDream { ref broadcast, .. } => { 159 | DacId::EtherDream { 160 | mac_address: broadcast.mac_address, 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | impl AsRef for Point { 168 | fn as_ref(&self) -> &Point { 169 | self 170 | } 171 | } 172 | 173 | impl Iterator for DetectDacs { 174 | type Item = io::Result; 175 | fn next(&mut self) -> Option { 176 | let res = self.dac_broadcasts.next()?; 177 | match res { 178 | Err(err) => Some(Err(err)), 179 | Ok((broadcast, source_addr)) => { 180 | Some(Ok(DetectedDac::EtherDream { broadcast, source_addr })) 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /examples/ffi.rs: -------------------------------------------------------------------------------- 1 | //! An example of using `nannou_laser`'s `ffi`. 2 | //! 3 | //! While this example is in rust and not C, it should give an idea how to use the API via C. 4 | 5 | #[repr(C)] 6 | struct CallbackData { 7 | pattern: u32, 8 | } 9 | 10 | type Pattern = u32; 11 | const RECTANGLE: Pattern = 0; 12 | const TRIANGLE: Pattern = 1; 13 | const CROSSHAIR: Pattern = 2; 14 | const THREE_VERTICAL_LINES: Pattern = 3; 15 | const SPIRAL: Pattern = 4; 16 | const TOTAL_PATTERNS: u32 = SPIRAL + 1; 17 | 18 | fn main() { 19 | unsafe { 20 | // Create the API. 21 | println!("Initialising API..."); 22 | let mut api = std::ptr::null_mut(); 23 | nannou_laser::ffi::api_new(&mut api); 24 | 25 | // Detect DAC. 26 | println!("Detecting DAC..."); 27 | let mut dac = std::mem::MaybeUninit::::uninit(); 28 | let res = nannou_laser::ffi::detect_dac(api, dac.as_mut_ptr()); 29 | if res as u32 != 0 { 30 | eprintln!("failed to detect DAC"); 31 | nannou_laser::ffi::api_drop(api); 32 | return; 33 | } 34 | let dac = dac.assume_init(); 35 | println!("Found DAC!"); 36 | 37 | // Only ether dream supported presently. 38 | let ether_dream = dac.kind.ether_dream; 39 | println!("{:#?}", ether_dream); 40 | 41 | // Create a frame stream. 42 | let mut frame_stream_conf = std::mem::MaybeUninit::::uninit(); 43 | nannou_laser::ffi::frame_stream_config_default(frame_stream_conf.as_mut_ptr()); 44 | let frame_stream_conf = frame_stream_conf.assume_init(); 45 | let callback_data: *mut CallbackData = Box::into_raw(Box::new(CallbackData { pattern: RECTANGLE })); 46 | let mut frame_stream = std::ptr::null_mut(); 47 | println!("Spawning new frame stream..."); 48 | nannou_laser::ffi::new_frame_stream( 49 | api, 50 | &mut frame_stream, 51 | &frame_stream_conf, 52 | callback_data as *mut std::os::raw::c_void, 53 | frame_render_callback, 54 | process_raw_callback, 55 | ); 56 | 57 | // Run through each pattern for 1 second. 58 | for pattern in 0..TOTAL_PATTERNS { 59 | println!("drawing pattern {}", pattern); 60 | (*callback_data).pattern = pattern; 61 | std::thread::sleep(std::time::Duration::from_secs(1)); 62 | } 63 | 64 | // Drop frame stream to close it. 65 | println!("Dropping the frame stream"); 66 | nannou_laser::ffi::frame_stream_drop(frame_stream); 67 | std::mem::drop(Box::from_raw(callback_data)); 68 | 69 | // Create a raw stream. 70 | let mut stream_conf = std::mem::MaybeUninit::::uninit(); 71 | nannou_laser::ffi::stream_config_default(stream_conf.as_mut_ptr()); 72 | let stream_conf = stream_conf.assume_init(); 73 | let callback_data: *mut CallbackData = Box::into_raw(Box::new(CallbackData { pattern: RECTANGLE })); 74 | let mut raw_stream = std::ptr::null_mut(); 75 | println!("Spawning a raw stream..."); 76 | nannou_laser::ffi::new_raw_stream( 77 | api, 78 | &mut raw_stream, 79 | &stream_conf, 80 | callback_data as *mut std::os::raw::c_void, 81 | raw_render_callback, 82 | ); 83 | 84 | std::thread::sleep(std::time::Duration::from_secs(1)); 85 | 86 | // Drop the raw stream to close it. 87 | println!("Dropping the raw stream"); 88 | nannou_laser::ffi::raw_stream_drop(raw_stream); 89 | std::mem::drop(Box::from_raw(callback_data)); 90 | 91 | // Release the handle to the API when we're done. 92 | println!("Cleaning up..."); 93 | nannou_laser::ffi::api_drop(api); 94 | 95 | println!("Done!"); 96 | } 97 | } 98 | 99 | // Called when the stream is ready for a new frame of data. 100 | extern fn frame_render_callback( 101 | data: *mut std::os::raw::c_void, 102 | frame: *mut nannou_laser::ffi::Frame, 103 | ) { 104 | unsafe { 105 | let data_ptr = data as *mut CallbackData; 106 | let data = &mut *data_ptr; 107 | let frame = &mut *frame; 108 | write_laser_frame_points(data, frame); 109 | } 110 | } 111 | 112 | fn write_laser_frame_points(data: &mut CallbackData, frame: &mut nannou_laser::ffi::Frame) { 113 | // Simple constructors for white or blank points. 114 | let lit_p = |position| nannou_laser::Point::new(position, [1.0; 3]); 115 | 116 | // Draw the frame with the selected pattern. 117 | match data.pattern { 118 | RECTANGLE => { 119 | let tl = [-1.0, 1.0]; 120 | let tr = [1.0, 1.0]; 121 | let br = [1.0, -1.0]; 122 | let bl = [-1.0, -1.0]; 123 | let positions = [tl, tr, br, bl, tl]; 124 | let points: Vec<_> = positions.iter().cloned().map(lit_p).collect(); 125 | unsafe { 126 | nannou_laser::ffi::frame_add_lines(frame, points.as_ptr(), points.len()); 127 | } 128 | } 129 | 130 | TRIANGLE => { 131 | let a = [-0.75, -0.75]; 132 | let b = [0.0, 0.75]; 133 | let c = [0.75, -0.75]; 134 | let positions = [a, b, c, a]; 135 | let points: Vec<_> = positions.iter().cloned().map(lit_p).collect(); 136 | unsafe { 137 | nannou_laser::ffi::frame_add_lines(frame, points.as_ptr(), points.len()); 138 | } 139 | } 140 | 141 | CROSSHAIR => { 142 | let xa = [-1.0, 0.0]; 143 | let xb = [1.0, 0.0]; 144 | let ya = [0.0, -1.0]; 145 | let yb = [0.0, 1.0]; 146 | let x = [lit_p(xa), lit_p(xb)]; 147 | let y = [lit_p(ya), lit_p(yb)]; 148 | unsafe { 149 | nannou_laser::ffi::frame_add_lines(frame, x.as_ptr(), x.len()); 150 | nannou_laser::ffi::frame_add_lines(frame, y.as_ptr(), y.len()); 151 | } 152 | } 153 | 154 | THREE_VERTICAL_LINES => { 155 | let la = [-1.0, -0.5]; 156 | let lb = [-1.0, 0.5]; 157 | let ma = [0.0, 0.5]; 158 | let mb = [0.0, -0.5]; 159 | let ra = [1.0, -0.5]; 160 | let rb = [1.0, 0.5]; 161 | let l = [lit_p(la), lit_p(lb)]; 162 | let m = [lit_p(ma), lit_p(mb)]; 163 | let r = [lit_p(ra), lit_p(rb)]; 164 | unsafe { 165 | nannou_laser::ffi::frame_add_lines(frame, l.as_ptr(), l.len()); 166 | nannou_laser::ffi::frame_add_lines(frame, m.as_ptr(), m.len()); 167 | nannou_laser::ffi::frame_add_lines(frame, r.as_ptr(), r.len()); 168 | } 169 | } 170 | 171 | SPIRAL => { 172 | let n_points = unsafe { 173 | nannou_laser::ffi::points_per_frame(frame) as usize / 2 174 | }; 175 | let radius = 1.0; 176 | let rings = 5.0; 177 | let points = (0..n_points) 178 | .map(|i| { 179 | let fract = i as f32 / n_points as f32; 180 | let mag = fract * radius; 181 | let phase = rings * fract * 2.0 * std::f32::consts::PI; 182 | let y = mag * -phase.sin(); 183 | let x = mag * phase.cos(); 184 | [x, y] 185 | }) 186 | .map(lit_p) 187 | .collect::>(); 188 | unsafe { 189 | nannou_laser::ffi::frame_add_lines(frame, points.as_ptr(), points.len()); 190 | } 191 | } 192 | 193 | _ => unreachable!(), 194 | } 195 | } 196 | 197 | // Called when the stream is ready for data. This is called after the `frame_render_callback` and 198 | // after all path optimisations have been applied. This is useful as a kind of post-processing 199 | // function, for applying safety zones, etc. 200 | extern fn process_raw_callback( 201 | _data: *mut std::os::raw::c_void, 202 | _buffer: *mut nannou_laser::ffi::Buffer, 203 | ) { 204 | } 205 | 206 | // Called when then 207 | extern fn raw_render_callback( 208 | _data: *mut std::os::raw::c_void, 209 | _buffer: *mut nannou_laser::ffi::Buffer, 210 | ) { 211 | } 212 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | //! Expose a C compatible interface. 2 | 3 | use std::ffi::{CStr, CString}; 4 | use std::os::raw; 5 | 6 | #[repr(C)] 7 | pub struct Api { 8 | inner: crate::Api, 9 | last_error: Option, 10 | } 11 | 12 | #[repr(C)] 13 | #[derive(Clone, Copy)] 14 | pub struct DetectedDac { 15 | pub kind: DetectedDacKind, 16 | } 17 | 18 | #[repr(C)] 19 | #[derive(Clone, Copy)] 20 | pub union DetectedDacKind { 21 | pub ether_dream: DacEtherDream, 22 | } 23 | 24 | #[repr(C)] 25 | #[derive(Clone, Copy, Debug)] 26 | pub struct DacEtherDream { 27 | pub broadcast: ether_dream::protocol::DacBroadcast, 28 | pub source_addr: *const raw::c_char, 29 | } 30 | 31 | #[repr(C)] 32 | #[derive(Clone, Copy, Debug)] 33 | pub struct StreamConfig { 34 | pub detected_dac: *const DetectedDac, 35 | pub point_hz: raw::c_uint, 36 | pub latency_points: raw::c_uint, 37 | } 38 | 39 | #[repr(C)] 40 | pub struct FrameStream { 41 | stream: FrameStreamInner, 42 | } 43 | 44 | #[repr(C)] 45 | pub struct RawStream { 46 | stream: RawStreamInner, 47 | } 48 | 49 | #[repr(C)] 50 | #[derive(Clone, Copy)] 51 | pub enum Result { 52 | Success = 0, 53 | FailedToDetectDac, 54 | FailedToBuildStream, 55 | } 56 | 57 | #[repr(C)] 58 | #[derive(Clone, Debug)] 59 | pub struct FrameStreamConfig { 60 | pub stream_conf: StreamConfig, 61 | pub frame_hz: u32, 62 | pub interpolation_conf: crate::stream::frame::opt::InterpolationConfig, 63 | } 64 | 65 | #[repr(C)] 66 | pub struct Frame<'a> { 67 | frame: &'a mut crate::stream::frame::Frame, 68 | } 69 | 70 | #[repr(C)] 71 | pub struct Buffer<'a> { 72 | buffer: &'a mut crate::stream::raw::Buffer, 73 | } 74 | 75 | struct FrameStreamModel(*mut raw::c_void, FrameRenderCallback, RawRenderCallback); 76 | struct RawStreamModel(*mut raw::c_void, RawRenderCallback); 77 | 78 | unsafe impl Send for FrameStreamModel {} 79 | unsafe impl Send for RawStreamModel {} 80 | 81 | type FrameStreamInner = crate::FrameStream; 82 | type RawStreamInner = crate::RawStream; 83 | 84 | /// Cast to `extern fn(*mut raw::c_void, *mut Frame)` internally. 85 | //pub type FrameRenderCallback = *const raw::c_void; 86 | pub type FrameRenderCallback = extern fn(*mut raw::c_void, *mut Frame); 87 | /// Cast to `extern fn(*mut raw::c_void, *mut Buffer)` internally. 88 | //pub type RawRenderCallback = *const raw::c_void; 89 | pub type RawRenderCallback = extern fn(*mut raw::c_void, *mut Buffer); 90 | 91 | /// Given some uninitialized pointer to an `Api` struct, fill it with a new Api instance. 92 | #[no_mangle] 93 | pub unsafe extern fn api_new(api_ptr: *mut *mut Api) { 94 | let inner = crate::Api::new(); 95 | let last_error = None; 96 | let api = Api { inner, last_error }; 97 | let boxed_api = Box::new(api); 98 | let new_api_ptr = Box::into_raw(boxed_api); 99 | *api_ptr = new_api_ptr; 100 | } 101 | 102 | /// Block the current thread until a new DAC is detected and return it. 103 | #[no_mangle] 104 | pub unsafe extern fn detect_dac(api: *const Api, detected_dac: *mut DetectedDac) -> Result { 105 | let api: &Api = &*api; 106 | let mut iter = match api.inner.detect_dacs() { 107 | Err(err) => { 108 | unimplemented!() 109 | }, 110 | Ok(iter) => iter, 111 | }; 112 | match iter.next() { 113 | None => return Result::FailedToDetectDac, 114 | Some(res) => { 115 | match res { 116 | Ok(crate::DetectedDac::EtherDream { broadcast, source_addr }) => { 117 | let string = format!("{}", source_addr); 118 | let bytes = string.into_bytes(); 119 | let source_addr = bytes.as_ptr() as *const raw::c_char; 120 | std::mem::forget(bytes); 121 | let ether_dream = DacEtherDream { broadcast, source_addr }; 122 | let kind = DetectedDacKind { ether_dream }; 123 | *detected_dac = DetectedDac { kind }; 124 | return Result::Success; 125 | } 126 | Err(err) => { 127 | unimplemented!() 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | fn default_stream_config() -> StreamConfig { 135 | let detected_dac = std::ptr::null(); 136 | let point_hz = crate::stream::DEFAULT_POINT_HZ; 137 | let latency_points = crate::stream::raw::default_latency_points(point_hz); 138 | StreamConfig { 139 | detected_dac, 140 | point_hz, 141 | latency_points, 142 | } 143 | } 144 | 145 | /// Initialise the given frame stream configuration with default values. 146 | #[no_mangle] 147 | pub unsafe extern fn frame_stream_config_default(conf: *mut FrameStreamConfig) { 148 | let stream_conf = default_stream_config(); 149 | let frame_hz = crate::stream::DEFAULT_FRAME_HZ; 150 | let interpolation_conf = crate::stream::frame::opt::InterpolationConfig::start().build(); 151 | *conf = FrameStreamConfig { 152 | stream_conf, 153 | frame_hz, 154 | interpolation_conf, 155 | }; 156 | } 157 | 158 | /// Initialise the given raw stream configuration with default values. 159 | #[no_mangle] 160 | pub unsafe extern fn stream_config_default(conf: *mut StreamConfig) { 161 | *conf = default_stream_config(); 162 | } 163 | 164 | /// Spawn a new frame rendering stream. 165 | #[no_mangle] 166 | pub unsafe extern fn new_frame_stream( 167 | api: *const Api, 168 | stream: *mut *mut FrameStream, 169 | config: *const FrameStreamConfig, 170 | callback_data: *mut raw::c_void, 171 | frame_render_callback: FrameRenderCallback, 172 | process_raw_callback: RawRenderCallback, 173 | ) -> Result { 174 | let api: &Api = &*api; 175 | let model = FrameStreamModel(callback_data, frame_render_callback, process_raw_callback); 176 | 177 | fn render_fn(model: &mut FrameStreamModel, frame: &mut crate::stream::frame::Frame) { 178 | let FrameStreamModel(callback_data_ptr, frame_render_callback, _) = *model; 179 | let mut frame = Frame { frame }; 180 | frame_render_callback(callback_data_ptr, &mut frame); 181 | } 182 | 183 | let mut builder = api.inner.new_frame_stream(model, render_fn) 184 | .point_hz((*config).stream_conf.point_hz as _) 185 | .latency_points((*config).stream_conf.latency_points as _) 186 | .frame_hz((*config).frame_hz as _); 187 | 188 | fn process_raw_fn(model: &mut FrameStreamModel, buffer: &mut crate::stream::raw::Buffer) { 189 | let FrameStreamModel(callback_data_ptr, _, process_raw_callback) = *model; 190 | let mut buffer = Buffer { buffer }; 191 | process_raw_callback(callback_data_ptr, &mut buffer); 192 | } 193 | 194 | builder = builder.process_raw(process_raw_fn); 195 | 196 | if (*config).stream_conf.detected_dac != std::ptr::null() { 197 | let ffi_dac = (*(*config).stream_conf.detected_dac).clone(); 198 | let broadcast = ffi_dac.kind.ether_dream.broadcast.clone(); 199 | let source_addr_ptr = ffi_dac.kind.ether_dream.source_addr; 200 | let source_addr = CStr::from_ptr(source_addr_ptr) 201 | .to_string_lossy() 202 | .parse() 203 | .expect("failed to parse `source_addr`"); 204 | let detected_dac = crate::DetectedDac::EtherDream { broadcast, source_addr }; 205 | builder = builder.detected_dac(detected_dac); 206 | } 207 | 208 | let frame_stream = match builder.build() { 209 | Err(err) => { 210 | unimplemented!(); 211 | return Result::FailedToBuildStream; 212 | }, 213 | Ok(stream) => Box::new(FrameStream { stream }), 214 | }; 215 | *stream = Box::into_raw(frame_stream); 216 | Result::Success 217 | } 218 | 219 | /// Spawn a new frame rendering stream. 220 | #[no_mangle] 221 | pub unsafe extern fn new_raw_stream( 222 | api: *const Api, 223 | stream: *mut *mut RawStream, 224 | config: *const StreamConfig, 225 | callback_data: *mut raw::c_void, 226 | process_raw_callback: RawRenderCallback, 227 | ) -> Result { 228 | let api: &Api = &*api; 229 | let model = RawStreamModel(callback_data, process_raw_callback); 230 | 231 | fn render_fn(model: &mut RawStreamModel, buffer: &mut crate::stream::raw::Buffer) { 232 | let RawStreamModel(callback_data_ptr, raw_render_callback) = *model; 233 | let mut buffer = Buffer { buffer }; 234 | raw_render_callback(callback_data_ptr, &mut buffer); 235 | } 236 | 237 | let mut builder = api.inner.new_raw_stream(model, render_fn) 238 | .point_hz((*config).point_hz as _) 239 | .latency_points((*config).latency_points as _); 240 | 241 | if (*config).detected_dac != std::ptr::null() { 242 | let ffi_dac = (*(*config).detected_dac).clone(); 243 | let broadcast = ffi_dac.kind.ether_dream.broadcast.clone(); 244 | let source_addr_ptr = ffi_dac.kind.ether_dream.source_addr; 245 | let source_addr = CStr::from_ptr(source_addr_ptr) 246 | .to_string_lossy() 247 | .parse() 248 | .expect("failed to parse `source_addr`"); 249 | let detected_dac = crate::DetectedDac::EtherDream { broadcast, source_addr }; 250 | builder = builder.detected_dac(detected_dac); 251 | } 252 | 253 | let raw_stream = match builder.build() { 254 | Err(err) => { 255 | unimplemented!(); 256 | return Result::FailedToBuildStream; 257 | }, 258 | Ok(stream) => Box::new(RawStream { stream }), 259 | }; 260 | *stream = Box::into_raw(raw_stream); 261 | 262 | Result::Success 263 | } 264 | 265 | /// Add a sequence of consecutive points separated by blank space. 266 | /// 267 | /// If some points already exist in the frame, this method will create a blank segment between the 268 | /// previous point and the first point before appending this sequence. 269 | #[no_mangle] 270 | pub unsafe extern fn frame_add_points(frame: *mut Frame, points: *const crate::Point, len: usize) { 271 | let frame = &mut *frame; 272 | let points = std::slice::from_raw_parts(points, len); 273 | frame.frame.add_points(points.iter().cloned()); 274 | } 275 | 276 | /// Add a sequence of consecutive lines. 277 | /// 278 | /// If some points already exist in the frame, this method will create a blank segment between the 279 | /// previous point and the first point before appending this sequence. 280 | #[no_mangle] 281 | pub unsafe extern fn frame_add_lines(frame: *mut Frame, points: *const crate::Point, len: usize) { 282 | let frame = &mut *frame; 283 | let points = std::slice::from_raw_parts(points, len); 284 | frame.frame.add_lines(points.iter().cloned()); 285 | } 286 | 287 | pub unsafe extern fn frame_hz(frame: *const Frame) -> u32 { 288 | (*frame).frame.frame_hz() 289 | } 290 | 291 | pub unsafe extern fn frame_point_hz(frame: *const Frame) -> u32 { 292 | (*frame).frame.point_hz() 293 | } 294 | 295 | pub unsafe extern fn frame_latency_points(frame: *const Frame) -> u32 { 296 | (*frame).frame.latency_points() 297 | } 298 | 299 | pub unsafe extern fn points_per_frame(frame: *const Frame) -> u32 { 300 | (*frame).frame.latency_points() 301 | } 302 | 303 | /// Must be called in order to correctly clean up the frame stream. 304 | #[no_mangle] 305 | pub unsafe extern fn frame_stream_drop(stream_ptr: *mut FrameStream) { 306 | if stream_ptr != std::ptr::null_mut() { 307 | Box::from_raw(stream_ptr); 308 | } 309 | } 310 | 311 | /// Must be called in order to correctly clean up the raw stream. 312 | #[no_mangle] 313 | pub unsafe extern fn raw_stream_drop(stream_ptr: *mut RawStream) { 314 | if stream_ptr != std::ptr::null_mut() { 315 | Box::from_raw(stream_ptr); 316 | } 317 | } 318 | 319 | /// Must be called in order to correctly clean up the API resources. 320 | #[no_mangle] 321 | pub unsafe extern fn api_drop(api_ptr: *mut Api) { 322 | if api_ptr != std::ptr::null_mut() { 323 | Box::from_raw(api_ptr); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/stream/frame/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{Point, RawPoint}; 2 | use crate::stream; 3 | use crate::stream::raw::{self, Buffer}; 4 | use std::io; 5 | use std::ops::{Deref, DerefMut}; 6 | use std::sync::{mpsc, Arc, Mutex}; 7 | 8 | pub mod opt; 9 | 10 | /// The function that will be called each time a new `Frame` is requested. 11 | pub trait RenderFn: Fn(&mut M, &mut Frame) {} 12 | impl RenderFn for F where F: Fn(&mut M, &mut Frame) {} 13 | 14 | /// A clone-able handle around a laser stream of frames. 15 | pub struct Stream { 16 | // A handle to the inner raw stream that drives this frame stream. 17 | raw: raw::Stream, 18 | // A channel over which updates to the interpolation conf can be sent. 19 | state_update_tx: mpsc::Sender, 20 | } 21 | 22 | // State associated with the frame stream shared between the handle and laser stream. 23 | #[derive(Clone)] 24 | struct State { 25 | frame_hz: u32, 26 | interpolation_conf: opt::InterpolationConfig, 27 | } 28 | 29 | // Updates for the interpolation config sent from the stream handle to the laser thread. 30 | type StateUpdate = Box; 31 | 32 | /// A wrapper around the `Vec` of points being collected for the frame. 33 | /// 34 | /// Provides a suite of methods that ease the process of submitting points. 35 | /// 36 | /// Segments that contain more than one blank point in a row will be considered a blank segment. 37 | pub struct Frame { 38 | frame_hz: u32, 39 | point_hz: u32, 40 | latency_points: u32, 41 | points: Vec, 42 | } 43 | 44 | // A type used for requesting frames from the user and feeding them to the raw buffer. 45 | struct Requester { 46 | last_frame_point: Option, 47 | raw_points: Vec, 48 | } 49 | 50 | // The type of the default function used for the `process_raw` function if none is specified. 51 | type DefaultProcessRawFn = fn(&mut M, &mut Buffer); 52 | 53 | /// A type allowing to build a raw laser stream. 54 | pub struct Builder> { 55 | /// The laser API inner state, used to find a DAC during `build` if one isn't specified. 56 | pub(crate) api_inner: Arc, 57 | pub builder: stream::Builder, 58 | pub model: M, 59 | pub render: F, 60 | pub process_raw: R, 61 | pub frame_hz: Option, 62 | pub interpolation_conf: opt::InterpolationConfigBuilder, 63 | } 64 | 65 | impl Stream { 66 | /// Update the `distance_per_point` field of the interpolation configuration. 67 | /// 68 | /// The value will be updated on the laser thread prior to requesting the next frame. 69 | /// 70 | /// Returns an `Err` if communication with the laser thread has been closed. 71 | pub fn set_distance_per_point(&self, d: f32) -> Result<(), mpsc::SendError<()>> { 72 | self.send_frame_state_update(move |state| state.interpolation_conf.distance_per_point = d) 73 | .map_err(|_| mpsc::SendError(())) 74 | } 75 | 76 | /// Update the `blank_delay_points` field of the interpolation configuration. 77 | /// 78 | /// The value will be updated on the laser thread prior to requesting the next frame. 79 | /// 80 | /// Returns an `Err` if communication with the laser thread has been closed. 81 | pub fn set_blank_delay_points(&self, ps: u32) -> Result<(), mpsc::SendError<()>> { 82 | self.send_frame_state_update(move |state| state.interpolation_conf.blank_delay_points = ps) 83 | .map_err(|_| mpsc::SendError(())) 84 | } 85 | 86 | /// Update the `radians_per_point` field of the interpolation configuration. 87 | /// 88 | /// The value will be updated on the laser thread prior to requesting the next frame. 89 | /// 90 | /// Returns an `Err` if communication with the laser thread has been closed. 91 | pub fn set_radians_per_point(&self, rad: f32) -> Result<(), mpsc::SendError<()>> { 92 | self.send_frame_state_update(move |state| state.interpolation_conf.radians_per_point = rad) 93 | .map_err(|_| mpsc::SendError(())) 94 | } 95 | 96 | /// Update the rate at which the stream will attempt to present images via the DAC. 97 | /// 98 | /// The value will be updated on the laser thread prior to requesting the next frame. 99 | /// 100 | /// Returns an `Err` if communication with the laser thread has been closed. 101 | pub fn set_frame_hz(&self, fps: u32) -> Result<(), mpsc::SendError<()>> { 102 | self.send_frame_state_update(move |state| state.frame_hz = fps) 103 | .map_err(|_| mpsc::SendError(())) 104 | } 105 | 106 | // Simplify sending a `StateUpdate` to the laser thread. 107 | fn send_frame_state_update(&self, update: F) -> Result<(), mpsc::SendError> 108 | where 109 | F: FnOnce(&mut State) + Send + 'static, 110 | { 111 | let mut update_opt = Some(update); 112 | let update_fn = move |state: &mut State| { 113 | if let Some(update) = update_opt.take() { 114 | update(state); 115 | } 116 | }; 117 | self.state_update_tx.send(Box::new(update_fn)) 118 | } 119 | } 120 | 121 | impl Builder { 122 | /// The DAC with which the stream should be established. 123 | pub fn detected_dac(mut self, dac: crate::DetectedDac) -> Self { 124 | self.builder.dac = Some(dac); 125 | self 126 | } 127 | 128 | /// The initial rate at which the DAC should process points per second. 129 | /// 130 | /// This value should be no greater than the detected DAC's `max_point_hz`. 131 | /// 132 | /// By default this value is `stream::DEFAULT_POINT_HZ`. 133 | pub fn point_hz(mut self, point_hz: u32) -> Self { 134 | self.builder.point_hz = Some(point_hz); 135 | self 136 | } 137 | 138 | /// The initial rate at which the DAC should output frames per second. 139 | /// 140 | /// This in combination with the `point_hz` is used to determine the `points_per_frame`. Frames 141 | /// yielded by the user will be interpolated so that they always use exactly `points_per_frame` 142 | /// number of points per frame. 143 | /// 144 | /// By default, this value is `stream::DEFAULT_FRAME_HZ`. 145 | pub fn frame_hz(mut self, frame_hz: u32) -> Self { 146 | self.frame_hz = Some(frame_hz); 147 | self 148 | } 149 | 150 | /// The maximum latency specified as a number of points. 151 | /// 152 | /// Each time the laser indicates its "fullness", the raw stream will request enough points 153 | /// from the render function to fill the DAC buffer up to `latency_points`. 154 | pub fn latency_points(mut self, points: u32) -> Self { 155 | self.builder.latency_points = Some(points); 156 | self 157 | } 158 | 159 | /// The minimum distance the interpolator can travel along an edge before a new point is 160 | /// required. 161 | /// 162 | /// By default, this value is `InterpolationConfig::DEFAULT_DISTANCE_PER_POINT`. 163 | pub fn distance_per_point(mut self, dpp: f32) -> Self { 164 | self.interpolation_conf.distance_per_point = Some(dpp); 165 | self 166 | } 167 | 168 | /// The number of points to insert at the end of a blank to account for light modulator delay. 169 | /// 170 | /// By default, this value is `InterpolationConfig::DEFAULT_BLANK_DELAY_POINTS`. 171 | pub fn blank_delay_points(mut self, points: u32) -> Self { 172 | self.interpolation_conf.blank_delay_points = Some(points); 173 | self 174 | } 175 | 176 | /// The amount of delay to add based on the angle of the corner in radians. 177 | /// 178 | /// By default, this value is `InterpolationConfig::DEFAULT_RADIANS_PER_POINT`. 179 | pub fn radians_per_point(mut self, radians: f32) -> Self { 180 | self.interpolation_conf.radians_per_point = Some(radians); 181 | self 182 | } 183 | 184 | /// Specify a function that allows for processing the raw points before submission to the DAC. 185 | /// 186 | /// This mgiht be useful for: 187 | /// 188 | /// - applying post-processing effects onto the optimised, interpolated points. 189 | /// - monitoring the raw points resulting from the optimisation and interpolation processes. 190 | /// - tuning brightness of colours based on safety zones. 191 | /// 192 | /// The given function will get called right before submission of the optimised, interpolated 193 | /// buffer. 194 | pub fn process_raw(self, process_raw: R2) -> Builder { 195 | let Builder { api_inner, builder, model, render, frame_hz, interpolation_conf, .. } = self; 196 | Builder { api_inner, builder, model, render, process_raw, frame_hz, interpolation_conf } 197 | } 198 | 199 | /// Build the stream with the specified parameters. 200 | /// 201 | /// **Note:** If no `dac` was specified, this will method will block until a DAC is detected. 202 | /// The first detected DAC is the DAC with which a stream will be established. 203 | pub fn build(self) -> io::Result> 204 | where 205 | M: 'static + Send, 206 | F: 'static + RenderFn + Send, 207 | R: 'static + raw::RenderFn + Send, 208 | { 209 | let Builder { 210 | api_inner, 211 | builder, 212 | model, 213 | render, 214 | process_raw, 215 | frame_hz, 216 | interpolation_conf, 217 | } = self; 218 | 219 | // Retrieve the interpolation configuration. 220 | let interpolation_conf = interpolation_conf.build(); 221 | // Retrieve the frame rate to initialise the stream with. 222 | let frame_hz = frame_hz.unwrap_or(stream::DEFAULT_FRAME_HZ); 223 | 224 | // The type used for buffering frames and using them to serve points to the raw stream. 225 | let requester = Requester { last_frame_point: None, raw_points: vec![] }; 226 | let requester = Arc::new(Mutex::new(requester)); 227 | 228 | // A channel for updating the interpolation config. 229 | let (state_update_tx, state_update_rx) = mpsc::channel(); 230 | let state_update_tx: mpsc::Sender = state_update_tx; 231 | 232 | // State to live on the stream thread. 233 | let state = Arc::new(Mutex::new(State { frame_hz, interpolation_conf })); 234 | 235 | // A render function for the inner raw stream. 236 | let raw_render = move |model: &mut M, buffer: &mut Buffer| { 237 | // Check for updates and retrieve a copy of the state. 238 | let state = { 239 | let mut state = state.lock().expect("failed to lock"); 240 | for mut state_update in state_update_rx.try_iter() { 241 | (*state_update)(&mut state); 242 | } 243 | state.clone() 244 | }; 245 | 246 | let mut guard = requester.lock().expect("failed to lock frame requester"); 247 | guard.fill_buffer(model, &render, buffer, &state); 248 | process_raw(model, buffer); 249 | }; 250 | 251 | // Create the raw builder and build the raw stream. 252 | let raw_builder = raw::Builder { api_inner, builder, model, render: raw_render }; 253 | let raw_stream = raw_builder.build()?; 254 | let stream = Stream { raw: raw_stream, state_update_tx }; 255 | Ok(stream) 256 | } 257 | } 258 | 259 | impl Frame { 260 | /// The rate at which frames of points will be emitted by the DAC. 261 | pub fn frame_hz(&self) -> u32 { 262 | self.frame_hz 263 | } 264 | 265 | /// The rate at which these points will be emitted by the DAC. 266 | pub fn point_hz(&self) -> u32 { 267 | self.point_hz 268 | } 269 | 270 | /// The maximum number of points with which to fill the DAC's buffer. 271 | pub fn latency_points(&self) -> u32 { 272 | self.latency_points 273 | } 274 | 275 | /// The number of points emitted by the DAC per frame. 276 | pub fn points_per_frame(&self) -> u32 { 277 | self.point_hz / self.frame_hz 278 | } 279 | 280 | /// Add a sequence of consecutive points separated by blank space. 281 | /// 282 | /// If some points already exist in the frame, this method will create a blank segment between 283 | /// the previous point and the first point before appending this sequence. 284 | pub fn add_points(&mut self, points: I) 285 | where 286 | I: IntoIterator, 287 | I::Item: AsRef, 288 | { 289 | for p in points { 290 | let p = *p.as_ref(); 291 | self.add_lines([p, p].iter().cloned()); 292 | } 293 | } 294 | 295 | /// Add a sequence of consecutive lines. 296 | /// 297 | /// If some points already exist in the frame, this method will create a blank segment between 298 | /// the previous point and the first point before appending this sequence. 299 | pub fn add_lines(&mut self, points: I) 300 | where 301 | I: IntoIterator, 302 | I::Item: AsRef, 303 | { 304 | let mut points = points.into_iter(); 305 | if let Some(&last) = self.points.last() { 306 | if let Some(next) = points.next() { 307 | let next = next.as_ref(); 308 | self.points.push(last.blanked()); 309 | self.points.push(next.blanked()); 310 | self.points.push(*next); 311 | } 312 | } 313 | self.points.extend(points.map(|p| p.as_ref().clone())); 314 | } 315 | } 316 | 317 | impl Requester { 318 | // Fill the given buffer by requesting frames from the given user `render` function as 319 | // required. 320 | fn fill_buffer( 321 | &mut self, 322 | model: &mut M, 323 | render: F, 324 | buffer: &mut Buffer, 325 | state: &State, 326 | ) 327 | where 328 | F: RenderFn, 329 | { 330 | // If the frame rate is `0`, leave the buffer empty. 331 | if state.frame_hz == 0 { 332 | return; 333 | } 334 | 335 | // If the buffer has no points, there's nothing to fill. 336 | if buffer.is_empty() { 337 | return; 338 | } 339 | 340 | // The number of points to generate per frame. 341 | let point_hz = buffer.point_hz(); 342 | let latency_points = buffer.latency_points(); 343 | 344 | // The starting index of the buffer we'll write to. 345 | let mut start = 0; 346 | 347 | // If there are still un-read points, use those first. 348 | if !self.raw_points.is_empty() { 349 | // If the pending range would not fill the buffer, write what we can. 350 | if self.raw_points.len() < buffer.len() { 351 | start = self.raw_points.len(); 352 | buffer[..start].copy_from_slice(&self.raw_points); 353 | self.raw_points.clear(); 354 | 355 | // If we have the exact number of frames as output, write them and return. 356 | } else if self.raw_points.len() == buffer.len() { 357 | buffer.copy_from_slice(&self.raw_points); 358 | self.raw_points.clear(); 359 | return; 360 | 361 | // If we have too many points, write what we can and leave the rest. 362 | } else { 363 | let end = buffer.len(); 364 | buffer.copy_from_slice(&self.raw_points[..end]); 365 | self.raw_points.drain(0..end); 366 | return; 367 | } 368 | } 369 | 370 | // The number of points to fill for each frame. 371 | let points_per_frame = point_hz / state.frame_hz; 372 | 373 | // If we reached this point, `self.raw_points` is empty so we should fill buffer with 374 | // frames until it is full. 375 | loop { 376 | // See how many points are left to fill. 377 | let num_points_remaining = buffer.len() - start; 378 | 379 | // Determine how many points to fill this pass. 380 | let num_points_to_fill = std::cmp::min(points_per_frame as usize, num_points_remaining); 381 | 382 | // Render a frame of points. 383 | let mut frame = Frame { 384 | point_hz, 385 | latency_points, 386 | frame_hz: state.frame_hz, 387 | points: vec![], // TODO: Reuse this buffer rather than allocating every loop. 388 | }; 389 | render(model, &mut frame); 390 | 391 | // If we were given no points, the user must be expecting an empty frame. 392 | if frame.points.is_empty() { 393 | let blank_point = self.last_frame_point.map(|p| p.blanked()) 394 | .unwrap_or_else(RawPoint::centered_blank); 395 | self.raw_points.extend((0..points_per_frame).map(|_| blank_point)); 396 | 397 | // Otherwise, we'll optimise and interpolate the given points. 398 | } else { 399 | // Optimisation passes. 400 | let segs = opt::points_to_segments(frame.iter().cloned()); 401 | let pg = opt::segments_to_point_graph(segs); 402 | let eg = opt::point_graph_to_euler_graph(&pg); 403 | let ec = opt::euler_graph_to_euler_circuit(&eg); 404 | 405 | // Blank from last point of the previous frame to first point of this one. 406 | let last_frame_point = self.last_frame_point.take(); 407 | let inter_frame_blank_points = match last_frame_point { 408 | Some(last) => { 409 | match eg.node_indices().next() { 410 | None => vec![], 411 | Some(next_id) => { 412 | let next = eg[next_id]; 413 | if last.position != next.position { 414 | let a = last.blanked().with_weight(0); 415 | let b = next.to_raw().blanked(); 416 | let blank_delay_points = 417 | state.interpolation_conf.blank_delay_points; 418 | opt::blank_segment_points(a, b, blank_delay_points).collect() 419 | } else { 420 | vec![] 421 | } 422 | } 423 | } 424 | } 425 | None => vec![], 426 | }; 427 | 428 | // Subtract the inter-frame blank points from points per frame to maintain frame_hz. 429 | let inter_frame_point_count = inter_frame_blank_points.len() as u32; 430 | let target_points = if points_per_frame > inter_frame_point_count { 431 | points_per_frame - inter_frame_point_count 432 | } else { 433 | 0 434 | }; 435 | 436 | // Join the inter-frame points with the interpolated frame. 437 | let interp_conf = &state.interpolation_conf; 438 | let mut interpolated = 439 | opt::interpolate_euler_circuit(&ec, &eg, target_points, interp_conf); 440 | 441 | // If the interpolated frame is empty there were no lit points or lines. 442 | // In this case, we'll produce an empty frame. 443 | if interpolated.is_empty() { 444 | let blank_point = inter_frame_blank_points.last() 445 | .map(|&p| p) 446 | .or_else(|| last_frame_point.map(|p| p.blanked())) 447 | .unwrap_or_else(RawPoint::centered_blank); 448 | interpolated.extend((0..target_points).map(|_| blank_point)); 449 | } 450 | 451 | self.raw_points.extend(inter_frame_blank_points); 452 | self.raw_points.extend(interpolated); 453 | } 454 | 455 | // Update the last frame point. 456 | self.last_frame_point = self.raw_points.last().map(|&p| p); 457 | 458 | // Write the points to buffer. 459 | let end = start + num_points_to_fill; 460 | let range = start..end; 461 | buffer[range.clone()].copy_from_slice(&self.raw_points[..range.len()]); 462 | self.raw_points.drain(..range.len()); 463 | 464 | // If this output filled the buffer, break. 465 | if end == buffer.len() { 466 | break; 467 | } 468 | 469 | // Continue looping through the next frames. 470 | start = end; 471 | } 472 | } 473 | } 474 | 475 | impl Deref for Frame { 476 | type Target = Vec; 477 | fn deref(&self) -> &Self::Target { 478 | &self.points 479 | } 480 | } 481 | 482 | impl DerefMut for Frame { 483 | fn deref_mut(&mut self) -> &mut Self::Target { 484 | &mut self.points 485 | } 486 | } 487 | 488 | impl Deref for Stream { 489 | type Target = raw::Stream; 490 | fn deref(&self) -> &Self::Target { 491 | &self.raw 492 | } 493 | } 494 | 495 | // The default function used for the `process_raw` function if none is specified. 496 | pub(crate) fn default_process_raw_fn(_model: &mut M, _buffer: &mut Buffer) { 497 | } 498 | -------------------------------------------------------------------------------- /src/stream/raw.rs: -------------------------------------------------------------------------------- 1 | use crate::{DetectedDac, RawPoint}; 2 | use crate::util::{clamp, map_range}; 3 | use derive_more::From; 4 | use failure::Fail; 5 | use std::io; 6 | use std::ops::{Deref, DerefMut}; 7 | use std::sync::atomic::{self, AtomicBool}; 8 | use std::sync::{mpsc, Arc, Mutex}; 9 | 10 | /// The function that will be called when a `Buffer` of points is requested. 11 | pub trait RenderFn: Fn(&mut M, &mut Buffer) {} 12 | impl RenderFn for F where F: Fn(&mut M, &mut Buffer) {} 13 | 14 | /// A clone-able handle around a raw laser stream. 15 | #[derive(Clone)] 16 | pub struct Stream { 17 | // A channel of updating the raw laser stream thread state. 18 | state_update_tx: mpsc::Sender, 19 | // A channel for sending model updates to the laser stream thread. 20 | model_update_tx: mpsc::Sender>, 21 | // Data shared between each `Stream` handle to a single stream. 22 | shared: Arc>, 23 | } 24 | 25 | // State managed on the laser thread. 26 | #[derive(Clone)] 27 | struct State { 28 | point_hz: u32, 29 | latency_points: u32, 30 | } 31 | 32 | // Data shared between each `Stream` handle to a single stream. 33 | struct Shared { 34 | // The user's laser model 35 | model: Arc>>, 36 | // Whether or not the stream is currently paused. 37 | is_paused: AtomicBool, 38 | // Whether or not the stream has been closed. 39 | is_closed: Arc, 40 | } 41 | 42 | /// A buffer of laser points yielded by either a raw or frame stream source function. 43 | #[derive(Debug)] 44 | pub struct Buffer { 45 | pub(crate) point_hz: u32, 46 | pub(crate) latency_points: u32, 47 | pub(crate) points: Box<[RawPoint]>, 48 | } 49 | 50 | /// A type allowing to build a raw laser stream. 51 | pub struct Builder { 52 | /// The laser API inner state, used to find a DAC during `build` if one isn't specified. 53 | pub(crate) api_inner: Arc, 54 | pub builder: super::Builder, 55 | pub model: M, 56 | pub render: F, 57 | } 58 | 59 | // The type used for sending state updates from the stream handle thread to the laser thread. 60 | type StateUpdate = Box; 61 | 62 | /// The type used for sending model updates from the stream handle thread to the laser thread. 63 | pub type ModelUpdate = Box; 64 | 65 | /// Errors that may occur while running a laser stream. 66 | #[derive(Debug, Fail, From)] 67 | pub enum RawStreamError { 68 | #[fail(display = "an Ether Dream DAC stream error occurred: {}", err)] 69 | EtherDreamStream { 70 | #[fail(cause)] 71 | err: EtherDreamStreamError, 72 | } 73 | } 74 | 75 | /// Errors that may occur while creating a node crate. 76 | #[derive(Debug, Fail, From)] 77 | pub enum EtherDreamStreamError { 78 | #[fail(display = "laser DAC detection failed: {}", err)] 79 | FailedToDetectDacs { 80 | #[fail(cause)] 81 | err: io::Error, 82 | }, 83 | #[fail(display = "failed to connect the DAC stream: {}", err)] 84 | FailedToConnectStream { 85 | #[fail(cause)] 86 | err: ether_dream::dac::stream::CommunicationError, 87 | }, 88 | #[fail(display = "failed to prepare the DAC stream: {}", err)] 89 | FailedToPrepareStream { 90 | #[fail(cause)] 91 | err: ether_dream::dac::stream::CommunicationError, 92 | }, 93 | #[fail(display = "failed to begin the DAC stream: {}", err)] 94 | FailedToBeginStream { 95 | #[fail(cause)] 96 | err: ether_dream::dac::stream::CommunicationError, 97 | }, 98 | #[fail(display = "failed to submit data over the DAC stream: {}", err)] 99 | FailedToSubmitData { 100 | #[fail(cause)] 101 | err: ether_dream::dac::stream::CommunicationError, 102 | }, 103 | #[fail(display = "failed to submit point rate change over the DAC stream: {}", err)] 104 | FailedToSubmitPointRate { 105 | #[fail(cause)] 106 | err: ether_dream::dac::stream::CommunicationError, 107 | }, 108 | #[fail(display = "failed to submit stop command to the DAC stream: {}", err)] 109 | FailedToStopStream { 110 | #[fail(cause)] 111 | err: ether_dream::dac::stream::CommunicationError, 112 | }, 113 | } 114 | 115 | impl Stream { 116 | /// Update the rate at which the DAC should process points per second. 117 | /// 118 | /// This value should be no greater than the detected DAC's `max_point_hz`. 119 | /// 120 | /// By default this value is `stream::DEFAULT_POINT_HZ`. 121 | pub fn set_point_hz(&self, point_hz: u32) -> Result<(), mpsc::SendError<()>> { 122 | self.send_raw_state_update(move |state| state.point_hz = point_hz) 123 | .map_err(|_| mpsc::SendError(())) 124 | } 125 | 126 | /// The maximum latency specified as a number of points. 127 | /// 128 | /// Each time the laser indicates its "fullness", the raw stream will request enough points 129 | /// from the render function to fill the DAC buffer up to `latency_points`. 130 | /// 131 | /// This value should be no greaterthan the DAC's `buffer_capacity`. 132 | pub fn set_latency_points(&self, points: u32) -> Result<(), mpsc::SendError<()>> { 133 | self.send_raw_state_update(move |state| state.latency_points = points) 134 | .map_err(|_| mpsc::SendError(())) 135 | } 136 | 137 | /// Send the given model update to the laser thread to be applied ASAP. 138 | /// 139 | /// If the laser is currently rendering, the update will be applied immediately after the 140 | /// function call completes. 141 | /// 142 | /// If the stream is currently paused, the update will be applied immediately. 143 | /// 144 | /// **Note:** This function will be applied on the real-time laser thread so users should avoid 145 | /// performing any kind of I/O, locking, blocking, (de)allocations or anything that may run for 146 | /// an indeterminate amount of time. 147 | pub fn send(&self, update: F) -> Result<(), mpsc::SendError>> 148 | where 149 | F: FnOnce(&mut M) + Send + 'static, 150 | { 151 | // NOTE: The following code may mean that on extremely rare occasions an update does 152 | // not get applied for an indeterminate amount of time. This might be the case if a 153 | // stream is unpaused but becomes paused *immediately* after the `is_paused` atomic 154 | // condition is read as `false` - the update would be sent but the stream would be 155 | // paused and in turn the update will not get processed until the stream is unpaused 156 | // again. It would be nice to work out a solution to this that does not require 157 | // spawning another thread for each stream. 158 | 159 | // If the thread is currently paused, take the lock and immediately apply it as we know 160 | // there will be no contention with the laser thread. 161 | if self.shared.is_paused.load(atomic::Ordering::Relaxed) { 162 | if let Ok(mut guard) = self.shared.model.lock() { 163 | let mut model = guard.take().unwrap(); 164 | update(&mut model); 165 | *guard = Some(model); 166 | } 167 | // Otherwise send the update to the laser thread. 168 | } else { 169 | // Move the `FnOnce` into a `FnMut` closure so that it can be called when it gets to 170 | // the laser thread. We do this as it's currently not possible to call a `Box`, 171 | // as `FnOnce`'s `call` method takes `self` by value and thus is technically not object 172 | // safe. 173 | let mut update_opt = Some(update); 174 | let update_fn = move |model: &mut M| { 175 | if let Some(update) = update_opt.take() { 176 | update(model); 177 | } 178 | }; 179 | self.model_update_tx.send(Box::new(update_fn))?; 180 | } 181 | 182 | Ok(()) 183 | } 184 | 185 | // Simplify sending a `StateUpdate` to the laser thread. 186 | fn send_raw_state_update(&self, update: F) -> Result<(), mpsc::SendError> 187 | where 188 | F: FnOnce(&mut State) + Send + 'static, 189 | { 190 | let mut update_opt = Some(update); 191 | let update_fn = move |state: &mut State| { 192 | if let Some(update) = update_opt.take() { 193 | update(state); 194 | } 195 | }; 196 | self.state_update_tx.send(Box::new(update_fn)) 197 | } 198 | } 199 | 200 | impl Buffer { 201 | /// The rate at which these points will be emitted by the DAC. 202 | pub fn point_hz(&self) -> u32 { 203 | self.point_hz 204 | } 205 | 206 | /// The maximum number of points with which to fill the DAC's buffer. 207 | pub fn latency_points(&self) -> u32 { 208 | self.latency_points 209 | } 210 | } 211 | 212 | impl Builder { 213 | /// The DAC with which the stream should be established. 214 | /// 215 | /// If none is specified, the stream will associate itself with the first DAC detecged on the 216 | /// system. 217 | /// 218 | /// ## DAC Dropouts 219 | /// 220 | /// If communication is lost with the DAC that was specified by this method, the stream will 221 | /// attempt to re-establish connection with this DAC as quickly as possible. If no DAC was 222 | /// specified, the stream will attempt to establish a new connection with the next DAC that is 223 | /// detected on the system. 224 | pub fn detected_dac(mut self, dac: DetectedDac) -> Self { 225 | self.builder.dac = Some(dac); 226 | self 227 | } 228 | 229 | /// The initial rate at which the DAC should process points per second. 230 | /// 231 | /// This value should be no greater than the detected DAC's `max_point_hz`. 232 | /// 233 | /// By default this value is `stream::DEFAULT_POINT_HZ`. 234 | pub fn point_hz(mut self, point_hz: u32) -> Self { 235 | self.builder.point_hz = Some(point_hz); 236 | self 237 | } 238 | 239 | /// The maximum latency specified as a number of points. 240 | /// 241 | /// Each time the laser indicates its "fullness", the raw stream will request enough points 242 | /// from the render function to fill the DAC buffer up to `latency_points`. 243 | pub fn latency_points(mut self, points: u32) -> Self { 244 | self.builder.latency_points = Some(points); 245 | self 246 | } 247 | 248 | /// Build the stream with the specified parameters. 249 | /// 250 | /// **Note:** If no `dac` was specified, this will method will block until a DAC is detected. 251 | /// The first detected DAC is the DAC with which a stream will be established. 252 | pub fn build(self) -> io::Result> 253 | where 254 | M: 'static + Send, 255 | F: 'static + RenderFn + Send, 256 | { 257 | let Builder { api_inner, builder, model, render } = self; 258 | 259 | // Prepare the model for sharing between the laser thread and stream handle. 260 | let model = Arc::new(Mutex::new(Some(model))); 261 | let model_2 = model.clone(); 262 | 263 | // The channels used for sending updates to the model via the stream handle. 264 | let (model_update_tx, m_rx) = mpsc::channel(); 265 | let (state_update_tx, s_rx) = mpsc::channel(); 266 | 267 | // Retrieve the specified point rate or use a default. 268 | let point_hz = builder.point_hz.unwrap_or(super::DEFAULT_POINT_HZ); 269 | // Retrieve the latency as a number of points. 270 | let latency_points = builder.latency_points 271 | .unwrap_or_else(|| default_latency_points(point_hz)); 272 | 273 | // The raw laser stream state to live on the laser thread. 274 | let state = Arc::new(Mutex::new(State { point_hz, latency_points })); 275 | 276 | // Retrieve whether or not the user specified a detected DAC. 277 | let mut maybe_dac = builder.dac; 278 | 279 | // A flag for tracking whether or not the stream has been closed. 280 | let is_closed = Arc::new(AtomicBool::new(false)); 281 | let is_closed2 = is_closed.clone(); 282 | 283 | // Spawn the thread for communicating with the DAC. 284 | std::thread::Builder::new() 285 | .name("raw_laser_stream_thread".into()) 286 | .spawn(move || { 287 | let mut connect_attempts = 3; 288 | while !is_closed2.load(atomic::Ordering::Relaxed) { 289 | // If there are no more remaining connection attempts, try to redetect the DAC 290 | // if a specific DAC was specified by the user. 291 | if connect_attempts == 0 { 292 | connect_attempts = 3; 293 | if let Some(ref mut dac) = maybe_dac { 294 | let dac_id = dac.id(); 295 | eprintln!("re-attempting to detect DAC with id: {:?}", dac_id); 296 | *dac = match api_inner.detect_dac(dac_id) { 297 | Ok(dac) => dac, 298 | Err(err) => { 299 | let err = EtherDreamStreamError::FailedToDetectDacs { err }; 300 | return Err(RawStreamError::EtherDreamStream { err }); 301 | } 302 | }; 303 | } 304 | } 305 | 306 | // Retrieve the DAC or find one. 307 | let dac = match maybe_dac { 308 | Some(ref dac) => dac.clone(), 309 | None => api_inner.detect_dacs() 310 | .map_err(|err| EtherDreamStreamError::FailedToDetectDacs { err })? 311 | .next() 312 | .expect("ether dream DAC detection iterator should never return `None`") 313 | .map_err(|err| EtherDreamStreamError::FailedToDetectDacs { err })?, 314 | }; 315 | 316 | // Connect and run the laser stream. 317 | match run_laser_stream(&dac, &state, &model_2, &render, &s_rx, &m_rx, &is_closed2) { 318 | Ok(()) => return Ok(()), 319 | Err(RawStreamError::EtherDreamStream { err }) => match err { 320 | // If we failed to connect to the DAC, keep track of attempts. 321 | EtherDreamStreamError::FailedToConnectStream { err } => { 322 | eprintln!("failed to connect to stream: {}", err); 323 | connect_attempts -= 1; 324 | eprintln!( 325 | "connection attempts remaining before re-detecting DAC: {}", 326 | connect_attempts, 327 | ); 328 | // Sleep for a moment to avoid spamming the socket. 329 | std::thread::sleep(std::time::Duration::from_millis(16)); 330 | } 331 | 332 | // If we failed to prepare the stream or submit data, retry. 333 | EtherDreamStreamError::FailedToPrepareStream { .. } 334 | | EtherDreamStreamError::FailedToBeginStream { .. } 335 | | EtherDreamStreamError::FailedToSubmitData { .. } 336 | | EtherDreamStreamError::FailedToSubmitPointRate { .. } => { 337 | eprintln!("{} - will now attempt to reconnect", err); 338 | } 339 | 340 | // Return all other errors. 341 | err => return Err(RawStreamError::EtherDreamStream { err }), 342 | } 343 | } 344 | } 345 | 346 | Ok(()) 347 | })?; 348 | 349 | let is_paused = AtomicBool::new(false); 350 | let shared = Arc::new(Shared { model, is_paused, is_closed }); 351 | let stream = Stream { shared, state_update_tx, model_update_tx }; 352 | Ok(stream) 353 | } 354 | } 355 | 356 | impl Deref for Buffer { 357 | type Target = [RawPoint]; 358 | fn deref(&self) -> &Self::Target { 359 | &self.points 360 | } 361 | } 362 | 363 | impl DerefMut for Buffer { 364 | fn deref_mut(&mut self) -> &mut Self::Target { 365 | &mut self.points 366 | } 367 | } 368 | 369 | impl Drop for Stream { 370 | fn drop(&mut self) { 371 | self.shared.is_closed.store(true, atomic::Ordering::Relaxed) 372 | } 373 | } 374 | 375 | /// Given the point rate, determine a default latency at ~16ms. 376 | pub fn default_latency_points(point_hz: u32) -> u32 { 377 | super::points_per_frame(point_hz, 60) 378 | } 379 | 380 | // The function to run on the laser stream thread. 381 | fn run_laser_stream( 382 | dac: &DetectedDac, 383 | state: &Arc>, 384 | model: &Arc>>, 385 | render: F, 386 | state_update_rx: &mpsc::Receiver, 387 | model_update_rx: &mpsc::Receiver>, 388 | is_closed: &AtomicBool, 389 | ) -> Result<(), RawStreamError> 390 | where 391 | F: RenderFn, 392 | { 393 | // Currently only ether dream is supported, so retrieve the broadcast and addr. 394 | let (broadcast, src_addr) = match dac { 395 | DetectedDac::EtherDream { broadcast, source_addr } => { 396 | (broadcast, source_addr) 397 | } 398 | }; 399 | 400 | // A buffer for collecting model updates. 401 | let mut pending_model_updates: Vec> = Vec::new(); 402 | 403 | // Establish the TCP connection. 404 | let ip = src_addr.ip().clone(); 405 | 406 | let mut stream = ether_dream::dac::stream::connect(&broadcast, ip) 407 | .map_err(|err| EtherDreamStreamError::FailedToConnectStream { err })?; 408 | 409 | // Prepare the DAC's playback engine and await the repsonse. 410 | stream 411 | .queue_commands() 412 | .prepare_stream() 413 | .submit() 414 | .map_err(|err| EtherDreamStreamError::FailedToPrepareStream { err })?; 415 | 416 | let dac_max_point_hz = dac.max_point_hz(); 417 | 418 | // Get the initial point hz by clamping via the DAC's maximum point rate. 419 | let init_point_hz = { 420 | let hz = state.lock().expect("failed to acquire raw state lock").point_hz; 421 | std::cmp::min(hz, dac_max_point_hz) 422 | }; 423 | 424 | // Queue the initial frame and tell the DAC to begin producing output. 425 | let low_water_mark = 0; 426 | let n_points = dac_remaining_buffer_capacity(stream.dac()); 427 | stream 428 | .queue_commands() 429 | .data((0..n_points).map(|_| centered_blank())) 430 | .begin(low_water_mark, init_point_hz) 431 | .submit() 432 | .map_err(|err| EtherDreamStreamError::FailedToBeginStream { err })?; 433 | 434 | // For collecting the ether-dream points. 435 | let mut ether_dream_points = vec![]; 436 | 437 | while !is_closed.load(atomic::Ordering::Relaxed) { 438 | // Collect any pending updates. 439 | pending_model_updates.extend(model_update_rx.try_iter()); 440 | // If there are some updates available, take the lock and apply them. 441 | if !pending_model_updates.is_empty() { 442 | if let Ok(mut guard) = model.lock() { 443 | let mut model = guard.take().unwrap(); 444 | for mut update in pending_model_updates.drain(..) { 445 | update(&mut model); 446 | } 447 | *guard = Some(model); 448 | } 449 | } 450 | 451 | // Check for updates and retrieve a copy of the state. 452 | let (state, prev_point_hz) = { 453 | let mut state = state.lock().expect("failed to acquare raw state lock"); 454 | 455 | // Keep track of whether or not the `point_hz` as changed. 456 | let prev_point_hz = std::cmp::min(state.point_hz, dac.max_point_hz()); 457 | 458 | // Apply updates. 459 | for mut state_update in state_update_rx.try_iter() { 460 | (*state_update)(&mut state); 461 | } 462 | 463 | (state.clone(), prev_point_hz) 464 | }; 465 | 466 | // Clamp the point hz by the DAC's maximum point rate. 467 | let point_hz = std::cmp::min(state.point_hz, dac.max_point_hz()); 468 | 469 | // If the point rate changed, we need to tell the DAC and set the control value on point 0. 470 | let point_rate_changed = point_hz != prev_point_hz; 471 | if point_rate_changed { 472 | stream 473 | .queue_commands() 474 | .point_rate(point_hz) 475 | .submit() 476 | .map_err(|err| EtherDreamStreamError::FailedToSubmitPointRate { err })?; 477 | } 478 | 479 | // Clamp the latency by the DAC's buffer capacity. 480 | let latency_points = std::cmp::min(state.latency_points, dac.buffer_capacity()); 481 | // Determine how many points the DAC can currently receive. 482 | let n_points = points_to_generate(stream.dac(), latency_points as u16) as usize; 483 | 484 | // The buffer that the user will write to. TODO: Re-use this points buffer. 485 | let mut buffer = Buffer { 486 | point_hz, 487 | latency_points: latency_points as _, 488 | points: vec![RawPoint::centered_blank(); n_points].into_boxed_slice(), 489 | }; 490 | 491 | // Request the points from the user. 492 | if let Ok(mut guard) = model.lock() { 493 | let mut m = guard.take().unwrap(); 494 | render(&mut m, &mut buffer); 495 | *guard = Some(m); 496 | } 497 | 498 | // Retrieve the points. 499 | ether_dream_points.extend(buffer.iter().cloned().map(point_to_ether_dream_point)); 500 | 501 | // If the point rate changed, set the control value on the first point to trigger it. 502 | if point_rate_changed && !ether_dream_points.is_empty() { 503 | ether_dream_points[0].control = ether_dream::dac::PointControl::CHANGE_RATE.bits(); 504 | } 505 | 506 | // Submit the points. 507 | stream 508 | .queue_commands() 509 | .data(ether_dream_points.drain(..)) 510 | .submit() 511 | .map_err(|err| EtherDreamStreamError::FailedToSubmitData { err })?; 512 | } 513 | 514 | stream 515 | .queue_commands() 516 | .stop() 517 | .submit() 518 | .map_err(|err| EtherDreamStreamError::FailedToStopStream { err })?; 519 | 520 | Ok(()) 521 | } 522 | 523 | // The number of remaining points in the DAC. 524 | fn dac_remaining_buffer_capacity(dac: ðer_dream::dac::Dac) -> u16 { 525 | dac.buffer_capacity - 1 - dac.status.buffer_fullness 526 | } 527 | 528 | // Determine the number of points needed to fill the DAC. 529 | fn points_to_generate(dac: ðer_dream::dac::Dac, latency_points: u16) -> u16 { 530 | let remaining_capacity = dac_remaining_buffer_capacity(dac); 531 | let n = if dac.status.buffer_fullness < latency_points { 532 | latency_points - dac.status.buffer_fullness 533 | } else { 534 | 0 535 | }; 536 | std::cmp::min(n, remaining_capacity) 537 | } 538 | 539 | // Constructor for a centered, blank ether dream DAC point. 540 | fn centered_blank() -> ether_dream::protocol::DacPoint { 541 | ether_dream::protocol::DacPoint { 542 | control: 0, 543 | x: 0, 544 | y: 0, 545 | r: 0, 546 | g: 0, 547 | b: 0, 548 | i: 0, 549 | u1: 0, 550 | u2: 0, 551 | } 552 | } 553 | 554 | // Convert a `lase::point::Position` type to an `i16` representation compatible with ether dream. 555 | fn position_to_ether_dream_position([px, py]: crate::point::Position) -> [i16; 2] { 556 | let min = std::i16::MIN; 557 | let max = std::i16::MAX; 558 | let x = map_range(clamp(px, -1.0, 1.0), -1.0, 1.0, min as f64, max as f64) as i16; 559 | let y = map_range(clamp(py, -1.0, 1.0), -1.0, 1.0, min as f64, max as f64) as i16; 560 | [x, y] 561 | } 562 | 563 | // Convert a `lase::point::Rgb` type to an `u16` representation compatible with ether dream. 564 | fn color_to_ether_dream_color([pr, pg, pb]: crate::point::Rgb) -> [u16; 3] { 565 | let r = (clamp(pr, 0.0, 1.0) * std::u16::MAX as f32) as u16; 566 | let g = (clamp(pg, 0.0, 1.0) * std::u16::MAX as f32) as u16; 567 | let b = (clamp(pb, 0.0, 1.0) * std::u16::MAX as f32) as u16; 568 | [r, g, b] 569 | } 570 | 571 | // Convert the laser point to an ether dream DAC point. 572 | fn point_to_ether_dream_point(p: RawPoint) -> ether_dream::protocol::DacPoint { 573 | let [x, y] = position_to_ether_dream_position(p.position); 574 | let [r, g, b] = color_to_ether_dream_color(p.color); 575 | let (control, i, u1, u2) = (0, 0, 0, 0); 576 | ether_dream::protocol::DacPoint { control, x, y, r, g, b, i, u1, u2 } 577 | } 578 | -------------------------------------------------------------------------------- /src/stream/frame/opt.rs: -------------------------------------------------------------------------------- 1 | //! A series of optimisation passes for laser frames. 2 | 3 | use crate::lerp::Lerp; 4 | use crate::point::{Point, Position, RawPoint}; 5 | use hashbrown::{HashMap, HashSet}; 6 | use petgraph::visit::EdgeRef; 7 | use petgraph::{Undirected}; 8 | 9 | /// Represents a line segment over which the laser scanner will travel. 10 | #[derive(Copy, Clone, Debug, PartialEq)] 11 | pub struct Segment { 12 | pub start: Point, 13 | pub end: Point, 14 | pub kind: SegmentKind, 15 | } 16 | 17 | /// describes whether a line segment between two points is blank or not. 18 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 19 | pub enum SegmentKind { 20 | Blank, 21 | Lit, 22 | } 23 | 24 | /// A type used to represent graph describing the points in a frame and how they are joined. 25 | /// 26 | /// Only lit edges are represented in this representation. 27 | pub type PointGraph = petgraph::Graph; 28 | 29 | /// A type used to represent a graph of points that contains at least one euler circuit. 30 | pub type EulerGraph = petgraph::Graph; 31 | 32 | /// A type used to represent a eulerian circuit through a eulerian graph. 33 | pub type EulerCircuit = Vec; 34 | 35 | type EdgeIndex = petgraph::graph::EdgeIndex; 36 | type NodeIndex = petgraph::graph::NodeIndex; 37 | 38 | /// An iterator yielding all lit line segments. 39 | #[derive(Clone)] 40 | pub struct Segments { 41 | points: I, 42 | last_point: Option, 43 | } 44 | 45 | /// Configuration options for eulerian circuit interpolation. 46 | #[repr(C)] 47 | #[derive(Clone, Debug, PartialEq)] 48 | pub struct InterpolationConfig { 49 | /// The minimum distance the interpolator can travel along an edge before a new point is 50 | /// required. 51 | pub distance_per_point: f32, 52 | /// The number of points to insert at the end of a blank to account for light modulator delay. 53 | pub blank_delay_points: u32, 54 | /// The amount of delay to add based on the angle of the corner in radians. 55 | pub radians_per_point: f32, 56 | } 57 | 58 | /// Parameters for the frame interpolator. 59 | #[derive(Copy, Clone, Debug, Default, PartialEq)] 60 | pub struct InterpolationConfigBuilder { 61 | pub distance_per_point: Option, 62 | pub blank_delay_points: Option, 63 | pub radians_per_point: Option, 64 | } 65 | 66 | /// For the blank ab: `[a, a.blanked(), b.blanked(), (0..delay).map(|_| b.blanked())]`. 67 | pub const BLANK_MIN_POINTS: u32 = 3; 68 | 69 | impl InterpolationConfig { 70 | /// The default distance the interpolator can travel before a new point is required. 71 | pub const DEFAULT_DISTANCE_PER_POINT: f32 = 0.1; 72 | /// The default number of points inserted for the end of each blank segment. 73 | pub const DEFAULT_BLANK_DELAY_POINTS: u32 = 10; 74 | /// The default radians per point of delay to reduce corner inertia. 75 | pub const DEFAULT_RADIANS_PER_POINT: f32 = 0.6; 76 | 77 | /// Start building a new `InterpolationConfig`. 78 | pub fn start() -> InterpolationConfigBuilder { 79 | InterpolationConfigBuilder::default() 80 | } 81 | } 82 | 83 | impl InterpolationConfigBuilder { 84 | /// The minimum distance the interpolator can travel along an edge before a new point is 85 | /// required. 86 | /// 87 | /// By default, this value is `InterpolationConfig::DEFAULT_DISTANCE_PER_POINT`. 88 | pub fn distance_per_point(mut self, dpp: f32) -> Self { 89 | self.distance_per_point = Some(dpp); 90 | self 91 | } 92 | 93 | /// The number of points to insert at the end of a blank to account for light modulator delay. 94 | /// 95 | /// By default, this value is `InterpolationConfig::DEFAULT_BLANK_DELAY_POINTS`. 96 | pub fn blank_delay_points(mut self, points: u32) -> Self { 97 | self.blank_delay_points = Some(points); 98 | self 99 | } 100 | 101 | /// The amount of delay to add based on the angle of the corner in radians. 102 | /// 103 | /// By default, this value is `InterpolationConfig::DEFAULT_RADIANS_PER_POINT`. 104 | pub fn radians_per_point(mut self, radians: f32) -> Self { 105 | self.radians_per_point = Some(radians); 106 | self 107 | } 108 | 109 | /// Build the `InterpolationConfig`, falling back to defaults where necessary. 110 | pub fn build(self) -> InterpolationConfig { 111 | InterpolationConfig { 112 | distance_per_point: self.distance_per_point 113 | .unwrap_or(InterpolationConfig::DEFAULT_DISTANCE_PER_POINT), 114 | blank_delay_points: self.blank_delay_points 115 | .unwrap_or(InterpolationConfig::DEFAULT_BLANK_DELAY_POINTS), 116 | radians_per_point: self.radians_per_point 117 | .unwrap_or(InterpolationConfig::DEFAULT_RADIANS_PER_POINT), 118 | } 119 | } 120 | } 121 | 122 | impl Default for InterpolationConfig { 123 | fn default() -> Self { 124 | Self::start().build() 125 | } 126 | } 127 | 128 | impl Iterator for Segments 129 | where 130 | I: Iterator, 131 | { 132 | type Item = Segment; 133 | fn next(&mut self) -> Option { 134 | while let Some(end) = self.points.next() { 135 | let start = match self.last_point.replace(end) { 136 | None => continue, 137 | Some(last) => last, 138 | }; 139 | 140 | // Skip duplicates. 141 | let kind = if start.position == end.position { 142 | if !start.is_blank() && !end.is_blank() { 143 | SegmentKind::Lit 144 | } else { 145 | continue; 146 | } 147 | } else if start.is_blank() && end.is_blank() { 148 | SegmentKind::Blank 149 | } else { 150 | SegmentKind::Lit 151 | }; 152 | 153 | return Some(Segment { start, end, kind }); 154 | } 155 | None 156 | } 157 | } 158 | 159 | /// Create an iterator yielding segments from an iterator yielding points. 160 | pub fn points_to_segments(points: I) -> Segments 161 | where 162 | I: IntoIterator, 163 | { 164 | let points = points.into_iter(); 165 | let last_point = None; 166 | Segments { points, last_point } 167 | } 168 | 169 | /// Convert the given laser frame vector segments to a graph of points. 170 | pub fn segments_to_point_graph(segments: I) -> PointGraph 171 | where 172 | I: IntoIterator, 173 | { 174 | // A hashable version of a `Point`, used for removing point duplicates during graph generation. 175 | #[derive(Eq, Hash, PartialEq)] 176 | struct HashPoint { 177 | pos: [i32; 2], 178 | rgb: [u32; 3], 179 | } 180 | 181 | struct Node { 182 | ix: NodeIndex, 183 | weight: u32, 184 | } 185 | 186 | impl From for HashPoint { 187 | fn from(p: Point) -> Self { 188 | let [px, py] = p.position; 189 | let [pr, pg, pb] = p.color; 190 | let x = (px * std::i16::MAX as f32) as i32; 191 | let y = (py * std::i16::MAX as f32) as i32; 192 | let r = (pr * std::u16::MAX as f32) as u32; 193 | let g = (pg * std::u16::MAX as f32) as u32; 194 | let b = (pb * std::u16::MAX as f32) as u32; 195 | let pos = [x, y]; 196 | let rgb = [r, g, b]; 197 | HashPoint { pos, rgb } 198 | } 199 | } 200 | 201 | let mut g = PointGraph::default(); 202 | let mut pt_to_id = HashMap::new(); 203 | 204 | // Build the graph. 205 | for seg in segments { 206 | match seg.kind { 207 | SegmentKind::Blank => (), 208 | SegmentKind::Lit => { 209 | let ha = HashPoint::from(seg.start); 210 | let hb = HashPoint::from(seg.end); 211 | let na = { 212 | let n = pt_to_id.entry(ha).or_insert_with(|| { 213 | let ix = g.add_node(seg.start); 214 | let weight = seg.start.weight; 215 | Node { ix, weight } 216 | }); 217 | n.weight = std::cmp::max(n.weight, seg.start.weight); 218 | n.ix 219 | }; 220 | let nb = { 221 | let n = pt_to_id.entry(hb).or_insert_with(|| { 222 | let ix = g.add_node(seg.end); 223 | let weight = seg.end.weight; 224 | Node { ix, weight } 225 | }); 226 | n.weight = std::cmp::max(n.weight, seg.end.weight); 227 | n.ix 228 | }; 229 | 230 | if na == nb { 231 | continue; 232 | } 233 | 234 | if g.find_edge(na, nb).is_none() { 235 | g.add_edge(na, nb, ()); 236 | } 237 | } 238 | } 239 | } 240 | 241 | g 242 | } 243 | 244 | /// Convert a point graph to a euler graph. 245 | /// 246 | /// This determines the minimum number of blank segments necessary to create a euler circuit 247 | /// from the given point graph. A euler circuit is useful as it represents a graph that can be 248 | /// drawn unicursally (one continuous path that covers all nodes while only traversing each edge 249 | /// once). 250 | pub fn point_graph_to_euler_graph(pg: &PointGraph) -> EulerGraph { 251 | // Find the connected components. 252 | let ccs = petgraph::algo::kosaraju_scc(pg); 253 | 254 | // The indices of the connected components whose nodes all have an even degree. 255 | let euler_components: hashbrown::HashSet<_> = ccs 256 | .iter() 257 | .enumerate() 258 | .filter(|(_, cc)| cc.iter().all(|&n| pg.edges(n).count() % 2 == 0)) 259 | .map(|(i, _)| i) 260 | .collect(); 261 | 262 | // Represents the nodes to be connected for a single component. 263 | struct ToConnect { 264 | // Connection to the previous component. 265 | prev: NodeIndex, 266 | // Consecutive connections within the component. 267 | inner: Vec, 268 | // Connection to the next component. 269 | next: NodeIndex, 270 | } 271 | 272 | // Collect the free nodes from each connected component that are to be connected by blanks. 273 | let mut to_connect = vec![]; 274 | for (i, cc) in ccs.iter().enumerate() { 275 | if euler_components.contains(&i) { 276 | // Take the first point. 277 | let n = cc[0]; 278 | to_connect.push(ToConnect { 279 | prev: n, 280 | inner: vec![], 281 | next: n, 282 | }); 283 | } else { 284 | let v: Vec<_> = cc 285 | .iter() 286 | .filter(|&&n| pg.edges(n).count() % 2 != 0) 287 | .collect(); 288 | 289 | // If there's a single point, connect to itself. 290 | if v.len() == 1 { 291 | let p = *v[0]; 292 | let prev = p; 293 | let inner = vec![]; 294 | let next = p; 295 | to_connect.push(ToConnect { prev, inner, next }); 296 | continue; 297 | 298 | // Otherwise convert to a euler component. 299 | } else { 300 | assert_eq!( 301 | v.len() % 2, 302 | 0, 303 | "expected even number of odd-degree nodes for non-Euler component", 304 | ); 305 | let prev = *v[0]; 306 | let inner = v[1..v.len() - 1].iter().map(|&&n| n).collect(); 307 | let next = *v[v.len() - 1]; 308 | to_connect.push(ToConnect { prev, inner, next }); 309 | } 310 | } 311 | } 312 | 313 | // Convert the `to_connect` Vec containing the nodes to be connected for each connected 314 | // component to a `Vec` containing the pairs of nodes which will be directly connected. 315 | let mut pairs = vec![]; 316 | let mut iter = to_connect.iter().enumerate().peekable(); 317 | while let Some((i, this)) = iter.next() { 318 | for ch in this.inner.chunks(2) { 319 | pairs.push((ch[0], ch[1])); 320 | } 321 | match iter.peek() { 322 | Some((_, next)) => pairs.push((this.next, next.prev)), 323 | None if i > 0 => pairs.push((this.next, to_connect[0].prev)), 324 | None => match euler_components.contains(&0) { 325 | // If there is only one component and it is euler, we are done. 326 | true => (), 327 | // If there is only one non-euler, connect it to itself. 328 | false => pairs.push((this.next, this.prev)), 329 | }, 330 | } 331 | } 332 | 333 | // Turn the graph into a euler graph by adding the blanks. 334 | let mut eg = pg.map(|_n_ix, n| n.clone(), |_e_ix, _| SegmentKind::Lit); 335 | for (na, nb) in pairs { 336 | eg.add_edge(na, nb, SegmentKind::Blank); 337 | } 338 | 339 | eg 340 | } 341 | 342 | /// Given a Euler Graph describing the vector image to be drawn, return the optimal Euler Circuit 343 | /// describing the path over which the laser should travel. 344 | /// 345 | /// This is Hierholzer's Algorithm with the amendment that during traversal of each vertex the edge 346 | /// with the closest angle to a straight line is always chosen. 347 | pub fn euler_graph_to_euler_circuit(eg: &EulerGraph) -> EulerCircuit { 348 | // If there is one or less nodes, there's no place for edges. 349 | if eg.node_count() == 0 || eg.node_count() == 1 { 350 | return vec![]; 351 | } 352 | 353 | // Begin the traversals to build the circuit, starting at `v0`. 354 | let start_n = eg 355 | .node_indices() 356 | .next() 357 | .expect("expected at least two nodes, found none"); 358 | let mut visited: HashSet = HashSet::new(); 359 | let mut visit_order: Vec = vec![]; 360 | loop { 361 | // Find a node in the visit order with untraversed edges, or pick one to begin if we're 362 | // just starting. We will do a traversal from this node. Keep track of where in the 363 | // existing `visit_order` we should merge this new traversal. If there are no nodes with 364 | // untraversed edges, we are done. 365 | let (merge_ix, n) = match visit_order.is_empty() { 366 | true => (0, start_n), 367 | false => { 368 | match visit_order 369 | .iter() 370 | .map(|&e| eg.raw_edges()[e.index()].source()) 371 | .enumerate() 372 | .find(|&(_i, n)| eg.edges(n).any(|e| !visited.contains(&e.id()))) 373 | { 374 | Some(n) => n, 375 | None => break, 376 | } 377 | } 378 | }; 379 | 380 | let traversal = traverse_unvisited(n, eg, &mut visited); 381 | let new_visit_order = visit_order 382 | .iter() 383 | .take(merge_ix) 384 | .cloned() 385 | .chain(traversal) 386 | .chain(visit_order.iter().skip(merge_ix).cloned()) 387 | .collect(); 388 | visit_order = new_visit_order; 389 | } 390 | 391 | visit_order 392 | } 393 | 394 | // A traversal through unvisited edges of the graph starting from `n`. 395 | // 396 | // Traversal ends when `n` is reached again. 397 | // 398 | // The returned `Vec` contains the index of each edge traversed. 399 | fn traverse_unvisited( 400 | start: NodeIndex, 401 | eg: &EulerGraph, 402 | visited: &mut HashSet, 403 | ) -> Vec { 404 | let mut n = start; 405 | let mut traversal: Vec = vec![]; 406 | loop { 407 | // Find the straightest edge that hasn't yet been traversed. 408 | let e_ref = { 409 | let mut untraversed_edges = eg.edges(n).filter(|e_ref| !visited.contains(&e_ref.id())); 410 | 411 | let init_e_ref = untraversed_edges 412 | .next() 413 | .expect("expected a strongly connected euler graph"); 414 | 415 | match traversal 416 | .last() 417 | .map(|e| eg.raw_edges()[e.index()].source()) 418 | .map(|n| eg[n].position) 419 | { 420 | // If this is the first edge in the traversal, use the first ref. 421 | None => init_e_ref, 422 | 423 | // Retrieve the three positions used to determine the angle. 424 | Some(prev_source_p) => { 425 | let source_p = eg[init_e_ref.source()].position; 426 | let target_p = eg[init_e_ref.target()].position; 427 | let init_dist = straight_angle_variance(prev_source_p, source_p, target_p); 428 | let init = (init_e_ref, init_dist); 429 | let (e_ref, _) = untraversed_edges.fold(init, |best, e_ref| { 430 | let (_, best_dist) = best; 431 | let target_p = eg[e_ref.target()].position; 432 | let dist = straight_angle_variance(prev_source_p, source_p, target_p); 433 | if dist < best_dist { 434 | (e_ref, dist) 435 | } else { 436 | best 437 | } 438 | }); 439 | e_ref 440 | } 441 | } 442 | }; 443 | 444 | // Add the edge into our visitation record. 445 | let e = e_ref.id(); 446 | n = e_ref.target(); 447 | visited.insert(e); 448 | traversal.push(e); 449 | 450 | // If this edge brings us back to the start, we have finished this traversal. 451 | if e_ref.target() == start { 452 | break; 453 | } 454 | } 455 | 456 | traversal 457 | } 458 | 459 | // Given an angle described by points a -> b -> c, return the variance from a straight angle in 460 | // radians. 461 | fn straight_angle_variance([ax, ay]: Position, [bx, by]: Position, [cx, cy]: Position) -> f32 { 462 | let [ux, uy] = [bx - ax, by - ay]; 463 | let [vx, vy] = [cx - bx, cy - by]; 464 | let ur = uy.atan2(ux); 465 | let vr = vy.atan2(vx); 466 | let diff_rad = vr - ur; 467 | 468 | // Convert the radians to the angular distance. 469 | fn angular_dist(rad: f32) -> f32 { 470 | let rad = rad.abs(); 471 | if rad > std::f32::consts::PI { 472 | -rad + std::f32::consts::PI * 2.0 473 | } else { 474 | rad 475 | } 476 | } 477 | 478 | angular_dist(diff_rad) 479 | } 480 | 481 | fn distance_squared(a: Position, b: Position) -> f32 { 482 | let [ax, ay] = a; 483 | let [bx, by] = b; 484 | let [abx, aby] = [bx - ax, by - ay]; 485 | abx * abx + aby * aby 486 | } 487 | 488 | /// The number of points used per blank segment given the `blank_delay_points` from a config. 489 | pub fn blank_segment_point_count(a_weight: u32, blank_delay_points: u32) -> u32 { 490 | a_weight + BLANK_MIN_POINTS + blank_delay_points 491 | } 492 | 493 | /// Returns the points used to blank between two given lit points *a* and *b*. 494 | pub fn blank_segment_points( 495 | a: Point, 496 | br: RawPoint, 497 | blank_delay_points: u32, 498 | ) -> impl Iterator { 499 | let ar = a.to_raw(); 500 | Some(ar) 501 | .into_iter() 502 | .chain(a.to_raw_weighted()) 503 | .chain(Some(ar.blanked())) 504 | .chain(Some(br.blanked())) 505 | .chain((0..blank_delay_points).map(move |_| br.blanked())) 506 | } 507 | 508 | /// The number of points added at a lit corner given its angle and angular delay rate. 509 | pub fn corner_point_count(rad: f32, corner_delay_radians_per_point: f32) -> u32 { 510 | (rad / corner_delay_radians_per_point) as _ 511 | } 512 | 513 | /// The minimum points for traversing a lit segment (not including end corner delays). 514 | pub fn distance_min_point_count(dist: f32, min_distance_per_point: f32) -> u32 { 515 | // There must be at least one point at the beginning of the line. 516 | const MIN_COUNT: u32 = 1; 517 | MIN_COUNT + (dist * min_distance_per_point) as u32 518 | } 519 | 520 | /// The minimum number of points used for a lit segment of the given distance and end angle. 521 | /// 522 | /// `a_weight` refers to the weight of the point at the beginning of the segment. 523 | pub fn lit_segment_min_point_count( 524 | distance: f32, 525 | end_corner_radians: f32, 526 | distance_per_point: f32, 527 | radians_per_point: f32, 528 | a_weight: u32, 529 | ) -> u32 { 530 | a_weight 531 | + corner_point_count(end_corner_radians, radians_per_point) 532 | + distance_min_point_count(distance, distance_per_point) 533 | } 534 | 535 | /// Returns the points that make up a lit segment between *a* and *b* including delay for the end 536 | /// corner. 537 | /// 538 | /// `excess_points` are distributed across the distance point count. This is used to allow the 539 | /// interpolation process to evenly distribute left-over points across a frame. 540 | pub fn lit_segment_points( 541 | a: Point, 542 | br: RawPoint, 543 | corner_point_count: u32, 544 | distance_min_point_count: u32, 545 | excess_points: u32, 546 | ) -> impl Iterator { 547 | let dist_point_count = distance_min_point_count + excess_points; 548 | let weight_points = a.to_raw_weighted(); 549 | let ar = a.to_raw(); 550 | let dist_points = (0..dist_point_count).map(move |i| { 551 | let lerp_amt = i as f32 / dist_point_count as f32; 552 | ar.lerp(&br, lerp_amt) 553 | }); 554 | let corner_points = (0..corner_point_count).map(move |_| br); 555 | weight_points.chain(dist_points).chain(corner_points) 556 | } 557 | 558 | /// Interpolate the given `EulerCircuit` with the given configuration in order to produce a path 559 | /// ready to be submitted to the DAC. 560 | /// 561 | /// The interpolation process will attempt to generate `target_points` number of points along the 562 | /// circuit, but may generate *more* points in the user's `InterpolationConfig` indicates that more 563 | /// are required for interpolating the specified circuit. 564 | /// 565 | /// Performs the following steps: 566 | /// 567 | /// 1. Determine the minimum number of required points: 568 | /// - 1 for each edge plus the 1 for the end. 569 | /// - The number of points required for each edge. 570 | /// - For lit edges: 571 | /// - The distance of each edge accounting for minimum points per distance. 572 | /// - The angular distance to the following lit edge (none if blank). 573 | /// - For blank edges: 574 | /// - The specified blank delay. 575 | /// 2. If the total is greater than `target_points`, we're done. If not, goto 3. 576 | /// 3. Determine a weight per lit edge based on the distance of each edge. 577 | /// 4. Distribute the remaining points between each lit edge distance based on their weights. 578 | /// 579 | /// **Panic!**s if the given graph is not actually a `EulerCircuit`. 580 | pub fn interpolate_euler_circuit( 581 | ec: &EulerCircuit, 582 | eg: &EulerGraph, 583 | target_points: u32, 584 | conf: &InterpolationConfig, 585 | ) -> Vec { 586 | // Capture a profile of each edge to assist with interpolation. 587 | #[derive(Debug)] 588 | struct EdgeProfile { 589 | a_weight: u32, 590 | kind: EdgeProfileKind, 591 | } 592 | 593 | #[derive(Debug)] 594 | enum EdgeProfileKind { 595 | Blank, 596 | Lit { 597 | distance: f32, 598 | end_corner: f32, 599 | } 600 | } 601 | 602 | impl EdgeProfile { 603 | // Create an `EdgeProfile` for the edge at the given index. 604 | fn from_index(ix: usize, ec: &EulerCircuit, eg: &EulerGraph) -> Self { 605 | let e = ec[ix]; 606 | let e_ref = &eg.raw_edges()[e.index()]; 607 | let a = eg[e_ref.source()]; 608 | let a_weight = a.weight; 609 | let kind = match e_ref.weight { 610 | SegmentKind::Blank => EdgeProfileKind::Blank, 611 | SegmentKind::Lit => { 612 | let a_pos = a.position; 613 | let b_pos = eg[e_ref.target()].position; 614 | let distance = distance_squared(a_pos, b_pos).sqrt(); 615 | let next_ix = (ix + 1) % ec.len(); 616 | let e_ref = &eg.raw_edges()[ec[next_ix].index()]; 617 | let c_pos = eg[e_ref.target()].position; 618 | let end_corner = straight_angle_variance(a_pos, b_pos, c_pos); 619 | EdgeProfileKind::Lit { distance, end_corner } 620 | } 621 | }; 622 | EdgeProfile { a_weight, kind } 623 | } 624 | 625 | fn is_lit(&self) -> bool { 626 | match self.kind { 627 | EdgeProfileKind::Lit { .. } => true, 628 | EdgeProfileKind::Blank => false, 629 | } 630 | } 631 | 632 | // The lit distance covered by this edge. 633 | fn lit_distance(&self) -> f32 { 634 | match self.kind { 635 | EdgeProfileKind::Lit { distance, .. } => distance, 636 | _ => 0.0, 637 | } 638 | } 639 | 640 | // The minimum number of points required to draw the edge. 641 | fn min_points(&self, conf: &InterpolationConfig) -> u32 { 642 | match self.kind { 643 | EdgeProfileKind::Blank => { 644 | blank_segment_point_count(self.a_weight, conf.blank_delay_points) 645 | } 646 | EdgeProfileKind::Lit { distance, end_corner } => { 647 | lit_segment_min_point_count( 648 | distance, 649 | end_corner, 650 | conf.distance_per_point, 651 | conf.radians_per_point, 652 | self.a_weight, 653 | ) 654 | } 655 | } 656 | } 657 | 658 | // The points for this edge. 659 | fn points( 660 | &self, 661 | e: EdgeIndex, 662 | eg: &EulerGraph, 663 | conf: &InterpolationConfig, 664 | excess_points: u32, 665 | ) -> Vec { 666 | let e_ref = &eg.raw_edges()[e.index()]; 667 | let a = eg[e_ref.source()]; 668 | let b = eg[e_ref.target()]; 669 | match self.kind { 670 | EdgeProfileKind::Blank => { 671 | blank_segment_points(a, b.to_raw(), conf.blank_delay_points) 672 | .collect() 673 | } 674 | EdgeProfileKind::Lit { end_corner, distance } => { 675 | let dist_point_count = 676 | distance_min_point_count(distance, conf.distance_per_point); 677 | let corner_point_count = 678 | corner_point_count(end_corner, conf.radians_per_point); 679 | let br = b.to_raw(); 680 | lit_segment_points(a, br, corner_point_count, dist_point_count, excess_points) 681 | .collect() 682 | } 683 | } 684 | } 685 | } 686 | 687 | // If the circuit is empty, so is our path. 688 | if ec.is_empty() || target_points == 0 { 689 | return vec![]; 690 | } 691 | 692 | // Create a profile of each edge containing useful information for interpolation. 693 | let edge_profiles = (0..ec.len()) 694 | .map(|ix| EdgeProfile::from_index(ix, ec, eg)) 695 | .collect::>(); 696 | 697 | // TODO: If the circuit doesn't contain any lit edges, what should we do?. 698 | if !edge_profiles.iter().any(|ep| ep.is_lit()) { 699 | return vec![]; 700 | } 701 | // The minimum number of points required to display the image. 702 | let min_points = edge_profiles.iter() 703 | .map(|ep| ep.min_points(conf)) 704 | .fold(0, |acc, n| acc + n); 705 | 706 | // The target number of points not counting the last to be added at the end. 707 | let target_points_minus_last = target_points - 1; 708 | 709 | // The excess points distributed across all edges. 710 | let edge_excess_point_counts = if min_points < target_points_minus_last { 711 | // A multiplier for determining excess points. This should be distributed across distance. 712 | let excess_points = target_points_minus_last - min_points; 713 | // The lit distance covered by each edge. 714 | let edge_lit_dists = edge_profiles.iter() 715 | .map(|ep| (ep.is_lit(), ep.lit_distance())) 716 | .collect::>(); 717 | // The total lit distance covered by the traversal. 718 | let total_lit_dist = edge_lit_dists.iter().fold(0.0, |acc, &(_, d)| acc + d); 719 | // Determine the weights for each edge based on distance. 720 | let edge_weights: Vec<(bool, f32)> = match total_lit_dist <= std::f32::EPSILON { 721 | true => { 722 | // If there was no total distance, distribute evenly. 723 | let n_lit_edges = edge_lit_dists.iter().filter(|&&(b, _)| b).count(); 724 | edge_lit_dists.iter() 725 | .map(|&(is_lit, _)| (is_lit, 1.0 / n_lit_edges as f32)) 726 | .collect() 727 | }, 728 | false => { 729 | // Otherwise weight by distance. 730 | edge_lit_dists.iter() 731 | .map(|&(is_lit, dist)| (is_lit, dist / total_lit_dist)) 732 | .collect() 733 | } 734 | }; 735 | 736 | // Multiply the weight by the excess points. Track fractional error and distribute. 737 | let mut v = Vec::with_capacity(ec.len()); 738 | let mut err = 0.0; 739 | let mut count = 0; 740 | for (is_lit, w) in edge_weights { 741 | if !is_lit { 742 | v.push(0); 743 | continue; 744 | } 745 | let nf = w * excess_points as f32 + err; 746 | err = nf.fract(); 747 | let n = nf as u32; 748 | count += n; 749 | v.push(n); 750 | } 751 | 752 | // Check for rounding error. 753 | if count == (excess_points - 1) { 754 | // Find first lit edge index. 755 | let (i, _) = edge_profiles.iter() 756 | .enumerate() 757 | .find(|&(_, ep)| ep.is_lit()) 758 | .expect("expected at least one lit edge"); 759 | v[i] += 1; 760 | count += 1; 761 | } 762 | 763 | // Sanity check that rounding errors have been handled. 764 | debug_assert_eq!(count, excess_points); 765 | 766 | v 767 | } else { 768 | vec![0; ec.len()] 769 | }; 770 | 771 | // Collect all points. 772 | let total_points = std::cmp::max(min_points, target_points); 773 | let mut points = Vec::with_capacity(total_points as usize); 774 | for elem in ec.iter().zip(&edge_profiles).zip(&edge_excess_point_counts) { 775 | let ((&ix, ep), &excess) = elem; 776 | points.extend(ep.points(ix, eg, conf, excess)); 777 | } 778 | 779 | // Push the last point. 780 | let last_point = eg[eg.raw_edges()[ec.last().unwrap().index()].target()]; 781 | points.push(last_point.to_raw()); 782 | 783 | // Sanity check that we generated at least `target_points`. 784 | debug_assert!(points.len() >= target_points as usize); 785 | 786 | points 787 | } 788 | 789 | #[cfg(test)] 790 | mod test { 791 | use crate::point::{Point, Position}; 792 | use hashbrown::HashSet; 793 | use super::{euler_graph_to_euler_circuit, point_graph_to_euler_graph, points_to_segments, 794 | segments_to_point_graph}; 795 | use super::{EulerGraph, PointGraph, SegmentKind}; 796 | 797 | fn graph_eq( 798 | a: &petgraph::Graph, 799 | b: &petgraph::Graph, 800 | ) -> bool 801 | where 802 | N: PartialEq, 803 | E: PartialEq, 804 | Ty: petgraph::EdgeType, 805 | Ix: petgraph::graph::IndexType + PartialEq, 806 | { 807 | let a_ns = a.raw_nodes().iter().map(|n| &n.weight); 808 | let b_ns = b.raw_nodes().iter().map(|n| &n.weight); 809 | let a_es = a.raw_edges().iter().map(|e| (e.source(), e.target(), &e.weight)); 810 | let b_es = b.raw_edges().iter().map(|e| (e.source(), e.target(), &e.weight)); 811 | a_ns.eq(b_ns) && a_es.eq(b_es) 812 | } 813 | 814 | fn is_euler_graph(g: &petgraph::Graph) -> bool 815 | where 816 | Ty: petgraph::EdgeType, 817 | Ix: petgraph::graph::IndexType, 818 | { 819 | let even_degree = g.node_indices().all(|n| g.edges(n).count() % 2 == 0); 820 | let strongly_connected = petgraph::algo::kosaraju_scc(g).len() == 1; 821 | even_degree && strongly_connected 822 | } 823 | 824 | fn white_pt(position: Position) -> Point { 825 | Point { 826 | position, 827 | color: [1.0; 3], 828 | weight: 0, 829 | } 830 | } 831 | 832 | fn blank_pt(position: Position) -> Point { 833 | Point { 834 | position, 835 | color: [0.0; 3], 836 | weight: 0, 837 | } 838 | } 839 | 840 | fn square_pts() -> [Point; 5] { 841 | let a = white_pt([-1.0, -1.0]); 842 | let b = white_pt([-1.0, 1.0]); 843 | let c = white_pt([1.0, 1.0]); 844 | let d = white_pt([1.0, -1.0]); 845 | [a, b, c, d, a] 846 | } 847 | 848 | fn two_vertical_lines_pts() -> [Point; 8] { 849 | let a = [-1.0, -1.0]; 850 | let b = [-1.0, 1.0]; 851 | let c = [1.0, -1.0]; 852 | let d = [1.0, 1.0]; 853 | [ 854 | white_pt(a), 855 | white_pt(b), 856 | blank_pt(b), 857 | blank_pt(c), 858 | white_pt(c), 859 | white_pt(d), 860 | blank_pt(d), 861 | blank_pt(a), 862 | ] 863 | } 864 | 865 | #[test] 866 | fn test_points_to_point_graph_no_blanks() { 867 | let pts = square_pts(); 868 | let segs = points_to_segments(pts.iter().cloned()); 869 | let pg = segments_to_point_graph(segs); 870 | 871 | let mut expected = PointGraph::default(); 872 | let na = expected.add_node(pts[0]); 873 | let nb = expected.add_node(pts[1]); 874 | let nc = expected.add_node(pts[2]); 875 | let nd = expected.add_node(pts[3]); 876 | expected.add_edge(na, nb, ()); 877 | expected.add_edge(nb, nc, ()); 878 | expected.add_edge(nc, nd, ()); 879 | expected.add_edge(nd, na, ()); 880 | 881 | assert!(graph_eq(&pg, &expected)); 882 | } 883 | 884 | #[test] 885 | fn test_points_to_point_graph_with_blanks() { 886 | let pts = two_vertical_lines_pts(); 887 | let segs = points_to_segments(pts.iter().cloned()); 888 | let pg = segments_to_point_graph(segs); 889 | 890 | let mut expected = PointGraph::default(); 891 | let na = expected.add_node(pts[0]); 892 | let nb = expected.add_node(pts[1]); 893 | let nc = expected.add_node(pts[4]); 894 | let nd = expected.add_node(pts[5]); 895 | expected.add_edge(na, nb, ()); 896 | expected.add_edge(nc, nd, ()); 897 | 898 | assert!(graph_eq(&pg, &expected)); 899 | } 900 | 901 | #[test] 902 | fn test_point_graph_to_euler_graph_no_blanks() { 903 | let pts = square_pts(); 904 | let segs = points_to_segments(pts.iter().cloned()); 905 | let pg = segments_to_point_graph(segs); 906 | let eg = point_graph_to_euler_graph(&pg); 907 | 908 | let mut expected = EulerGraph::default(); 909 | let na = expected.add_node(pts[0]); 910 | let nb = expected.add_node(pts[1]); 911 | let nc = expected.add_node(pts[2]); 912 | let nd = expected.add_node(pts[3]); 913 | expected.add_edge(na, nb, SegmentKind::Lit); 914 | expected.add_edge(nb, nc, SegmentKind::Lit); 915 | expected.add_edge(nc, nd, SegmentKind::Lit); 916 | expected.add_edge(nd, na, SegmentKind::Lit); 917 | 918 | assert!(graph_eq(&eg, &expected)); 919 | } 920 | 921 | #[test] 922 | fn test_point_graph_to_euler_graph_with_blanks() { 923 | let pts = two_vertical_lines_pts(); 924 | let segs = points_to_segments(pts.iter().cloned()); 925 | let pg = segments_to_point_graph(segs); 926 | let eg = point_graph_to_euler_graph(&pg); 927 | 928 | assert!(is_euler_graph(&eg)); 929 | 930 | let pg_ns: Vec<_> = pg.raw_nodes().iter().map(|n| n.weight).collect(); 931 | let eg_ns: Vec<_> = eg.raw_nodes().iter().map(|n| n.weight).collect(); 932 | assert_eq!(pg_ns, eg_ns); 933 | 934 | assert_eq!(eg.raw_edges().iter().filter(|e| e.weight == SegmentKind::Blank).count(), 2); 935 | assert_eq!(eg.raw_edges().iter().filter(|e| e.weight == SegmentKind::Lit).count(), 2); 936 | } 937 | 938 | #[test] 939 | fn test_euler_graph_to_euler_circuit_no_blanks() { 940 | let pts = square_pts(); 941 | let segs = points_to_segments(pts.iter().cloned()); 942 | let pg = segments_to_point_graph(segs); 943 | let eg = point_graph_to_euler_graph(&pg); 944 | let ec = euler_graph_to_euler_circuit(&eg); 945 | 946 | let mut ns = eg.node_indices(); 947 | let na = ns.next().unwrap(); 948 | let nb = ns.next().unwrap(); 949 | let nc = ns.next().unwrap(); 950 | let nd = ns.next().unwrap(); 951 | 952 | let expected = vec![ 953 | eg.find_edge(na, nb).unwrap(), 954 | eg.find_edge(nb, nc).unwrap(), 955 | eg.find_edge(nc, nd).unwrap(), 956 | eg.find_edge(nd, na).unwrap(), 957 | ]; 958 | 959 | assert_eq!(ec, expected); 960 | } 961 | 962 | #[test] 963 | fn test_euler_graph_to_euler_circuit_with_blanks() { 964 | let pts = two_vertical_lines_pts(); 965 | let segs = points_to_segments(pts.iter().cloned()); 966 | let pg = segments_to_point_graph(segs); 967 | let eg = point_graph_to_euler_graph(&pg); 968 | let ec = euler_graph_to_euler_circuit(&eg); 969 | 970 | assert_eq!(ec.len(), eg.edge_count()); 971 | 972 | let mut visited = HashSet::new(); 973 | let mut walk = ec.iter().cycle().map(|&e| (e, &eg.raw_edges()[e.index()])); 974 | while visited.len() < 4 { 975 | let (e_id, _) = walk.next().unwrap(); 976 | assert!(visited.insert(e_id)); 977 | } 978 | } 979 | 980 | #[test] 981 | fn test_euler_circuit_duplicate_points() { 982 | let pts = [white_pt([0., 0.]), white_pt([0., 1.]), white_pt([0., 1.])]; 983 | let segs = points_to_segments(pts.iter().cloned()); 984 | let pg = segments_to_point_graph(segs); 985 | let eg = point_graph_to_euler_graph(&dbg!(pg)); 986 | let _ = euler_graph_to_euler_circuit(&dbg!(eg)); 987 | } 988 | 989 | #[test] 990 | fn test_single_point() { 991 | let pts = [white_pt([0., 0.]), white_pt([0., 0.])]; 992 | let segs = points_to_segments(pts.iter().cloned()); 993 | let pg = segments_to_point_graph(segs); 994 | let eg = point_graph_to_euler_graph(&dbg!(pg)); 995 | let ec = euler_graph_to_euler_circuit(&dbg!(eg)); 996 | assert_eq!(ec.len(), 0); 997 | } 998 | } 999 | --------------------------------------------------------------------------------