Compare commits

...

3 commits
v0.1.5 ... main

16 changed files with 352 additions and 152 deletions

126
Cargo.lock generated
View file

@ -1,17 +1,16 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "ELApse" name = "ELApse"
version = "0.1.5" version = "0.1.5"
dependencies = [ dependencies = [
"clap", "clap",
"ftdi-embedded-hal",
"image", "image",
"piston",
"piston2d-graphics 0.45.0",
"piston_window", "piston_window",
"rand", "rgb",
"unreal_asset", "unreal_asset",
"url", "url",
] ]
@ -541,6 +540,32 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-hal-nb"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
dependencies = [
"embedded-hal 1.0.0",
"nb 1.1.0",
]
[[package]] [[package]]
name = "enum_dispatch" name = "enum_dispatch"
version = "0.3.13" version = "0.3.13"
@ -633,6 +658,29 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "ftdi-embedded-hal"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e328c2738c104e2649586bda4baabf376825db50014eb0d41bee74fb6064deb"
dependencies = [
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-nb",
"ftdi-mpsse",
"libftd2xx",
"nb 1.1.0",
]
[[package]]
name = "ftdi-mpsse"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7cfcda69930a8d2fdcdd7ffb9234fe4c79a8c73934ed4904327d77bfb5078a"
dependencies = [
"static_assertions",
]
[[package]] [[package]]
name = "funty" name = "funty"
version = "2.0.0" version = "2.0.0"
@ -976,6 +1024,28 @@ version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libftd2xx"
version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f20d68b3138aaabb97edc3db8ed8f418d60f8abd2a7d8220d562906fb38ff08e"
dependencies = [
"ftdi-mpsse",
"libftd2xx-ffi",
"log",
"paste",
"static_assertions",
]
[[package]]
name = "libftd2xx-ffi"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40bed7f53ea45282e0e4f1361d1a8094e62abe0ccfd9a6dbf7e3db932b2789ce"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
version = "0.4.7" version = "0.4.7"
@ -1119,6 +1189,21 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce7b49e1e6d8aa67232ef1c4c936c0af58756eb2db6f65c40bacb39035e7f42" checksum = "fce7b49e1e6d8aa67232ef1c4c936c0af58756eb2db6f65c40bacb39035e7f42"
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.7.0" version = "0.7.0"
@ -1433,7 +1518,7 @@ dependencies = [
"gfx", "gfx",
"piston-gfx_texture", "piston-gfx_texture",
"piston-shaders_graphics2d", "piston-shaders_graphics2d",
"piston2d-graphics 0.44.0", "piston2d-graphics",
"shader_version", "shader_version",
] ]
@ -1452,19 +1537,6 @@ dependencies = [
"vecmath", "vecmath",
] ]
[[package]]
name = "piston2d-graphics"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efa81d40c7b656d270fdbfeef8c531cc04493a001937f5a7d0c5cdb3db2b1469"
dependencies = [
"interpolation",
"piston-texture",
"piston-viewport",
"read_color",
"vecmath",
]
[[package]] [[package]]
name = "piston_window" name = "piston_window"
version = "0.132.0" version = "0.132.0"
@ -1476,7 +1548,7 @@ dependencies = [
"piston", "piston",
"piston-texture", "piston-texture",
"piston2d-gfx_graphics", "piston2d-gfx_graphics",
"piston2d-graphics 0.44.0", "piston2d-graphics",
"pistoncore-glutin_window", "pistoncore-glutin_window",
"shader_version", "shader_version",
] ]
@ -1786,9 +1858,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]] [[package]]
name = "rgb" name = "rgb"
version = "0.8.50" version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
] ]
@ -1954,6 +2026,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strict-num" name = "strict-num"
version = "0.1.1" version = "0.1.1"
@ -2264,6 +2342,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View file

@ -9,14 +9,16 @@ readme = "README.md"
[dependencies] [dependencies]
clap = { version = "4.5.17", features = ["derive"] } clap = { version = "4.5.17", features = ["derive"] }
ftdi-embedded-hal = { version = "0.22.0", features = ["libftd2xx"] }
image = "0.25.2" image = "0.25.2"
piston = "1.0.0"
piston2d-graphics = "0.45.0"
piston_window = "0.132.0" piston_window = "0.132.0"
rand = "0.8.5" rgb = "0.8.52"
unreal_asset = "0.1.16" unreal_asset = "0.1.16"
url = "2.5.2" url = "2.5.2"
[profile.dev]
opt-level = 3
# The profile that 'cargo dist' will build with # The profile that 'cargo dist' will build with
[profile.dist] [profile.dist]
inherits = "release" inherits = "release"

View file

@ -1 +1,35 @@
# ELApse # ELApse
ELApse is a tool used for processing and displaying the .ELA format from a dead rhythm game
## Downloading
You can either download the latest release from the [releases](https://github.com/BlackDragon-B/ELApse/releases) or build it yourself using cargo
## Usage
```bash
ELApse --input <INPUT> --output <OUTPUT>
Options:
-i, --input <INPUT> Input to use
-o, --output <OUTPUT> Output to use
-h, --help Print help
-V, --version Print version
```
## Features
### Input
- [WARLS](https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control) (--input udp://0.0.0.0:21324)
- Images (--input example.png)
- .uasset/.uexp (--input ELA_BP_Title_in_00.uasset)
- File containing raw rgb8 (--input file)
- Raw rgb8 from stdin (--input stdin)
### Output
- [WARLS](https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control) (--output udp://0.0.0.0:21324)
- Built-in renderer built with [Piston](https://www.piston.rs/) (--output piston)
## Contributing
Feel free to open an issue if you think this tool is missing something critical. Pull requests are always welcome.
## License
ELApse is licensed under the [GPLv3](https://choosealicense.com/licenses/gpl-3.0/) license.

View file

@ -1,2 +0,0 @@
[toolchain]
channel = "nightly"

View file

@ -1,61 +0,0 @@
#![allow(non_camel_case_types)]
use std::{io::Read, net::UdpSocket, sync::mpsc::{self, Receiver, Sender}, thread, time::{Duration, Instant}};
use crate::{ parser, fileparser };
pub fn stdin(tx: Sender<Vec<[f32; 4]>>) {
let (stdintx, stdinrx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = mpsc::channel();
thread::spawn( move || {
let mut buf = [0; 1500];
let mut e = std::io::stdin().lock();
loop {
let amt = e.read(&mut buf).unwrap();
if buf[..amt].to_vec().len() > 0 {
let _ = stdintx.send(buf[..amt].to_vec());
}
}
});
thread::spawn( move || {
loop {
// let amt = e.read(&mut buf).unwrap();
match stdinrx.recv_timeout(Duration::from_millis(500)) {
Ok(d) => {
let r: Vec<[f32; 4]> = parser::parseleds(d, parser::ParserOptions::default().mirror(true).double(true).offset(15));
let _ = tx.send(r);
},
Err(_) => {
std::process::exit(0);
},
};
}
});
}
pub fn realtimeudp(tx: Sender<Vec<[f32; 4]>>,bindaddr: String) {
thread::spawn( move || {
let socket = UdpSocket::bind(bindaddr).unwrap();
let mut buf = [0; 1500];
loop {
let (amt, _src) = socket.recv_from(&mut buf).unwrap();
let r = parser::parseleds(buf[..amt].to_vec(), parser::ParserOptions::default().stripheader(2));
let _ = tx.send(r);
}
});
}
pub fn file(tx: Sender<Vec<[f32; 4]>>, path: String) {
thread::spawn( move || {
let Ok(rawbytes) = fileparser::parse(&path) else {
println!("File not found");
std::process::exit(1)
};
for frame in rawbytes.chunks(720) {
let st = Instant::now();
let r = parser::parseleds(frame.to_vec(), parser::ParserOptions::default().mirror(true).double(true).offset(15));
let _ = tx.send(r);
if Duration::from_secs_f32(1.0/60.0) > st.elapsed() {
thread::sleep(Duration::from_secs_f32(1.0/60.0)-st.elapsed());
};
}
std::process::exit(0);
});
}

29
src/input/file.rs Normal file
View file

@ -0,0 +1,29 @@
#![allow(non_camel_case_types)]
use std::{sync::mpsc::Sender, thread, time::{Duration, Instant}};
use crate::{fileparser, input::Input, parser};
impl Input {
pub fn file(tx: Sender<Vec<[f32; 4]>>, path: String) {
thread::spawn(move || {
let Ok(rawbytes) = fileparser::parse(&path) else {
println!("File not found");
std::process::exit(1)
};
for frame in rawbytes.chunks(720) {
let st = Instant::now();
let r = parser::parseleds(
frame.to_vec(),
parser::ParserOptions::default()
.mirror(true)
.double(true)
.offset(15),
);
let _ = tx.send(r);
if Duration::from_secs_f32(1.0 / 60.0) > st.elapsed() {
thread::sleep(Duration::from_secs_f32(1.0 / 60.0) - st.elapsed());
};
}
std::process::exit(0);
});
}
}

5
src/input/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod file;
mod realtimeudp;
mod stdin;
pub struct Input;

20
src/input/realtimeudp.rs Normal file
View file

@ -0,0 +1,20 @@
#![allow(non_camel_case_types)]
use std::{net::UdpSocket, sync::mpsc::Sender, thread};
use crate::{input::Input, parser};
impl Input {
pub fn realtimeudp(tx: Sender<Vec<[f32; 4]>>, bindaddr: String) {
thread::spawn(move || {
let socket = UdpSocket::bind(bindaddr).unwrap();
let mut buf = [0; 1500];
loop {
let (amt, _src) = socket.recv_from(&mut buf).unwrap();
let r = parser::parseleds(
buf[..amt].to_vec(),
parser::ParserOptions::default().stripheader(2),
);
let _ = tx.send(r);
}
});
}
}

39
src/input/stdin.rs Normal file
View file

@ -0,0 +1,39 @@
#![allow(non_camel_case_types)]
use std::{io::Read, sync::mpsc::{self, Receiver, Sender}, thread, time::Duration};
use crate::{input::Input, parser};
impl Input {
pub fn stdin(tx: Sender<Vec<[f32; 4]>>) {
let (stdintx, stdinrx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = mpsc::channel();
thread::spawn(move || {
let mut buf = [0; 1500];
let mut e = std::io::stdin().lock();
loop {
let amt = e.read(&mut buf).unwrap();
if buf[..amt].to_vec().len() > 0 {
let _ = stdintx.send(buf[..amt].to_vec());
}
}
});
thread::spawn(move || {
loop {
// let amt = e.read(&mut buf).unwrap();
match stdinrx.recv_timeout(Duration::from_millis(500)) {
Ok(d) => {
let r: Vec<[f32; 4]> = parser::parseleds(
d,
parser::ParserOptions::default()
.mirror(true)
.double(true)
.offset(15),
);
let _ = tx.send(r);
}
Err(_) => {
std::process::exit(0);
}
};
}
});
}
}

View file

@ -1,4 +1,3 @@
#![feature(array_chunks)]
mod input; mod input;
mod output; mod output;
mod parser; mod parser;
@ -8,6 +7,8 @@ mod fileparser;
use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::mpsc::{self, Receiver, Sender};
use clap::Parser; use clap::Parser;
use url::Url; use url::Url;
use crate::output::Output;
use crate::input::Input;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -25,15 +26,15 @@ fn main() {
let args = Args::parse(); let args = Args::parse();
let (tx, rx): (Sender<Vec<[f32; 4]>>, Receiver<Vec<[f32; 4]>>) = mpsc::channel(); let (tx, rx): (Sender<Vec<[f32; 4]>>, Receiver<Vec<[f32; 4]>>) = mpsc::channel();
match args.input.as_str() { match args.input.as_str() {
"-" | "stdin" => input::stdin(tx), "-" | "stdin" => Input::stdin(tx),
_ => { _ => {
match Url::parse(&args.input) { match Url::parse(&args.input) {
Ok(o) => { Ok(o) => {
match o.scheme() { match o.scheme() {
"udp" => { "udp" => {
input::realtimeudp(tx, utils::get_host(o)); Input::realtimeudp(tx, utils::get_host(o));
}, },
"file" => input::file(tx, o.path().to_owned()), "file" => Input::file(tx, o.path().to_owned()),
_ => { _ => {
println!("Unrecognized uri scheme"); println!("Unrecognized uri scheme");
std::process::exit(1); std::process::exit(1);
@ -41,18 +42,19 @@ fn main() {
} }
}, },
Err(_) => { Err(_) => {
input::file(tx, args.input); Input::file(tx, args.input);
}, },
}; };
} }
} }
match args.output.as_str() { match args.output.as_str() {
"piston" => output::piston(rx), "piston" => Output::piston(rx),
"ftd2xx" => Output::ft2dxx(rx),
_ => { _ => {
match Url::parse(&args.output) { match Url::parse(&args.output) {
Ok(o) => { Ok(o) => {
match o.scheme() { match o.scheme() {
"udp" => output::realtimeudp(rx,utils::get_host(o)), "udp" => Output::realtimeudp(rx,utils::get_host(o)),
_ => { _ => {
println!("Unrecognized uri scheme"); println!("Unrecognized uri scheme");
std::process::exit(1); std::process::exit(1);

View file

@ -1,57 +0,0 @@
#![allow(non_camel_case_types)]
use piston_window::*;
use piston_window::graphics::math::Scalar;
use std::{net::UdpSocket, sync::mpsc::Receiver, time::Duration};
use crate::utils::{compare, offset};
pub fn piston(rx: Receiver<Vec<[f32; 4]>>) {
let mut window: PistonWindow =
WindowSettings::new("ELApse", [800, 800])
.transparent(true)
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
let r = match rx.recv_timeout(Duration::from_millis(66)) {
Ok(e) => e,
Err(_) => vec![[0.02, 0.02, 0.02, 1.0]; 480],
};
let size = window.size();
let wh = compare(size.height, size.width)*0.75;
window.draw_2d(&e, |c, g, _device| {
for e in 1..9 {
for i in 0..60 {
let rwh = wh*(1.0-(e as f64*0.06));
circle_arc(r[i*8+(e-1)], rwh/32.0, Scalar::deg_to_rad(offset((((360/60)*i)) as i16, 360, 90) as f64), Scalar::deg_to_rad(offset((((360/60)*i)+6) as i16, 360, 90) as f64), [(size.width/2.0)-(rwh/2.0), (size.height/2.0)-(rwh/2.0), rwh, rwh], c.transform, g);
}
}
});
}
}
pub fn realtimeudp(rx: Receiver<Vec<[f32; 4]>>, addr: String) {
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
loop {
let leds = rx.recv().unwrap();
let mut packet: Vec<u8> = vec![2, 2];
for led in leds {
packet.push((led[0]*255.0) as u8);
packet.push((led[1]*255.0) as u8);
packet.push((led[2]*255.0) as u8);
}
match socket.send_to(&packet, &addr) {
Ok(_) => (),
Err(err) => {
println!("Error while sending trying to send data: {}", err);
std::process::exit(1);
},
};
}
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum Output {
piston,
realtimeudp,
}

40
src/output/ft2dxx.rs Normal file
View file

@ -0,0 +1,40 @@
#![allow(non_camel_case_types)]
use std::sync::mpsc::Receiver;
use ftdi_embedded_hal::eh0::prelude::_embedded_hal_blocking_spi_Write;
use ftdi_embedded_hal::{self as hal, libftd2xx};
use crate::output::Output;
impl Output {
pub fn ft2dxx(rx: Receiver<Vec<[f32; 4]>>) {
let device = libftd2xx::Ft232h::with_description("Single RS232-HS").unwrap();
println!("Connected to FT232H");
let hal = hal::FtHal::init_freq(device, 3_000_000).unwrap();
let mut spi = hal.spi().unwrap();
// led stuff
loop {
let leds = rx.recv().unwrap();
let mut fdata: Vec<u8> = Vec::new();
for led in leds {
let sled = [led[1], led[0], led[2]]; //Shift colors (be gone when I make a color struct)
for i in 0..3 {
let mut data = (sled[i].clone() * 255.0) as u8;
let patterns = [0b1000_1000, 0b1000_1110, 0b11101000, 0b11101110];
for _ in 0..4 {
let bits = (data & 0b1100_0000) >> 6;
fdata.push(patterns[bits as usize]);
data <<= 2;
}
}
}
let _ = spi.write(&fdata);
// for _ in 0..140 {
// spi.write(std::array::from_ref(&0)).unwrap();
// }
}
}
}

5
src/output/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod ft2dxx;
mod piston;
mod realtimeudp;
pub struct Output;

34
src/output/piston.rs Normal file
View file

@ -0,0 +1,34 @@
#![allow(non_camel_case_types)]
use piston_window::*;
use piston_window::graphics::math::Scalar;
use std::{sync::mpsc::Receiver, time::Duration};
use crate::output::Output;
use crate::utils::{compare, offset};
impl Output {
pub fn piston(rx: Receiver<Vec<[f32; 4]>>) {
let mut window: PistonWindow =
WindowSettings::new("ELApse", [800, 800])
.transparent(true)
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
let r = match rx.recv_timeout(Duration::from_millis(66)) {
Ok(e) => e,
Err(_) => vec![[0.02, 0.02, 0.02, 1.0]; 480],
};
let size = window.size();
let wh = compare(size.height, size.width)*0.75;
window.draw_2d(&e, |c, g, _device| {
for e in 1..9 {
for i in 0..60 {
let rwh = wh*(1.0-(e as f64*0.06));
circle_arc(r[i*8+(e-1)], rwh/32.0, Scalar::deg_to_rad(offset((((360/60)*i)) as i16, 360, 90) as f64), Scalar::deg_to_rad(offset((((360/60)*i)+6) as i16, 360, 90) as f64), [(size.width/2.0)-(rwh/2.0), (size.height/2.0)-(rwh/2.0), rwh, rwh], c.transform, g);
}
}
});
}
}
}

26
src/output/realtimeudp.rs Normal file
View file

@ -0,0 +1,26 @@
#![allow(non_camel_case_types)]
use std::{net::UdpSocket, sync::mpsc::Receiver};
use crate::output::Output;
impl Output {
pub fn realtimeudp(rx: Receiver<Vec<[f32; 4]>>, addr: String) {
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
loop {
let leds = rx.recv().unwrap();
let mut packet: Vec<u8> = vec![2, 2];
for led in leds {
packet.push((led[0] * 255.0) as u8);
packet.push((led[1] * 255.0) as u8);
packet.push((led[2] * 255.0) as u8);
}
match socket.send_to(&packet, &addr) {
Ok(_) => (),
Err(err) => {
println!("Error while sending trying to send data: {}", err);
std::process::exit(1);
}
};
}
}
}

View file

@ -34,7 +34,7 @@ pub fn parseleds(mut data: Vec<u8>, options: ParserOptions) -> Vec<[f32; 4]> {
if data.len() < expectedlen { if data.len() < expectedlen {
data.append(&mut vec![5; expectedlen-out.len()]); data.append(&mut vec![5; expectedlen-out.len()]);
} }
for x in data.array_chunks::<3>() { for x in data.as_chunks::<3>().0.iter() {
out.push([utils::scaleu8tof32(x[0]), utils::scaleu8tof32(x[1]), utils::scaleu8tof32(x[2]), 1.0]); out.push([utils::scaleu8tof32(x[0]), utils::scaleu8tof32(x[1]), utils::scaleu8tof32(x[2]), 1.0]);
if options.double && !options.mirror { if options.double && !options.mirror {
out.push(out.last().unwrap().clone()); out.push(out.last().unwrap().clone());