diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8b46379..741c2a5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,20 +17,20 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: wasm32-unknown-unknown + targets: wasm32-unknown-unknown + components: rustfmt - name: Install alsa dependencies run: | sudo apt-get update sudo apt-get install -y libasound2-dev - name: cargo fmt - run: cargo fmt -- --check + run: cargo fmt --all -- --check - name: cargo check - run: cargo check --verbose + run: cargo check --workspace --all-targets --verbose - name: cargo check (wasm) - run: cargo check --verbose --target wasm32-unknown-unknown + run: cargo check --workspace --all-targets --verbose --target wasm32-unknown-unknown - name: cargo test - run: cargo test --verbose - + run: cargo test --workspace --verbose diff --git a/Cargo.toml b/Cargo.toml index 675887e..86e740b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,41 @@ members = [ "murrelet_gpu", "murrelet_livecode", "murrelet_livecode_macros", + "murrelet_livecode_macros/murrelet_livecode_derive", "murrelet_perform", "murrelet_src_audio", "murrelet_src_midi", "murrelet_src_osc", "murrelet_svg", - "examples/murrelet_example", - "examples/foolish_guillemot", - "tinylivecode" + "tinylivecode", + "murrelet_schema", + "murrelet_schema_derive", + "murrelet_gui", + "murrelet_gui_derive", + "murrelet_gen", + "murrelet_gen_derive", ] resolver = "2" + +[workspace.dependencies] +murrelet = { path = "murrelet", version = "0.1.2" } +murrelet_common = { path = "murrelet_common", version = "0.1.2" } +murrelet_draw = { path = "murrelet_draw", version = "0.1.2", default-features = false } +murrelet_gpu = { path = "murrelet_gpu", version = "0.1.2" } +murrelet_livecode = { path = "murrelet_livecode", version = "0.1.2", default-features = false } +murrelet_livecode_macros = { path = "murrelet_livecode_macros", version = "0.1.2", default-features = false } +murrelet_livecode_derive = { path = "murrelet_livecode_macros/murrelet_livecode_derive", version = "0.1.2", default-features = false } +murrelet_perform = { path = "murrelet_perform", version = "0.1.2", default-features = false } +murrelet_src_audio = { path = "murrelet_src_audio", version = "0.1.2" } +murrelet_src_midi = { path = "murrelet_src_midi", version = "0.1.2" } +murrelet_src_osc = { path = "murrelet_src_osc", version = "0.1.2" } +murrelet_svg = { path = "murrelet_svg", version = "0.1.2" } +tinylivecode = { path = "tinylivecode", version = "0.1.2" } +murrelet_schema = { path = "murrelet_schema", version = "0.1.2" } +murrelet_schema_derive = { path = "murrelet_schema_derive", version = "0.1.2" } +murrelet_gui = { path = "murrelet_gui", version = "0.1.2" } +murrelet_gui_derive = { path = "murrelet_gui_derive", version = "0.1.2" } +murrelet_gen = { path = "murrelet_gen", version = "0.1.2" } +murrelet_gen_derive = { path = "murrelet_gen_derive", version = "0.1.2" } +getrandom = { version = "0.2.17", features = ["js"] } diff --git a/examples/foolish_guillemot/Cargo.toml b/examples/foolish_guillemot/Cargo.toml deleted file mode 100644 index 76ec7fc..0000000 --- a/examples/foolish_guillemot/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -name = "foolish_guillemot" -version = "0.1.2" -edition = "2021" -authors = ["Jessica Stringham"] - - -[profile.release] -lto = true -opt-level = 'z' - -[lib] -crate-type = ["cdylib"] - -[dependencies] -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4.42" -console_error_panic_hook = "0.1" - -glam = "0.28.0" -itertools = "0.10.5" - -serde = { version = "1.0.104", features = ["derive"] } -serde_yaml = "0.9.17" - -murrelet = { version = "0.1.1", path = "../../murrelet/" } -murrelet_common = { version = "0.1.1", path = "../../murrelet_common/" } -murrelet_livecode = { version = "0.1.1", path = "../../murrelet_livecode/" } -murrelet_livecode_macros = { version = "0.1.1", path = "../../murrelet_livecode_macros/" } -murrelet_livecode_derive = { version = "0.1.1", path = "../../murrelet_livecode_macros/murrelet_livecode_derive/" } -murrelet_perform = { version = "0.1.1", path = "../../murrelet_perform/", features = [ - "for_the_web", -] } -murrelet_draw = { version = "0.1.1", path = "../../murrelet_draw/" } -murrelet_svg = { version = "0.1.1", path = "../../murrelet_svg/" } -murrelet_gpu = { version = "0.1.1", path = "../../murrelet_gpu/", features = [ - "no_nannou", -] } - -lerpable = "0.0.2" - -wgpu = { version = "0.20.1", features = ["webgpu", "webgl"] } -anyhow = "1.0.86" - -[dependencies.getrandom] -version = "0.2" -features = ["js"] - -# todo: sort out dev build -[dependencies.web-sys] -version = "0.3" -features = [ - "console", - "Window", - "Document", - "Element", - "HtmlCanvasElement", - "CanvasRenderingContext2d", -] diff --git a/examples/foolish_guillemot/src/draw.rs b/examples/foolish_guillemot/src/draw.rs deleted file mode 100644 index cf80a71..0000000 --- a/examples/foolish_guillemot/src/draw.rs +++ /dev/null @@ -1,156 +0,0 @@ -use glam::*; -use murrelet_common::*; -use murrelet_draw::{ - draw::{CoreSDrawCtx, CoreSDrawCtxUnitCell, MurreletStyle, Sdraw}, - style::{MurreletPath, StyledPath}, -}; -use murrelet_livecode::unitcells::UnitCellContext; -use murrelet_perform::perform::SvgDrawConfig; -use murrelet_svg::svg::{SvgPathCache, SvgPathCacheRef}; - -// from the wasm-rust tutorial, this let's you log messages to the js console -// extern crate web_sys; -// macro_rules! log { -// ( $( $t:tt )* ) => { -// web_sys::console::log_1(&format!( $( $t )* ).into()); -// } -// } - -#[derive(Clone)] -pub struct WebSDrawCtxUnitCell { - ctx: CoreSDrawCtxUnitCell, - sdraw: WebSDrawCtx, -} - -impl WebSDrawCtxUnitCell { - pub fn draw_curve_path(&self, cd: MurreletPath) { - // todo, this is a little clumsy - let mut path = cd; - path = path.transform_with(&self.ctx.unit_cell_transform()); - path = path.transform_with_mat4_after(self.sdraw.transform()); - - self.sdraw.svg_draw.add_styled_path( - "", - StyledPath::new_from_path(path, self.svg_style().clone()), - ); - } - - pub fn clear_context(&self) -> WebSDrawCtx { - self.sdraw.clone() - } - - pub fn with_unit_cell_skew(&self, skew: bool) -> Self { - let mut c = self.clone(); - c.ctx = self.ctx.with_unit_cell_skew(skew); - c - } -} - -impl Sdraw for WebSDrawCtxUnitCell { - fn with_svg_style(&self, svg_style: murrelet_draw::draw::MurreletStyle) -> Self { - let mut sdraw = self.clone(); - sdraw.ctx = self.ctx.with_svg_style(svg_style); - sdraw - } - - fn svg_style(&self) -> murrelet_draw::draw::MurreletStyle { - self.ctx.svg_style() - } - - fn transform(&self) -> Mat4 { - self.ctx.transform() - } - - fn set_transform(&self, m: Mat4) -> Self { - let mut sdraw = self.clone(); - sdraw.ctx = self.ctx.set_transform(m); - sdraw - } - - fn transform_points(&self, face: &F) -> Polyline { - self.ctx.transform_points(face) - } - - fn line_space_multi(&self) -> f32 { - self.ctx.line_space_multi() - } -} - -#[derive(Clone)] -pub struct WebSDrawCtx { - ctx: CoreSDrawCtx, - pub svg_draw: SvgPathCacheRef, -} - -impl WebSDrawCtx { - fn _draw_curve_path(&self, cd_raw: MurreletPath) { - // todo, this is clunky - let cd = cd_raw; - - let path = StyledPath::new_from_path(cd, self.svg_style().clone()); - - self.svg_draw.add_styled_path("", path) - } - - pub fn make_html(&self) -> Vec { - self.svg_draw.make_html() - } - - pub fn add_guides(&self) { - self.svg_draw.add_guides(); - } - - pub fn save_doc(&self) { - self.svg_draw.save_doc(); - } - - pub fn with_detail(&self, detail: &UnitCellContext) -> WebSDrawCtxUnitCell { - let ctx = self.ctx.with_detail(detail); - WebSDrawCtxUnitCell { - ctx, - sdraw: self.clone(), - } - } - - pub fn new(svg_draw_config: &SvgDrawConfig) -> WebSDrawCtx { - let svg_draw = SvgPathCache::svg_draw(svg_draw_config); - - let ctx = CoreSDrawCtx::new( - MurreletStyle::new_white(false, false), - svg_draw_config.frame() as f32, - Mat4::IDENTITY, - ); - - WebSDrawCtx { svg_draw, ctx } - } -} - -impl Sdraw for WebSDrawCtx { - fn with_svg_style(&self, svg_style: MurreletStyle) -> Self { - let mut c = self.clone(); - c.ctx = c.ctx.with_svg_style(svg_style); - c - } - - fn svg_style(&self) -> MurreletStyle { - self.ctx.svg_style() - } - - fn transform(&self) -> Mat4 { - self.ctx.transform() - } - - fn set_transform(&self, m: Mat4) -> Self { - let mut c = self.clone(); - c.ctx = c.ctx.set_transform(m); - c - } - - fn transform_points(&self, face: &F) -> Polyline { - self.ctx.transform_points(face) - } - - fn line_space_multi(&self) -> f32 { - self.ctx.line_space_multi() - } -} diff --git a/examples/foolish_guillemot/src/lib.rs b/examples/foolish_guillemot/src/lib.rs deleted file mode 100644 index 488d4b0..0000000 --- a/examples/foolish_guillemot/src/lib.rs +++ /dev/null @@ -1,211 +0,0 @@ -pub mod draw; - -use draw::{WebSDrawCtx, WebSDrawCtxUnitCell}; -use glam::*; - -use lerpable::Lerpable; -use murrelet::prelude::*; -use murrelet_common::{lerpify_vec2, mat4_from_mat3_transform}; -use murrelet_draw::{ - compass::*, - draw::*, - sequencers::*, - style::{styleconf::*, MurreletPath}, -}; -use murrelet_livecode::types::{AdditionalContextNode, LivecodeError}; -use wasm_bindgen::prelude::*; - -// from the wasm-rust tutorial, this let's you log messages to the js console -// extern crate web_sys; - -// A macro to provide `println!(..)`-style syntax for `console.log` logging. -// macro_rules! log { -// ( $( $t:tt )* ) => { -// web_sys::console::log_1(&format!( $( $t )* ).into()) -// } -// } - -#[derive(Debug, Clone, Default, Livecode, Lerpable)] -struct StyledShape { - shape: MurreletCompass, - style: StyleConf, - #[livecode(serde_default = "false")] - skew: bool, -} -impl StyledShape { - fn draw(&self, draw_ctx: &WebSDrawCtxUnitCell) { - // first do the simple transform - - draw_ctx - .with_unit_cell_skew(self.skew) - .with_style(self.style.clone()) - .draw_curve_path(MurreletPath::curve(self.shape.to_curve_maker())); - } -} - -#[derive(Debug, Clone, Default, Livecode, Lerpable)] -struct SimpleTile(Vec); -impl SimpleTile { - fn draw(&self, draw_ctx: &WebSDrawCtxUnitCell) { - for v in &self.0 { - v.draw(draw_ctx); - } - } -} - -#[derive(Debug, Clone, Livecode, Lerpable)] -struct DrawingConfig { - #[livecode(serde_default = "false")] - debug: bool, - sequencer: Sequencer, - ctx: AdditionalContextNode, - #[livecode(src = "sequencer", ctx = "ctx")] - node: UnitCells, - #[lerpable(func = "lerpify_vec2")] - offset: Vec2, - scale: f32, -} -impl DrawingConfig { - fn draw(&self, draw_ctx: &WebSDrawCtx) { - // todo, this is a little clunky.. and isn't so clunky in my other code hrm - let transform = mat4_from_mat3_transform( - Mat3::from_scale(Vec2::ONE * self.scale) * Mat3::from_translation(self.offset), - ); - - for t in self.node.iter() { - t.node - .draw(&draw_ctx.set_transform(transform).with_detail(&t.detail)) - } - } -} - -// set up livecoder -#[derive(Debug, Clone, Livecode, Lerpable, TopLevelLiveCode)] -struct LiveCodeConf { - app: AppConfig, - drawing: DrawingConfig, -} - -#[wasm_bindgen] -pub async fn new_model(conf: String) -> WasmMurreletModelResult { - MurreletModel::new(conf).await -} - -#[wasm_bindgen] -pub struct MurreletModel { - livecode: LiveCode, -} -#[wasm_bindgen] -impl MurreletModel { - #[wasm_bindgen(constructor)] - pub async fn new(conf: String) -> WasmMurreletModelResult { - // turn this on if you need to debug - // std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - - let livecode_src = LivecodeSrc::new(vec![Box::new(AppInputValues::new(false))]); - - match LiveCode::new_web(conf, livecode_src, &vec![]) { - Ok(livecode) => { - let r = MurreletModel { livecode }; - WasmMurreletModelResult::ok(r) - } - Err(e) => WasmMurreletModelResult::err(e), - } - } - - #[wasm_bindgen] - pub fn update_config(&mut self, conf: String) -> String { - match self.livecode.update_config_to(&conf) { - Ok(_) => "Success!".to_owned(), - Err(e) => e, - } - } - - #[wasm_bindgen] - pub fn update_frame( - &mut self, - frame: u64, - dim_x: f32, - dim_y: f32, - mouse_x: f32, - mouse_y: f32, - click: bool, - ) { - let app_input = - MurreletAppInput::new_no_key(vec2(dim_x, dim_y), vec2(mouse_x, mouse_y), click, frame); - // todo, show an error about this? - self.livecode.update(&app_input, false).ok(); - } - - // useful if you have shaders, this will list what canvases to draw to - #[wasm_bindgen] - pub fn canvas_ids(&self) -> Vec { - Vec::new() - } - - // useful if you have shaders, this should generate the DOM for image textures in the svg - #[wasm_bindgen] - pub fn make_img_defs(&self) -> String { - String::new() - } - - #[wasm_bindgen] - pub fn draw(&self) -> Vec { - let svg_draw_config = self.livecode.svg_save_path(); - let draw_ctx = WebSDrawCtx::new(&svg_draw_config); - - self.livecode.config().drawing.draw(&draw_ctx); - - draw_ctx.make_html() - } - - #[wasm_bindgen] - pub fn fps(&self) -> f32 { - self.livecode.app_config().time.fps - } - - #[wasm_bindgen] - pub fn bg_color(&self) -> String { - self.livecode.app_config().bg_color.to_svg_rgb() - } -} - -// just creating a Result that we can send to javascript -#[wasm_bindgen] -pub struct WasmMurreletModelResult { - m: Option, - err: String, -} - -#[wasm_bindgen] -impl WasmMurreletModelResult { - fn ok(m: MurreletModel) -> WasmMurreletModelResult { - WasmMurreletModelResult { - m: Some(m), - err: String::new(), - } - } - - fn err(err: LivecodeError) -> WasmMurreletModelResult { - WasmMurreletModelResult { - m: None, - err: err.to_string(), - } - } - - #[wasm_bindgen] - pub fn is_err(&self) -> bool { - self.m.is_none() - } - - #[wasm_bindgen] - pub fn err_msg(self) -> String { - self.err - } - - #[wasm_bindgen] - pub fn to_model(self) -> MurreletModel { - // panics if you don't check is error first - self.m.unwrap() - } -} diff --git a/examples/murrelet_example/Cargo.toml b/examples/murrelet_example/Cargo.toml deleted file mode 100644 index 12695e5..0000000 --- a/examples/murrelet_example/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "murrelet_example" -version = "0.1.2" -edition = "2021" -authors = ["Jessica Stringham "] -repository = "https://github.com/jessstringham/murrelet.git" - -[dependencies] -glam = "0.28.0" -itertools = "0.10.5" -serde = { version = "1.0.104", features = ["derive"] } -serde_yaml = "0.9.17" - -murrelet = { version = "0.1.1", path = "../../murrelet/" } -murrelet_livecode = { version = "0.1.1", path = "../../murrelet_livecode/" } -murrelet_livecode_macros = { version = "0.1.1", path = "../../murrelet_livecode_macros/" } -murrelet_common = { version = "0.1.1", path = "../../murrelet_common/" } -murrelet_livecode_derive = { version = "0.1.1", path = "../../murrelet_livecode_macros/murrelet_livecode_derive/" } -murrelet_perform = { version = "0.1.1", path = "../../murrelet_perform/" } -murrelet_draw = { version = "0.1.1", path = "../../murrelet_draw/" } -murrelet_svg = { version = "0.1.1", path = "../../murrelet_svg/" } - -murrelet_src_audio = { version = "0.1.1", path = "../../murrelet_src_audio/" } -murrelet_src_midi = { version = "0.1.1", path = "../../murrelet_src_midi/" } - -lerpable = "0.0.2" -schemars = "0.8.21" \ No newline at end of file diff --git a/examples/murrelet_example/src/configs/basic.yaml b/examples/murrelet_example/src/configs/basic.yaml deleted file mode 100644 index e9c54f6..0000000 --- a/examples/murrelet_example/src/configs/basic.yaml +++ /dev/null @@ -1,43 +0,0 @@ -app: - debug: false - capture: false - width: 600.0 - clear_bg: kCt - bg_alpha: 0.2 # low enough so it won't clear for gpu - time: - realtime: true - fps: 40.0 - bpm: 120.0 - midi: true - audio: true - redraw: 1.0 - svg: - size: 120.0 - save: kAt - ctx: | - true; - -drawing: - sequencer: - type: Square # Rect, Hex - rows: 5 - cols: 5 - size: 10 - ctx: | - something = 10.0; - node: - val: x + something * a - curve: - closed: true - start: - type: Basic # Reference - loc: [0.0, 0.0] - angle_pi: 0.0 - dirs: - - type: Line - length: 50.0 - - type: Angle - angle_pi: 0.5 - - type: Arc - arc_length: 0.5 - radius: 50.0 \ No newline at end of file diff --git a/examples/murrelet_example/src/main.rs b/examples/murrelet_example/src/main.rs deleted file mode 100644 index 196c4f5..0000000 --- a/examples/murrelet_example/src/main.rs +++ /dev/null @@ -1,131 +0,0 @@ -use lerpable::Lerpable; -use murrelet::prelude::*; -use murrelet_draw::{compass::*, sequencers::*, style::MurreletPath}; -use murrelet_livecode::types::*; -use murrelet_src_audio::audio_src::AudioMng; -//use murrelet_src_blte::blte::BlteMng; -use murrelet_src_midi::midi::MidiMng; -use murrelet_svg::svg::ToSvgData; - -#[derive(Debug, Clone, Livecode, Lerpable, Default)] -struct SimpleTile { - val: f32, - curve: MurreletCompass, -} -impl SimpleTile { - fn draw(&self, _ctx: &UnitCellContext) { - // very simple program that outputs the value in test - - let m = MurreletPath::curve(self.curve.to_curve_maker()).to_svg(); - - println!("val {:?}", self.val); - println!("m {:?}", m); - //println!("ctx {:?}", ctx); - } -} - -#[derive(Debug, Clone, Livecode, Lerpable)] -struct DrawingConfig { - #[livecode(serde_default = "false")] - debug: bool, - sequencer: Sequencer, - ctx: AdditionalContextNode, - #[livecode(src = "sequencer", ctx = "ctx")] - node: UnitCells, -} -impl DrawingConfig { - fn output(&self) { - for t in self.node.iter() { - t.node.draw(&t.detail) - } - } -} - -// set up livecoder -#[derive(Debug, Clone, Livecode, Lerpable, TopLevelLiveCode)] -struct LiveCodeConf { - // global things - app: AppConfig, - drawing: DrawingConfig, -} - -struct Model { - livecode: LiveCode, - curr_frame: u64, -} -impl Model { - fn new() -> Model { - let capture_path_prefix = std::env::current_dir() - .expect("failed to locate `project_path`") - .join("_recordings"); - - // here is where you connect the livecode srcs (time is always included) - let livecode_src = LivecodeSrc::new(vec![ - Box::new(AppInputValues::new(true)), - Box::new(AudioMng::new()), - Box::new(MidiMng::new()), - //Box::new(BlteMng::new()), - ]); - - let livecode = LiveCode::new(capture_path_prefix, livecode_src, &[]); - // let drawing_state = { - // Drawing::new(&livecode.config().drawing) - // }; - - Model { - livecode, - curr_frame: 0, - } - } - - fn update(&mut self) { - self.curr_frame += 1; - - let app_input = MurreletAppInput::default_with_frames(self.curr_frame); - - self.livecode.update(&app_input, true).ok(); - - // this is also where you could update state - // instead of having the model hold the config directly, you can have - // it hold state - // if self.livecode.app_config().reload { - // self.drawing_state = Drawing::new(&self.livecode.config().drawing); - // } - } - - fn should_update(&self) -> bool { - let w = self.livecode.world(); - w.actual_frame() as u64 % self.livecode.app_config().redraw != 0 - } - - fn draw(&self) { - let _svg_draw_config = self.livecode.svg_save_path(); - - if !self.livecode.app_config().svg.save && self.should_update() { - return; - } - - // let draw_ctx = SDrawCtx::new( - // draw, - // &svg_draw_config, - // self.livecode.should_save_svg() - // ); - - // self.livecode.draw(&draw_ctx); // clear bg, etc - - // draw your stuff - // self.draw(&draw_ctx); - self.livecode.config().drawing.output() - } -} - -fn main() { - // normally you call this on start up - let mut model = Model::new(); - - for _ in 0..10 { - model.update(); - model.draw(); - std::thread::sleep(std::time::Duration::from_secs(1)); - } -} diff --git a/murrelet/Cargo.toml b/murrelet/Cargo.toml index ac60176..c6f9602 100644 --- a/murrelet/Cargo.toml +++ b/murrelet/Cargo.toml @@ -17,17 +17,24 @@ schemars = [ "murrelet_livecode_derive/schemars", "murrelet_perform/schemars", ] +# murrelet_gui = [ +# "murrelet_draw/murrelet_gui", +# "murrelet_livecode/murrelet_gui", +# "murrelet_livecode_macros/murrelet_gui", +# "murrelet_livecode_derive/murrelet_gui", +# "murrelet_perform/murrelet_gui", +# ] [dependencies] -glam = "0.28.0" +glam = { version = "0.28.0", features = ["serde"] } itertools = "0.10.5" serde = { version = "1.0.104", features = ["derive"] } serde_yaml = "0.9.17" -murrelet_common = { version = "0.1.2", path = "../murrelet_common" } -murrelet_draw = { version = "0.1.2", path = "../murrelet_draw", default-features = false } -murrelet_livecode = { version = "0.1.2", path = "../murrelet_livecode", default-features = false } -murrelet_livecode_macros = { version = "0.1.2", path = "../murrelet_livecode_macros" , default-features = false } -murrelet_livecode_derive = { version = "0.1.2", path = "../murrelet_livecode_macros/murrelet_livecode_derive/" , default-features = false } -murrelet_perform = { version = "0.1.2", path = "../murrelet_perform", default-features = false } +murrelet_common = { workspace = true } +murrelet_draw = { workspace = true, default-features = false } +murrelet_livecode = { workspace = true, default-features = false } +murrelet_livecode_macros = { workspace = true, default-features = false } +murrelet_livecode_derive = { workspace = true, default-features = false } +murrelet_perform = { workspace = true, default-features = false } diff --git a/murrelet_common/Cargo.toml b/murrelet_common/Cargo.toml index 92148e5..e7b97bc 100644 --- a/murrelet_common/Cargo.toml +++ b/murrelet_common/Cargo.toml @@ -9,11 +9,14 @@ license = "AGPL-3.0-or-later" [dependencies] itertools = "0.10.5" -glam = "0.28.0" +glam = { version = "0.28.0", features = ["serde"] } palette = "0.7.6" rand = "0.8" num-traits = "0.2.19" -lerpable = "0.0.2" +lerpable = "0.0.3" +serde = { version = "1.0.104", features = ["derive"] } +bytemuck = { version = "1.15", features = ["derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2" +getrandom = { workspace = true } diff --git a/murrelet_common/src/assets.rs b/murrelet_common/src/assets.rs index c6bf8d7..5757fa6 100644 --- a/murrelet_common/src/assets.rs +++ b/murrelet_common/src/assets.rs @@ -13,11 +13,11 @@ use crate::{IsPolyline, Polyline}; // eeh, this is not very good, svg assumptions are all mixed in #[derive(Debug, Clone)] -pub struct Asset { +pub struct VectorAsset { layers: Vec, // to support indexing map: HashMap>, } -impl Asset { +impl VectorAsset { pub fn get_layer(&self, layer: &str) -> Option<&Vec> { self.map.get(layer) } @@ -51,11 +51,11 @@ impl Asset { } #[derive(Debug, Clone)] -pub struct Assets { - filename_to_polyline_layers: HashMap, +pub struct VectorLayersAssetLookup { + filename_to_polyline_layers: HashMap, } -impl Assets { - pub fn new(filename_to_polyline_layers: HashMap) -> Self { +impl VectorLayersAssetLookup { + pub fn new(filename_to_polyline_layers: HashMap) -> Self { Self { filename_to_polyline_layers, } @@ -67,25 +67,99 @@ impl Assets { } } - pub fn new_ref(filename_to_polyline_layers: HashMap) -> AssetsRef { - Arc::new(Self::new(filename_to_polyline_layers)) + pub fn asset_layer(&self, key: &str, layer_idx: usize) -> Option<&Vec> { + let asset = &self.filename_to_polyline_layers[key]; + asset.get_layer_idx(layer_idx) + } + + pub fn layer_for_key(&self, key: &str) -> &[String] { + &self.filename_to_polyline_layers[key].layers + } +} + +pub trait IsColorType {} + +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +pub struct BlackWhite(bool); +impl IsColorType for BlackWhite {} + +// struct RGBAu8([u8; 4]),; +// struct RGBAf32([f32; 4]); + +#[derive(Debug, Clone)] +pub enum RasterAsset { + RasterBW(Vec>), +} + +#[derive(Debug, Clone)] +pub struct RasterAssetLookup(HashMap); +impl RasterAssetLookup { + pub fn empty() -> Self { + Self(HashMap::new()) + } + + pub fn insert(&mut self, filename: String, img: RasterAsset) { + self.0.insert(filename, img); + } +} + +#[derive(Debug, Clone)] +pub struct JsonAssetLookup(HashMap); +impl JsonAssetLookup { + pub fn empty() -> Self { + Self(HashMap::new()) + } + + pub fn insert(&mut self, filename: String, img: String) { + self.0.insert(filename, img); + } +} + +#[derive(Debug, Clone)] + +pub struct Assets { + vectors: VectorLayersAssetLookup, + #[allow(dead_code)] + rasters: RasterAssetLookup, + #[allow(dead_code)] + json: JsonAssetLookup, +} +impl Assets { + pub fn new( + vectors: VectorLayersAssetLookup, + rasters: RasterAssetLookup, + json: JsonAssetLookup, + ) -> Self { + Self { + vectors, + rasters, + json, + } } pub fn empty_ref() -> AssetsRef { Arc::new(Self::empty()) } - pub fn asset_layer(&self, key: &str, layer_idx: usize) -> Option<&Vec> { - let asset = &self.filename_to_polyline_layers[key]; - asset.get_layer_idx(layer_idx) + pub fn empty() -> Assets { + Self { + vectors: VectorLayersAssetLookup::empty(), + rasters: RasterAssetLookup::empty(), + json: JsonAssetLookup::empty(), + } } pub fn to_ref(self) -> AssetsRef { Arc::new(self) } + pub fn asset_layer(&self, key: &str, layer_idx: usize) -> Option<&Vec> { + self.vectors.asset_layer(key, layer_idx) + } + pub fn layer_for_key(&self, key: &str) -> &[String] { - &self.filename_to_polyline_layers[key].layers + self.vectors.layer_for_key(key) } } diff --git a/murrelet_common/src/color.rs b/murrelet_common/src/color.rs index 21df571..bfe212d 100644 --- a/murrelet_common/src/color.rs +++ b/murrelet_common/src/color.rs @@ -1,14 +1,19 @@ -use std::fmt; +use std::{ + fmt, + ops::{Add, Mul}, +}; +use glam::{vec3, Vec3}; use lerpable::{IsLerpingMethod, Lerpable}; -use palette::{ - rgb::Rgb, FromColor, Hsva, IntoColor, LinSrgb, LinSrgba, RgbHue, Srgb, Srgba, WithAlpha, -}; +use palette::{rgb::Rgb, FromColor, Hsva, IntoColor, LinSrgb, LinSrgba, Srgb, Srgba, WithAlpha}; +use serde::{Deserialize, Serialize}; + +use crate::rgb_to_hex; // hrm, color is confusing, so make a newtype around LinSrgba for all our color stuff // i need to double check i'm handling linear/not rgb right -#[derive(Copy, Clone, Default)] -pub struct MurreletColor(LinSrgba); +#[derive(Copy, Clone, Default, Serialize, Deserialize)] +pub struct MurreletColor([f32; 4]); // hsva impl fmt::Debug for MurreletColor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -18,30 +23,29 @@ impl fmt::Debug for MurreletColor { } impl MurreletColor { - pub fn from_palette_linsrgba(c: LinSrgba) -> Self { - Self(c) + // 0 to 1 + pub fn alpha(&self) -> f32 { + let [_, _, _, a] = self.into_hsva_components(); + a } - pub fn black() -> Self { - Self::hsva(0.0, 0.0, 0.0, 1.0) - } - - pub fn white() -> Self { - Self::rgba(1.0, 1.0, 1.0, 1.0) + pub fn from_palette_linsrgba(c: LinSrgba) -> Self { + let srgba: Srgba = Srgba::from_linear(c.into_format::()); + Self::from_hsva(Hsva::from_color(srgba)) } - pub fn transparent() -> Self { - Self::hsva(0.0, 0.0, 0.0, 0.0) - } + pub fn from_hsva(c: Hsva) -> Self { + // let c = Srgba::from_color(c).into_color(); - // shorthand for a really bright color - pub fn hue(h: f32) -> MurreletColor { - Self::hsva(h, 1.0, 1.0, 1.0) + let (h, s, v, a) = c.into_components(); + Self([h.into_degrees() / 360.0, s, v, a]) } pub fn hsva(h: f32, s: f32, v: f32, a: f32) -> MurreletColor { - let c = Hsva::new(RgbHue::from_degrees(h * 360.0), s, v, a); - Self::from_hsva(c) + // let c = Hsva::new(RgbHue::from_degrees(h * 360.0), s, v, a); + // Self::from_hsva(c) + + Self([h, s, v, a]) } pub fn srgb(r: f32, g: f32, b: f32) -> Self { @@ -54,6 +58,17 @@ impl MurreletColor { Self::from_srgba(c) } + pub fn rgb_vec3(rgb: Vec3) -> Self { + let (r, g, b) = rgb.into(); + let c = Srgba::new(r, g, b, 1.0); + Self::from_srgba(c) + } + + pub fn to_rgb_vec3(&self) -> Vec3 { + let [r, g, b, _] = self.into_rgba_components(); + vec3(r, g, b) + } + pub fn gray(g: f32) -> Self { Self::srgb(g, g, g) } @@ -64,7 +79,7 @@ impl MurreletColor { let b = b as f32 / 255.0; let c = LinSrgba::from_components((r, g, b, 1.0)); - MurreletColor(c) + MurreletColor::from_palette_linsrgba(c) } pub fn rgb_u8_tuple(rgb: (u8, u8, u8)) -> Self { @@ -76,62 +91,81 @@ impl MurreletColor { pub fn from_srgb(c: Srgb) -> Self { let c = LinSrgb::from_color(c).with_alpha(1.0); - MurreletColor(c) - } - - pub fn from_srgb_u8(c: Srgb) -> Self { - Self::rgb_u8(c.red, c.green, c.blue) + MurreletColor::from_palette_linsrgba(c) } pub fn from_srgba(c: Srgba) -> Self { let c = LinSrgba::from_color(c); - MurreletColor(c) + MurreletColor::from_palette_linsrgba(c) } pub fn from_rgb_u8(c: Rgb) -> Self { Self::rgb_u8(c.red, c.green, c.blue) } - pub fn from_hsva(c: Hsva) -> Self { - let c = Srgba::from_color(c).into_color(); - MurreletColor(c) + pub fn from_srgb_u8(c: Srgb) -> Self { + Self::rgb_u8(c.red, c.green, c.blue) } // getting info out of it pub fn into_hsva_components(&self) -> [f32; 4] { - let srgba: Srgba = self.0.into_format().into_color(); - let hsva: Hsva = Hsva::from_color(srgba); - [ - hsva.hue.into_raw_degrees() / 360.0, - hsva.saturation, - hsva.value, - self.0.alpha, - ] + self.0 + } + + pub fn to_srgba(&self) -> Srgba { + let [h, s, v, a] = self.into_hsva_components(); + let hsva: Hsva = Hsva::from_components((h * 360.0, s, v, a)); + Srgba::from_color(hsva) } pub fn into_rgba_components(&self) -> [f32; 4] { - let srgb: Srgba = self.0.into_format().into_color(); - srgb.into_components().into() + self.to_srgba().into_components().into() } pub fn to_linsrgba(&self) -> LinSrgba { - self.0 + self.to_srgba().into_color() } pub fn with_alpha(&self, alpha: f32) -> MurreletColor { - Self(self.0.with_alpha(alpha)) + let [h, s, v, _a] = self.into_hsva_components(); + Self([h, s, v, alpha]) + } + + pub fn black() -> Self { + Self::hsva(0.0, 0.0, 0.0, 1.0) + } + + pub fn white() -> Self { + Self::rgba(1.0, 1.0, 1.0, 1.0) + } + + pub fn transparent() -> Self { + Self::hsva(0.0, 0.0, 0.0, 0.0) + } + + // shorthand for a really bright color + pub fn hue(h: f32) -> MurreletColor { + Self::hsva(h, 1.0, 1.0, 1.0) } pub fn to_svg_rgb(&self) -> String { - let [r, g, b, a] = self.into_rgba_components(); - format!( - "rgba({} {} {} / {})", - (r * 255.0) as i32, - (g * 255.0) as i32, - (b * 255.0) as i32, - a - ) + self.hex() + } + + pub fn to_fill_opacity(&self) -> String { + format!("{}", self.alpha()) + } + + pub fn hex(&self) -> String { + let [r, g, b, _a] = self.into_rgba_components(); + rgb_to_hex(r, g, b) + } + + pub fn with_saturation(&self, sat: f32) -> MurreletColor { + let mut c = *self; + c.0[1] = sat; + c } } @@ -147,7 +181,7 @@ impl MurreletIntoLinSrgba for Rgb { impl MurreletIntoLinSrgba for LinSrgba { fn into_murrelet_color(&self) -> MurreletColor { - MurreletColor(*self) + MurreletColor::from_palette_linsrgba(*self) } } @@ -175,3 +209,23 @@ impl Lerpable for MurreletColor { ) } } + +impl Add for MurreletColor { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + let [h, s, v, a] = self.into_hsva_components(); + let [h2, s2, v2, a2] = other.into_hsva_components(); + MurreletColor::hsva(h + h2, s + s2, v + v2, a + a2) + } +} + +impl Mul for MurreletColor { + type Output = Self; + + fn mul(self, scalar: f32) -> Self::Output { + let [h, s, v, a] = self.into_hsva_components(); + + MurreletColor::hsva(h * scalar, s * scalar, v * scalar, a * scalar) + } +} diff --git a/murrelet_common/src/geometry.rs b/murrelet_common/src/geometry.rs index f03bd6f..180b8c1 100644 --- a/murrelet_common/src/geometry.rs +++ b/murrelet_common/src/geometry.rs @@ -3,25 +3,36 @@ use std::{f32::consts::PI, ops::Add}; use glam::{vec2, Mat3, Mat4, Vec2}; +use lerpable::Lerpable; +use serde::{Deserialize, Serialize}; use crate::{ - intersection::{find_intersection_inf, within_segment}, + intersection::{find_intersection_inf, find_intersection_segments, within_segment}, transform::TransformVec2, + triangulate::DefaultVertex, + SimpleTransform2d, SimpleTransform2dStep, }; pub fn a_pi(a: f32) -> AnglePi { AnglePi::new(a) } -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Lerpable, Serialize, Deserialize)] pub struct AnglePi(f32); impl AnglePi { pub const ZERO: Self = AnglePi(0.0); + pub const HALF: Self = AnglePi(0.5); + pub const ONE: Self = AnglePi(1.0); + pub const NEG_HALF: Self = AnglePi(-0.5); pub fn new(v: f32) -> AnglePi { AnglePi(v) } + pub fn to_transform(&self) -> SimpleTransform2d { + SimpleTransform2d::rotate_pi(self.angle_pi()) + } + pub fn _angle(&self) -> f32 { let a: Angle = (*self).into(); a.angle() @@ -54,6 +65,10 @@ impl AnglePi { pub fn is_neg(&self) -> bool { self.0 < 0.0 } + + pub fn rotate_vec2(&self, v: Vec2) -> Vec2 { + self.to_transform().transform_vec2(v) + } } impl std::ops::Neg for AnglePi { @@ -152,7 +167,7 @@ impl From for Angle { } } -// newtype +// newtype. #[derive(Copy, Clone, PartialEq, PartialOrd)] pub struct Angle(f32); impl Angle { @@ -165,11 +180,11 @@ impl Angle { a._angle_pi() } - pub fn scale(&self, s: f32) -> Self { + pub fn _scale(&self, s: f32) -> Self { Angle(self.angle() * s) } - pub fn hyp_given_opp(&self, opp: Length) -> Length { + pub fn hyp_given_opp(&self, opp: L) -> Length { Length(opp.len() / self.angle().sin()) } @@ -203,6 +218,10 @@ impl Angle { Angle::new(t.transform_vec2(self.to_norm_dir()).to_angle()) } + pub fn rotate_vec2(&self, v: Vec2) -> Vec2 { + self.as_angle_pi().rotate_vec2(v) + } + pub fn is_vertical(&self) -> bool { (self.angle_pi() - 0.5 % 1.0) < 1e-2 } @@ -214,44 +233,52 @@ impl Angle { // todo: mirror across angle } +impl TransformVec2 for Angle { + fn transform_vec2(&self, v: Vec2) -> Vec2 { + self.rotate_vec2(v) + } +} + impl std::fmt::Debug for Angle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { AnglePi::fmt(&(*self).into(), f) } } -pub trait IsAngle { +pub trait IsAngle: Sized { fn angle_pi(&self) -> f32; fn angle(&self) -> f32; fn as_angle(&self) -> Angle; fn as_angle_pi(&self) -> AnglePi; fn to_norm_dir(&self) -> Vec2; fn to_mat3(&self) -> Mat3; - fn perp_to_left(&self) -> Angle; - fn perp_to_right(&self) -> Angle; -} + fn perp_to_left(&self) -> Self; + fn perp_to_right(&self) -> Self; + fn scale(&self, s: f32) -> Self; -impl IsAngle for A -where - Angle: From, - A: Copy, -{ - fn to_norm_dir(&self) -> Vec2 { - Angle::from(*self)._to_norm_dir() + fn mod2(&self) -> AnglePi { + AnglePi::new(self.angle_pi() % 2.0) } - fn perp_to_left(&self) -> Angle { - Angle::from(*self)._perp_to_left() + fn flip(&self) -> Self { + self.perp_to_left().perp_to_left() } - fn perp_to_right(&self) -> Angle { - Angle::from(*self)._perp_to_right() + fn reflect_x(&self) -> Self { + self.scale(-1.0) } - fn to_mat3(&self) -> Mat3 { - Mat3::from_angle(Angle::from(*self).angle()) + fn reflect_y(&self) -> Self { + self.perp_to_left().scale(-1.0).perp_to_right() } +} +impl IsAngle for A +where + Angle: From, + A: From, + A: Copy, +{ fn angle_pi(&self) -> f32 { Angle::from(*self)._angle_pi() } @@ -267,6 +294,26 @@ where fn as_angle(&self) -> Angle { (*self).into() } + + fn to_norm_dir(&self) -> Vec2 { + Angle::from(*self)._to_norm_dir() + } + + fn perp_to_left(&self) -> Self { + Angle::from(*self)._perp_to_left().into() + } + + fn perp_to_right(&self) -> Self { + Angle::from(*self)._perp_to_right().into() + } + + fn to_mat3(&self) -> Mat3 { + Mat3::from_angle(Angle::from(*self).angle()) + } + + fn scale(&self, s: f32) -> Self { + self.as_angle()._scale(s).into() + } } // LENGTH @@ -355,10 +402,12 @@ impl IsLength for PointToPoint { // Special types -#[derive(Debug, Copy, Clone)] +// should combine this with Tangent... + +#[derive(Debug, Copy, Clone, PartialEq)] pub struct SpotOnCurve { - loc: Vec2, - angle: Angle, + pub loc: Vec2, + pub angle: Angle, } impl SpotOnCurve { @@ -419,12 +468,30 @@ impl SpotOnCurve { } } - pub fn move_left_perp_dist(&self, length: Length) -> Vec2 { - self.turn_left_perp().to_line(length).to_last_point() + pub fn move_left_perp_dist(&self, length: L) -> Vec2 { + self.turn_left_perp() + .to_line(length.to_length()) + .to_last_point() + } + + pub fn move_left_perp_dist_spot(&self, length: L) -> SpotOnCurve { + SpotOnCurve::new(self.move_left_perp_dist(length), self.angle()) } - pub fn move_right_perp_dist(&self, length: Length) -> Vec2 { - self.turn_right_perp().to_line(length).to_last_point() + pub fn move_right_perp_dist_spot(&self, length: L) -> SpotOnCurve { + SpotOnCurve::new(self.move_right_perp_dist(length), self.angle()) + } + + pub fn move_right_perp_dist(&self, length: L) -> Vec2 { + self.turn_right_perp() + .to_line(length.to_length()) + .to_last_point() + } + + // x moves along the direction, y moves left perp + pub fn offset_amount(&self, offset: Vec2) -> Vec2 { + let a = self.to_line(offset.x).to_last_point(); + SpotOnCurve::new(a, self.angle).move_left_perp_dist(offset.y) } pub fn rotate(&self, rotate: AnglePi) -> Self { @@ -433,6 +500,80 @@ impl SpotOnCurve { angle: self.angle + rotate, } } + + pub fn to_transform(&self) -> SimpleTransform2d { + SimpleTransform2d::new(vec![ + SimpleTransform2dStep::rotate_pi(self.angle()), + SimpleTransform2dStep::translate(self.loc()), + ]) + } + + pub fn set_angle>(&self, new: A) -> Self { + let mut c = *self; + c.angle = new.into(); + c + } + + pub fn line_to_spot(&self, length: f32) -> Vec2 { + self.loc() + self.angle().to_norm_dir() * length + } + + pub fn flip(&self) -> SpotOnCurve { + Self::new(self.loc, self.angle.perp_to_left().perp_to_left()) + } + + pub fn with_loc(&self, loc: Vec2) -> SpotOnCurve { + Self { + loc, + angle: self.angle, + } + } + + pub fn travel(&self, length: f32) -> SpotOnCurve { + Self { + loc: self.line_to_spot(length), + angle: self.angle, + } + } + + pub fn travel_2d(&self, dist: Vec2) -> SpotOnCurve { + let a = Angle::new(dist.to_angle()); + let loc = self.travel(dist.x).turn_right_perp().travel(dist.y).loc; + SpotOnCurve::new(loc, a) + } + + pub fn move_back(&self, dist: f32) -> SpotOnCurve { + self.flip().travel(dist).flip() + } +} + +pub trait ToSpotWithAngle { + fn to_spot_a>(&self, a: A) -> SpotOnCurve; + + fn to_spot_forward(&self) -> SpotOnCurve { + self.to_spot_a(AnglePi::new(0.0)) + } + fn to_spot_backward(&self) -> SpotOnCurve { + self.to_spot_a(AnglePi::new(1.0)) + } + fn to_spot_upward(&self) -> SpotOnCurve { + self.to_spot_a(AnglePi::new(0.5)) + } + fn to_spot_downward(&self) -> SpotOnCurve { + self.to_spot_a(AnglePi::new(1.5)) + } +} + +impl ToSpotWithAngle for Vec2 { + fn to_spot_a>(&self, a: A) -> SpotOnCurve { + SpotOnCurve::new(*self, a.into()) + } +} + +impl ToSpotWithAngle for SpotOnCurve { + fn to_spot_a>(&self, a: A) -> SpotOnCurve { + SpotOnCurve::new(self.loc, a.into()) + } } #[derive(Clone, Copy, Debug)] @@ -452,7 +593,7 @@ impl CornerAngleToAngle { } // dist is how far away from the current point. left is positive (inside of angle) (i think) - pub fn corner_at_point(&self, dist: Length) -> Vec2 { + pub fn corner_at_point(&self, dist: L) -> Vec2 { // mid-way between the two angles, and then go perpindicular at some point let p = if dist.len() < 0.0 { @@ -487,6 +628,30 @@ impl PrevCurrNextVec2 { let next_to_curr = self.next - self.curr; Angle::new(curr_to_prev.angle_to(next_to_curr)) } + + pub fn tri_contains(&self, other_v: &Vec2) -> bool { + // https://nils-olovsson.se/articles/ear_clipping_triangulation/ + let v0 = self.next - self.prev; + let v1 = self.curr - self.prev; + let v2 = *other_v - self.prev; + + let dot00 = v0.dot(v0); + let dot01 = v0.dot(v1); + let dot02 = v0.dot(v2); + let dot11 = v1.dot(v1); + let dot12 = v1.dot(v2); + + let denom = dot00 * dot11 - dot01 * dot01; + if denom.abs() < 1e-10 { + return true; + } + + let inv_denom = 1.0 / denom; + let u = (dot11 * dot02 - dot01 * dot12) * inv_denom; + let v = (dot00 * dot12 - dot01 * dot02) * inv_denom; + + (u >= 0.0) && (v >= 0.0) && (u + v < 1.0) + } } #[derive(Copy, Clone, Debug)] @@ -509,7 +674,7 @@ impl PointToPoint { } pub fn midpoint(&self) -> Vec2 { - 0.5 * (self.start + self.end) + self.pct(0.5) } pub fn to_vec(&self) -> Vec { @@ -524,6 +689,10 @@ impl PointToPoint { find_intersection_inf(self.to_tuple(), other.to_tuple()) } + pub fn find_intersection(&self, other: PointToPoint) -> Option { + find_intersection_segments(self.to_tuple(), other.to_tuple()) + } + pub fn within_segment(self, intersection: Vec2, eps: f32) -> bool { within_segment(self.to_tuple(), intersection, eps) } @@ -535,6 +704,55 @@ impl PointToPoint { pub fn end(&self) -> Vec2 { self.end } + + pub fn closest_pt_to_pt(&self, intersection: Vec2) -> PointToPoint { + let closest_point = self.closest_point_to_line(intersection); + + PointToPoint { + start: intersection, + end: closest_point, + } + } + + // drop a right angle down from intersection, where does it fall along + // the line extended from point to point? + pub fn closest_point_to_line(&self, intersection: Vec2) -> Vec2 { + self.start + (intersection - self.start).dot(self.to_norm_dir()) * self.to_norm_dir() + } + + pub fn pct(&self, loc: f32) -> Vec2 { + self.start + loc * (self.end - self.start) + } + + pub fn pct_spot(&self, pct: f32) -> SpotOnCurve { + SpotOnCurve::new(self.pct(pct), self.angle()) + } + + pub fn start_spot(&self) -> SpotOnCurve { + SpotOnCurve::new(self.start, self.angle()) + } + + pub fn end_spot(&self) -> SpotOnCurve { + SpotOnCurve::new(self.end, self.angle()) + } + + pub fn flip(&self) -> PointToPoint { + PointToPoint::new(self.end, self.start) + } + + pub fn shift_right_perp(&self, dist: f32) -> PointToPoint { + Self::new( + self.start_spot().turn_right_perp().travel(dist).loc, + self.end_spot().turn_right_perp().travel(dist).loc, + ) + } + + pub fn shift_left_perp(&self, dist: f32) -> PointToPoint { + Self::new( + self.start_spot().turn_left_perp().travel(dist).loc, + self.end_spot().turn_left_perp().travel(dist).loc, + ) + } } #[derive(Copy, Clone, Debug)] @@ -544,15 +762,103 @@ pub struct LineFromVecAndLen { length: Length, } impl LineFromVecAndLen { - pub fn new(start: Vec2, angle: Angle, length: Length) -> Self { + pub fn new(start: Vec2, angle: Angle, length: L) -> Self { Self { start, angle, - length, + length: length.to_length(), } } + pub fn new_centered(start: Vec2, angle: Angle, length: L) -> Self { + let first_pt = Self::new(start, angle, -0.5 * length.len()).to_last_point(); + Self::new(first_pt, angle, length) + } + pub fn to_last_point(&self) -> Vec2 { self.start + self.length.len() * self.angle.to_norm_dir() } + + pub fn to_vec(&self) -> Vec { + vec![self.start, self.to_last_point()] + } + + pub fn to_p2p(&self) -> PointToPoint { + PointToPoint::new(self.start, self.to_last_point()) + } +} + +// more curve things + +//https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Tangents_between_two_circles + +pub enum TangentBetweenCirclesKind { + RightRight, + LeftLeft, + RightLeft, + LeftRight, +} + +pub fn tangents_between_two_circles( + kind: TangentBetweenCirclesKind, + v1: Vec2, + r1: f32, + v2: Vec2, + r2: f32, +) -> Option<(Vec2, Vec2)> { + let d_sq = v1.distance_squared(v2); + + // these are too close together + if d_sq <= (r1 - r2).powi(2) { + return None; + } + + let d = d_sq.sqrt(); + + let v = (v2 - v1) / d; + + let (sign1, sign2) = match kind { + TangentBetweenCirclesKind::RightRight => (1.0, 1.0), + TangentBetweenCirclesKind::LeftLeft => (1.0, -1.0), + TangentBetweenCirclesKind::RightLeft => (-1.0, 1.0), + TangentBetweenCirclesKind::LeftRight => (-1.0, -1.0), + }; + + let c = (r1 - sign1 * r2) / d; + + // Now we're just intersecting a line with a circle: v*n=c, n*n=1 + + if c.powi(2) > 1.0 { + return None; + } + let h = (1.0 - c.powi(2)).max(0.0).sqrt(); + + let nx = v.x * c - sign2 * h * v.y; + let ny = v.y * c + sign2 * h * v.x; + + let start = vec2(v1.x + r1 * nx, v1.y + r1 * ny); + + let end = vec2(v2.x + sign1 * r2 * nx, v2.y + sign1 * r2 * ny); + + Some((start, end)) +} + +pub trait ToVec2 { + fn to_vec2(&self) -> Vec2; +} + +impl ToVec2 for Vec2 { + fn to_vec2(&self) -> Vec2 { + *self + } +} + +impl ToVec2 for DefaultVertex { + fn to_vec2(&self) -> Vec2 { + self.pos2d() + } +} + +pub fn sagitta_from_arc_len(radius: f32, central_angle: AnglePi) -> f32 { + radius * (1.0 - (0.5 * central_angle.angle()).cos()) } diff --git a/murrelet_common/src/idx.rs b/murrelet_common/src/idx.rs index 0d6c1c5..2bfc30c 100644 --- a/murrelet_common/src/idx.rs +++ b/murrelet_common/src/idx.rs @@ -3,10 +3,18 @@ use glam::{vec2, Vec2}; use itertools::Itertools; use rand::{rngs::StdRng, Rng, SeedableRng}; +use serde::{Deserialize, Serialize}; use crate::lerp; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy)] +pub enum IdxMatch { + First, + Last, + Idx(u64), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IdxInRange { i: u64, total: u64, // the count @@ -23,6 +31,13 @@ impl IdxInRange { } } + pub fn new_last>(total: U) -> IdxInRange + where + >::Error: core::fmt::Debug, + { + IdxInRange::new(0, total).last_i() + } + pub fn enumerate<'a, T, I>(iter: I) -> Vec<(IdxInRange, T)> where I: ExactSizeIterator, @@ -33,6 +48,32 @@ impl IdxInRange { .collect_vec() } + pub fn enumerate_count>(total: U) -> Vec + where + >::Error: core::fmt::Debug, + { + let total = total.try_into().expect("can't convert to u64"); + (0..total).map(|i| IdxInRange::new(i, total)).collect_vec() + } + + pub fn idx_rep(&self) -> IdxMatch { + if self.is_first() { + IdxMatch::First + } else if self.is_last() { + IdxMatch::Last + } else { + IdxMatch::Idx(self.i()) + } + } + + pub fn matches(&self, m: &IdxMatch) -> bool { + match m { + IdxMatch::First => self.i() == 0, + IdxMatch::Last => self.is_last(), + IdxMatch::Idx(i) => self.i() == *i, + } + } + pub fn prev_i(&self) -> Option { if self.i == 0 { None @@ -104,16 +145,61 @@ impl IdxInRange { self.i } - fn is_last(&self) -> bool { + pub fn is_last(&self) -> bool { self.i == self.total - 1 } pub fn amount_from_end(&self) -> u64 { self.total - self.i - 1 } + + pub fn s(&self, range: Vec2) -> f32 { + self.scale(range.x, range.y) + } + + pub fn scale(&self, start: f32, end: f32) -> f32 { + lerp(start, end, self.pct()) + } + + pub fn to_2d(&self) -> IdxInRange2d { + IdxInRange2d::new_from_single_idx(*self) + } + + pub fn skip>(&self, count: T) -> Option + where + >::Error: core::fmt::Debug, + { + let count_u64 = count.try_into().expect("can't convert to u64"); + if self.i + count_u64 >= self.total { + None + } else { + Some(IdxInRange { + i: self.i + count_u64, + total: self.total, + }) + } + } + + pub fn is_first(&self) -> bool { + self.i == 0 + } + + pub fn i_usize(&self) -> usize { + self.i() as usize + } } -#[derive(Debug, Clone, Copy)] +pub trait IdxInRangeEnum<'a, T> { + fn iter_enum_idx(&'a self) -> Vec<(IdxInRange, &'a T)>; +} + +impl<'a, T> IdxInRangeEnum<'a, T> for &[T] { + fn iter_enum_idx(&'a self) -> Vec<(IdxInRange, &'a T)> { + IdxInRange::enumerate(self.iter()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IdxInRange2d { pub i: IdxInRange, pub j: IdxInRange, @@ -130,6 +216,21 @@ impl IdxInRange2d { } } + pub fn enumerate_counts + Copy>(ii: U, jj: U) -> Vec + where + >::Error: core::fmt::Debug, + { + let ii = ii.try_into().expect("can't convert to u64"); + let jj = jj.try_into().expect("can't convert to u64"); + let mut v = vec![]; + for i in 0..ii { + for j in 0..jj { + v.push(IdxInRange2d::new_rect(i, j, ii, jj)); + } + } + v + } + pub fn to_alternating_i(&self) -> IdxInRange2d { IdxInRange2d { i: IdxInRange::new(self.i.i() / 2, self.i.total / 2), @@ -158,6 +259,11 @@ impl IdxInRange2d { IdxInRange2d { i, j } } + pub fn new_from_single_idx(i: IdxInRange) -> IdxInRange2d { + let j = IdxInRange::new(0, 1); + IdxInRange2d { i, j } + } + pub fn pct(&self) -> Vec2 { vec2(self.i.pct(), self.j.pct()) } @@ -203,11 +309,9 @@ impl IdxInRange2d { vec2(self.i.half_step_pct(), self.j.half_step_pct()) } - pub fn lerp_idx(&self, x: f32, y: f32) -> [(usize, usize); 4] { - // helps tell which indexes to use for lerping - let x_idx = x as usize; - let y_idx = y as usize; - + pub fn lerp_idx(&self) -> [(usize, usize); 4] { + let x_idx = self.i.i() as usize; + let y_idx = self.j.i() as usize; let x_is_too_far = x_idx + 1 >= self.i.total as usize; let y_is_too_far = y_idx + 1 >= self.j.total as usize; @@ -232,7 +336,7 @@ impl IdxInRange2d { } pub fn is_alternate(&self) -> bool { - (self.i.i() % 2 != 0) ^ (self.j.i() % 2 == 0) + !self.i.i().is_multiple_of(2) ^ self.j.i().is_multiple_of(2) } pub fn is_last_x(&self) -> bool { @@ -248,4 +352,16 @@ impl IdxInRange2d { let y = self.j.to_range(range.x, range.y); vec2(x, y) } + + pub fn i_total(&self) -> f32 { + self.i.total as f32 + } + + pub fn i(&self) -> u64 { + self.i.i() + } + + pub fn j(&self) -> u64 { + self.j.i() + } } diff --git a/murrelet_common/src/intersection.rs b/murrelet_common/src/intersection.rs index b93f65b..010fd31 100644 --- a/murrelet_common/src/intersection.rs +++ b/murrelet_common/src/intersection.rs @@ -1,4 +1,6 @@ -use glam::{vec2, Vec2}; +use glam::Vec2; + +use crate::SpotOnCurve; // todo, can i replace this with geo? pub fn find_intersection_inf(line0: (Vec2, Vec2), line1: (Vec2, Vec2)) -> Option { @@ -15,24 +17,58 @@ pub fn find_intersection_inf(line0: (Vec2, Vec2), line1: (Vec2, Vec2)) -> Option let y4 = line1_end.y; // first find if the lines intersect - let d: f32 = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); - - if d == 0.0 { - None // the lines are parallel, we're done + let d = (y2 - y1) * (x3 - x4) - (x2 - x1) * (y3 - y4); + let epsilon = 1e-7; + if d.abs() < epsilon { + None // parallel, we're done } else { - let t_num = (x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4); - let t = t_num / d; - - let px = x1 + t * (x2 - x1); - let py = y1 + t * (y2 - y1); + // let intersection_point_f32: Vec2; - let intersection = vec2(px, py); + let self_is_vertical = (x1 - x2).abs() < epsilon; + let other_is_vertical = (x3 - x4).abs() < epsilon; - // okay we have an intersection! now just make sure it's in each segment - Some(intersection) + // some help from gemini 2.5 pro + let pt = if self_is_vertical && other_is_vertical { + // Both vertical and den != 0. This should not happen if logic is sound, + // as two distinct vertical lines would have den = 0. + // This implies they might be collinear and overlapping if den was non-zero due to epsilon, + // but the den check should have caught true parallelism. + // For safety, returning None if this unexpected state is reached. + return None; + } else if self_is_vertical { + // Self is vertical, other is not. + let px = x1; + // Slope of other segment + let m_other = (y4 - y3) / (x4 - x3); + // y = m(x - x_pt) + y_pt. Using (x3,y3) from other segment. + let py = m_other * (px - x3) + y3; + Vec2::new(px, py) + } else if other_is_vertical { + // Other is vertical, self is not. + let px = x3; + // Slope of self segment (safe as it's not vertical) + let m_self = (y2 - y1) / (x2 - x1); + // y = m(x - x_pt) + y_pt. Using (x1,y1) from self segment. + let py = m_self * (px - x1) + y1; + Vec2::new(px, py) + } else { + let t_numerator = (x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4); + let t = t_numerator / d; + let px = x1 + t * (x2 - x1); + let py = y1 + t * (y2 - y1); + Vec2::new(px, py) + }; + Some(pt) } } +pub fn find_intersect_spots(spot0: SpotOnCurve, spot1: SpotOnCurve) -> Option { + find_intersection_inf( + (spot0.loc(), spot0.to_line(-100.0).to_last_point()), + (spot1.loc(), spot1.to_line(-100.0).to_last_point()), + ) +} + pub fn find_intersection_segments(line0: (Vec2, Vec2), line1: (Vec2, Vec2)) -> Option { find_intersection_inf(line0, line1).filter(|&intersection| { within_segment(line0, intersection, 0.0001) && within_segment(line1, intersection, 0.0001) diff --git a/murrelet_common/src/lib.rs b/murrelet_common/src/lib.rs index bb2f92e..bc76d08 100644 --- a/murrelet_common/src/lib.rs +++ b/murrelet_common/src/lib.rs @@ -4,6 +4,7 @@ use glam::{vec3, Vec3}; use itertools::Itertools; use lerpable::{IsLerpingMethod, Lerpable}; use num_traits::NumCast; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::hash::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -18,6 +19,7 @@ mod iter; mod metric; mod polyline; mod transform; +pub mod triangulate; pub use assets::*; pub use color::*; @@ -43,7 +45,7 @@ pub struct MurreletTime(u128); // millis impl MurreletTime { pub fn now() -> Self { - MurreletTime(epoch_time_ms()) + MurreletTime(epoch_time_us()) } pub fn epoch() -> Self { @@ -62,21 +64,25 @@ impl MurreletTime { MurreletTime::in_x_ms(1000) } - pub fn as_millis_u128(&self) -> u128 { - self.0 - } - pub fn as_secs(&self) -> u64 { - (self.0 / 1000) as u64 + (self.as_millis_u128() / 1000) as u64 } // f32 for historical reasons, can change at some point pub fn as_secs_f32(&self) -> f32 { - (self.0 as f32) / 1000.0 + (self.as_millis()) / 1000.0 + } + + pub fn as_millis_u128(&self) -> u128 { + self.0 / 1000 } pub fn as_millis(&self) -> f32 { - self.0 as f32 + self.0 as f32 / 1000.0 + } + + pub fn as_micro(&self) -> u128 { + self.0 } } @@ -88,7 +94,6 @@ impl std::ops::Sub for MurreletTime { } } -// in seconds pub fn epoch_time_ms() -> u128 { #[cfg(target_arch = "wasm32")] { @@ -116,6 +121,30 @@ pub fn epoch_time_ms() -> u128 { } } +pub fn epoch_time_us() -> u128 { + #[cfg(target_arch = "wasm32")] + { + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(js_namespace = Date)] + fn now() -> f64; + } + + (now() * 1000.0) as u128 + } + + #[cfg(not(target_arch = "wasm32"))] + { + use std::time::SystemTime; + use std::time::UNIX_EPOCH; + + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("wat") + .as_micros() + } +} + // // RUN ID // @@ -386,6 +415,30 @@ impl Rect { pub fn xy(&self) -> Vec2 { self.xy } + + pub fn center(&self) -> Vec2 { + self.xy() + } + + pub fn pos(&self, s: Vec2) -> Vec2 { + vec2( + (s.x - self.left()) / self.w(), + (s.y - self.bottom()) / self.h(), + ) + } + + pub fn transform_to_other_rect(&self, target_rect: Rect) -> SimpleTransform2d { + let scale = target_rect.w() / self.w(); + + SimpleTransform2d::new(vec![ + // first move to center + SimpleTransform2dStep::translate(-self.center()), + // scale + SimpleTransform2dStep::scale_both(scale), + // then move to target + SimpleTransform2dStep::translate(target_rect.center()), + ]) + } } #[derive(Debug, Clone, Copy)] @@ -468,7 +521,11 @@ pub trait IsLivecodeSrc { fn to_exec_funcs(&self) -> Vec<(String, LivecodeValue)>; // this is a way to give usage feedback to the livecode src, e.g. tell a MIDI controller // we're using a parameter, or what value to set indicator lights to. - fn feedback(&mut self, _variables: &HashMap) { + fn feedback( + &mut self, + _variables: &HashMap, + _outgoing_msgs: &[(String, String, LivecodeValue)], + ) { // default don't do anything } } @@ -477,6 +534,36 @@ pub struct LivecodeSrc { vs: Vec>, } +#[derive(Default, Debug, Clone)] +pub struct CustomVars(Option>); + +impl CustomVars { + pub fn new(hash_map: HashMap) -> Self { + Self(Some(hash_map)) + } + + pub fn to_exec_funcs(&self) -> Vec<(String, LivecodeValue)> { + if let Some(hm) = &self.0 { + let mut v = vec![]; + for (key, value) in hm.iter() { + v.push((key.clone(), LivecodeValue::float(*value))) + } + v + } else { + vec![] + } + } + + pub fn update(&mut self, new: &Self) { + // basically update adds, and you can never delete >:D + if let Some(o) = &new.0 { + self.0 + .get_or_insert_with(Default::default) + .extend(o.iter().map(|(k, v)| (k.clone(), *v))); + } + } +} + // what is sent from apps (like nannou) #[derive(Default)] pub struct MurreletAppInput { @@ -485,6 +572,7 @@ pub struct MurreletAppInput { pub mouse_position: Vec2, pub mouse_left_is_down: bool, pub elapsed_frames: u64, + pub custom_vars: CustomVars, } impl MurreletAppInput { @@ -501,6 +589,7 @@ impl MurreletAppInput { mouse_position, mouse_left_is_down, elapsed_frames, + custom_vars: CustomVars::default(), } } @@ -509,6 +598,7 @@ impl MurreletAppInput { mouse_position: Vec2, mouse_left_is_down: bool, elapsed_frames: u64, + custom_vars: HashMap, ) -> Self { Self { keys: None, @@ -516,6 +606,7 @@ impl MurreletAppInput { mouse_position, mouse_left_is_down, elapsed_frames, + custom_vars: CustomVars::new(custom_vars), } } @@ -578,9 +669,13 @@ impl LivecodeSrc { self.vs.iter().flat_map(|v| v.to_exec_funcs()).collect_vec() } - pub fn feedback(&mut self, variables: &HashMap) { + pub fn feedback( + &mut self, + variables: &HashMap, + outgoing_msgs: &[(String, String, LivecodeValue)], + ) { for v in self.vs.iter_mut() { - v.feedback(variables); + v.feedback(variables, outgoing_msgs); } } } @@ -589,7 +684,24 @@ const MAX_STRID_LEN: usize = 16; #[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct StrId([u8; MAX_STRID_LEN]); +impl Serialize for StrId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> Deserialize<'de> for StrId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(StrId::new(&s)) + } +} // from chatgpt impl StrId { pub fn new(s: &str) -> Self { @@ -633,20 +745,58 @@ pub fn fixed_pt_f32_to_str(x: f32) -> String { FixedPointF32::new(x).to_str() } -#[derive(Debug, Copy, Clone, Ord, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Debug, Copy, Clone, Ord, Eq, PartialEq, PartialOrd, Hash, Serialize, Deserialize)] pub struct FixedPointF32 { pub x: i64, } + +impl FixedPointF32 { + pub fn abs(&self) -> Self { + Self { x: self.x.abs() } + } + + pub fn decode(s: &str) -> Self { + Self { + x: s.parse::().unwrap_or(0), + } + } + + pub fn encode(&self) -> String { + self.x.to_string() + } +} + +impl std::ops::Sub for FixedPointF32 { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + x: self.x - other.x, + } + } +} + +impl std::ops::Add for FixedPointF32 { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + x: self.x + other.x, + } + } +} impl FixedPointF32 { pub const MAX: Self = FixedPointF32 { x: i64::MAX }; pub const MIN: Self = FixedPointF32 { x: i64::MIN }; + pub const GRANULARITY: f32 = 1e4f32; + fn f32_to_i64(f: f32) -> i64 { - (f * 1e4f32) as i64 + (f * Self::GRANULARITY).round() as i64 } fn i64_to_f32(f: i64) -> f32 { - f as f32 / 1e4f32 + f as f32 / Self::GRANULARITY } pub fn to_i64(&self) -> i64 { @@ -681,11 +831,19 @@ impl FixedPointF32 { } } -#[derive(Debug, Copy, Clone, Ord, Eq, PartialEq, PartialOrd, Hash)] +#[derive(Copy, Clone, Ord, Eq, PartialEq, PartialOrd, Hash, Serialize, Deserialize)] pub struct FixedPointVec2 { pub x: FixedPointF32, pub y: FixedPointF32, } + +impl std::fmt::Debug for FixedPointVec2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let vec2_representation = self.to_vec2(); + write!(f, "FixedPointVec2({:?})", vec2_representation) + } +} + impl FixedPointVec2 { pub fn round(&self, n: i64) -> FixedPointVec2 { FixedPointVec2::new_from_fixed_point(self.x.round(n), self.y.round(n)) @@ -747,6 +905,13 @@ impl FixedPointVec2 { pub fn nudge(&self, x: i64, y: i64) -> Self { Self::new_from_fixed_point(self.x.nudge(x), self.y.nudge(y)) } + + // manhattan + pub fn dist_man(&self, other: FixedPointVec2) -> i64 { + let dx = (self.x - other.x).abs(); + let dy = (self.y - other.y).abs(); + (dx + dy).to_i64() + } } pub fn approx_eq_eps(x: f32, y: f32, eps: f32) -> bool { @@ -851,3 +1016,144 @@ pub fn lerpify_vec_vec3( lerped.into_iter().map(|v| v.into()).collect_vec() } + +// todo, did i end up using this? +#[derive(Clone, Copy, Debug)] +pub struct Dim2d { + x: usize, // e.g. rows + y: usize, // e.g. cols +} + +impl Dim2d { + pub fn new(x: usize, y: usize) -> Self { + Self { x, y } + } + + pub fn x(&self) -> usize { + self.x + } + + pub fn y(&self) -> usize { + self.y + } + + pub fn row(&self) -> usize { + self.y + } + + pub fn col(&self) -> usize { + self.x + } + + pub fn i(&self) -> usize { + self.y + } + + pub fn j(&self) -> usize { + self.x + } + + pub fn i_plus_1(&self) -> Self { + let i = self.i(); + let j = self.j(); + Self::from_i_j(i + 1, j) + } + + pub fn j_plus_1(&self) -> Self { + let i = self.i(); + let j = self.j(); + Self::from_i_j(i, j + 1) + } + + pub fn from_x_y>(x: T, y: T) -> Self + where + T::Error: std::fmt::Debug, + { + Self { + x: x.try_into().expect("Conversion failed") as usize, + y: y.try_into().expect("Conversion failed") as usize, + } + } + + pub fn from_row_col>(row: T, col: T) -> Self + where + T::Error: std::fmt::Debug, + { + Self::from_x_y(col, row) + } + + pub fn from_i_j>(i: T, j: T) -> Self + where + T::Error: std::fmt::Debug, + { + Self::from_x_y(j, i) + } +} + +pub fn rgb_to_hex(r: f32, g: f32, b: f32) -> String { + let r = clamp(r, 0.0, 1.0); + let g = clamp(g, 0.0, 1.0); + let b = clamp(b, 0.0, 1.0); + + let r = (r * 255.0) as u8; + let g = (g * 255.0) as u8; + let b = (b * 255.0) as u8; + + format!("#{:02X}{:02X}{:02X}", r, g, b) +} + +pub trait MurreletIterHelpers { + type T: Clone; + fn to_iter<'a>(&'a self) -> std::slice::Iter<'a, Self::T>; + fn as_vec_ref(&self) -> &Vec; + + fn map_iter_collect(&self, f: F) -> Vec + where + F: Fn(&Self::T) -> U, + { + self.to_iter().map(f).collect_vec() + } + + fn owned_iter(&self) -> std::vec::IntoIter { + self.to_iter().cloned().collect_vec().into_iter() + } + + fn take_count(&self, amount: usize) -> Vec { + self.owned_iter().take(amount).collect::>() + } + + fn prev_curr_next_loop_iter<'a>( + &'a self, + ) -> Box + 'a> { + prev_curr_next_loop_iter(self.as_vec_ref()) + } + + fn prev_curr_next_no_loop_iter<'a>( + &'a self, + ) -> Box + 'a> { + prev_curr_next_no_loop_iter(self.as_vec_ref()) + } + + fn curr_next_loop_iter<'a>( + &'a self, + ) -> Box + 'a> { + curr_next_loop_iter(self.as_vec_ref()) + } + + fn curr_next_no_loop_iter<'a>( + &'a self, + ) -> Box + 'a> { + curr_next_no_loop_iter(self.as_vec_ref()) + } +} + +impl MurreletIterHelpers for Vec { + type T = T; + fn to_iter<'a>(&'a self) -> std::slice::Iter<'a, T> { + self.iter() + } + + fn as_vec_ref(&self) -> &Vec { + self + } +} diff --git a/murrelet_common/src/metric.rs b/murrelet_common/src/metric.rs index 1cbdd88..6a4aecd 100644 --- a/murrelet_common/src/metric.rs +++ b/murrelet_common/src/metric.rs @@ -77,6 +77,67 @@ impl Default for BoundMetricF32 { } } +#[derive(Debug, Copy, Clone)] +pub struct BoundMetricUsize { + min: usize, + max: usize, + count: usize, + sum: usize, +} +impl BoundMetricUsize { + pub fn new() -> BoundMetricUsize { + BoundMetricUsize { + min: usize::MAX, + max: usize::MIN, + count: 0, + sum: 0, + } + } + + pub fn new_init(x: usize) -> BoundMetricUsize { + let mut a = Self::new(); + a.add_point(x); + a + } + + pub fn add_point(&mut self, x: usize) { + if x < self.min { + self.min = x + } + if x > self.max { + self.max = x + } + self.count += 1; + self.sum += x; + } + + pub fn size(&self) -> usize { + self.max - self.min + } + + pub fn scale(&self) -> usize { + self.size() + } + + pub fn min(&self) -> usize { + self.min + } + + pub fn max(&self) -> usize { + self.max + } + + pub fn count(&self) -> usize { + self.count + } +} + +impl Default for BoundMetricUsize { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug, Copy, Clone)] pub struct BoundMetric { x_bound: BoundMetricF32, @@ -193,4 +254,12 @@ impl BoundMetric { self.add_point(other.lower_left()); self.add_point(other.upper_right()); } + + pub fn min(&self) -> Vec2 { + vec2(self.x_min(), self.y_min()) + } + + pub fn max(&self) -> Vec2 { + vec2(self.x_max(), self.y_max()) + } } diff --git a/murrelet_common/src/transform.rs b/murrelet_common/src/transform.rs index 44024cf..7a43811 100644 --- a/murrelet_common/src/transform.rs +++ b/murrelet_common/src/transform.rs @@ -1,12 +1,9 @@ -//! Since I need to do so much transforms of vec2, this -//! trait just makes it easier to use different types to -//! do that. use glam::{vec2, vec3, Mat2, Mat3, Mat4, Vec2, Vec3}; use itertools::Itertools; use lerpable::Lerpable; use crate::{ - lerp, + approx_eq_eps, lerp, polyline::{IsPolyline, Polyline}, vec_lerp, AnglePi, IsAngle, }; @@ -58,9 +55,9 @@ pub fn mat4_from_mat3_transform(m: Mat3) -> Mat4 { // i don't know about this.. // println!("_z_z {:?}", _z_z); if z_z != 1.0 { - println!("trying to turn a mat3 to mat4 with invalid values"); - println!("z_z {:?}", z_z); - println!("m {:?}", m); + // println!("trying to turn a mat3 to mat4 with invalid values"); + // println!("z_z {:?}", z_z); + // println!("m {:?}", m); } let z_axis = vec3(z_x, z_y, 0.0); @@ -108,10 +105,22 @@ impl SimpleTransform2dStep { Self::Translate(v) } + pub fn rotate_pi(angle_pi: A) -> Self { + Self::Rotate(Vec2::ZERO, angle_pi.as_angle_pi()) + } + pub fn scale_both(v: f32) -> Self { Self::Scale(Vec2::ONE * v) } + pub fn reflect_x() -> Self { + Self::Scale(vec2(-1.0, 1.0)) + } + + pub fn reflect_y() -> Self { + Self::Scale(vec2(1.0, -1.0)) + } + pub fn transform(&self) -> Mat3 { match self { Self::Translate(v) => Mat3::from_translation(*v), @@ -157,6 +166,25 @@ impl Lerpable for SimpleTransform2dStep { } } +pub trait Transformable { + fn transform_with(&self, t: &T) -> Self; +} + +impl Transformable for Vec2 { + fn transform_with(&self, t: &T) -> Self { + t.to_simple_transform().transform_vec2(*self) + } +} + +impl Transformable for Vec +where + A: Transformable, +{ + fn transform_with(&self, t: &T) -> Self { + self.iter().map(|x| x.transform_with(t)).collect_vec() + } +} + #[derive(Clone, Debug)] pub struct SimpleTransform2d(Vec); impl SimpleTransform2d { @@ -164,22 +192,40 @@ impl SimpleTransform2d { Self(v) } + pub fn rotate(angle_pi: AnglePi) -> Self { + Self(vec![SimpleTransform2dStep::rotate_pi(angle_pi)]) + } + + pub fn rotate_pi(angle_pi: f32) -> Self { + Self(vec![SimpleTransform2dStep::rotate_pi(AnglePi::new( + angle_pi, + ))]) + } + + pub fn noop() -> Self { + Self(vec![]) + } + pub fn steps(&self) -> &Vec { &self.0 } - pub fn add_after(&self, transform_vertex: &SimpleTransform2d) -> SimpleTransform2d { + pub fn add_transform_after(&self, other: &F) -> SimpleTransform2d { // just append let v = self .0 .iter() - .chain(transform_vertex.0.iter()) + .chain(other.to_simple_transform().0.iter()) .cloned() .collect(); SimpleTransform2d(v) } + pub fn add_transform_before(&self, other: &F) -> SimpleTransform2d { + other.to_simple_transform().add_transform_after(self) + } + pub fn ident() -> SimpleTransform2d { SimpleTransform2d(vec![]) } @@ -187,12 +233,79 @@ impl SimpleTransform2d { pub fn translate(v: Vec2) -> Self { Self(vec![SimpleTransform2dStep::Translate(v)]) } + + pub fn to_mat3(&self) -> Mat3 { + self.0 + .iter() + .fold(Mat3::IDENTITY, |acc, el| el.transform() * acc) + } + + pub fn to_mat4(&self) -> Mat4 { + mat4_from_mat3_transform(self.to_mat3()) + } + + pub fn is_similarity_transform(&self) -> bool { + for s in &self.0 { + match s { + SimpleTransform2dStep::Scale(v2) => { + if !approx_eq_eps(v2.x.abs(), v2.y.abs(), 1.0e-3) { + return false; + } + } + SimpleTransform2dStep::Skew(_, _) => return false, + _ => {} + } + } + true + } + + // experimental + pub fn approx_scale(&self) -> f32 { + let mut scale = 1.0; + for a in &self.0 { + match a { + SimpleTransform2dStep::Translate(_) => {} + SimpleTransform2dStep::Rotate(_, _) => {} + SimpleTransform2dStep::Scale(s) => scale *= s.x.max(s.y), + SimpleTransform2dStep::Skew(_, _) => todo!(), + } + } + scale + } + + pub fn approx_rotate(&self) -> AnglePi { + let mut rotate = AnglePi::new(0.0); + for a in &self.0 { + match a { + SimpleTransform2dStep::Translate(_) => {} + SimpleTransform2dStep::Rotate(_, s) => rotate = rotate + *s, + SimpleTransform2dStep::Scale(_) => {} + SimpleTransform2dStep::Skew(_, _) => todo!(), + } + } + rotate + } +} + +pub trait ToSimpleTransform { + fn to_simple_transform(&self) -> SimpleTransform2d; } -impl TransformVec2 for SimpleTransform2d { +impl ToSimpleTransform for SimpleTransform2d { + fn to_simple_transform(&self) -> SimpleTransform2d { + self.clone() + } +} + +impl TransformVec2 for T +where + T: ToSimpleTransform, +{ fn transform_vec2(&self, v: Vec2) -> Vec2 { + let s = self.to_simple_transform(); + let mut v = v; - for step in &self.0 { + for step in &s.0 { v = step.transform().transform_vec2(v); } v diff --git a/murrelet_common/src/triangulate.rs b/murrelet_common/src/triangulate.rs new file mode 100644 index 0000000..35edd35 --- /dev/null +++ b/murrelet_common/src/triangulate.rs @@ -0,0 +1,133 @@ +use bytemuck::{Pod, Zeroable}; +use glam::{vec2, Vec2, Vec3}; +use lerpable::Lerpable; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Zeroable, Pod)] +pub struct DefaultVertex { + pub position: [f32; 3], + pub normal: [f32; 3], + pub face_pos: [f32; 2], +} + +impl DefaultVertex { + pub fn new(position: [f32; 3], normal: [f32; 3], face_pos: [f32; 2]) -> Self { + Self { + position, + normal, + face_pos, + } + } + pub fn pos(&self) -> [f32; 3] { + self.position + } + + pub fn pos_vec3(&self) -> Vec3 { + glam::vec3(self.position[0], self.position[1], self.position[2]) + } + + pub fn pos2d(&self) -> Vec2 { + vec2(self.position[0], self.position[1]) + } + + pub fn attrs(&self) -> Vec { + vec![ + self.normal[0], + self.normal[1], + self.normal[2], + self.face_pos[0], + self.face_pos[1], + ] + } +} + +impl Lerpable for DefaultVertex { + fn lerpify(&self, other: &Self, pct: &T) -> Self { + DefaultVertex { + position: [ + self.position[0].lerpify(&other.position[0], pct), + self.position[1].lerpify(&other.position[1], pct), + self.position[2].lerpify(&other.position[2], pct), + ], + normal: [ + self.normal[0].lerpify(&other.normal[0], pct), + self.normal[1].lerpify(&other.normal[1], pct), + self.normal[2].lerpify(&other.normal[2], pct), + ], + face_pos: [ + self.face_pos[0].lerpify(&other.face_pos[0], pct), + self.face_pos[1].lerpify(&other.face_pos[1], pct), + ], + } + } +} + +#[derive(Debug, Clone)] +pub struct Triangulate { + pub vertices: Vec, + pub order: Vec, +} + +impl Default for Triangulate { + fn default() -> Self { + Self::new() + } +} + +impl Triangulate { + pub fn new() -> Self { + Triangulate { + vertices: vec![], + order: vec![], + } + } + + pub fn new_from_vertices_indices(vertices: Vec, order: Vec) -> Self { + Triangulate { vertices, order } + } + + pub fn add_many_vertices_and_offset(&mut self, vertices: Vec, indices: Vec) { + let vertex_offset = self.vertices.len() as u32; + self.vertices.extend(vertices); + self.order + .extend(indices.iter().map(|i| *i + vertex_offset)); + } + + pub fn vertices(&self) -> &[Vertex] { + &self.vertices + } + + pub fn add_vertex_simple(&mut self, vv: Vertex) -> u32 { + self.vertices.push(vv); + (self.vertices.len() - 1) as u32 + } + + pub fn add_tri(&mut self, tri: [u32; 3]) { + self.order.extend(tri) + } + + pub fn set_order(&mut self, u: Vec) { + self.order = u; + } + + pub fn order(&self) -> &[u32] { + &self.order + } + + pub fn indices(&self) -> &[u32] { + &self.order + } + + pub fn add_order(&mut self, collect: &[u32]) { + self.order.extend_from_slice(collect); + } + + pub fn add_rect_simple(&mut self, ids: &[u32; 4], flip: bool) { + let [v0, v1, v3, v2] = ids; + if !flip { + self.order.extend([v0, v2, v1, v1, v2, v3]) + } else { + self.order.extend([v0, v1, v2, v1, v3, v2]) + } + } +} diff --git a/murrelet_draw/Cargo.toml b/murrelet_draw/Cargo.toml index 78643bd..2e39a85 100644 --- a/murrelet_draw/Cargo.toml +++ b/murrelet_draw/Cargo.toml @@ -8,16 +8,22 @@ description = "drawing functions for murrelet, a livecode framework" license = "AGPL-3.0-or-later" [features] -schemars = ["dep:schemars", "murrelet_livecode/schemars", "murrelet_livecode_macros/schemars", "murrelet_livecode_derive/schemars"] +schemars = [ + "dep:schemars", + "murrelet_livecode/schemars", + "murrelet_livecode_macros/schemars", + "murrelet_livecode_derive/schemars", +] + [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } +murrelet_common = { workspace = true } -murrelet_livecode = { version = "0.1.2", path = "../murrelet_livecode/", default-features = false } -murrelet_livecode_macros = { version = "0.1.2", path = "../murrelet_livecode_macros/", default-features = false } -murrelet_livecode_derive = { version = "0.1.2", path = "../murrelet_livecode_macros/murrelet_livecode_derive/", default-features = false } +murrelet_livecode = { workspace = true, default-features = false } +murrelet_livecode_macros = { workspace = true, default-features = false } +murrelet_livecode_derive = { workspace = true, default-features = false } -lerpable = "0.0.2" +lerpable = { version = "0.0.3", features = ["glam"] } serde = { version = "1.0.104", features = ["derive"] } serde_yaml = "0.9.17" @@ -32,3 +38,13 @@ md-5 = "0.10.6" hex = "0.4.3" schemars = { version = "0.8.21", optional = true } +murrelet_gui = { workspace = true, features = ["glam", "murrelet"] } + +kurbo = "0.11" +svg = "0.10.0" +lyon = "0.17" +delaunator = "1.0.2" +geo = "0.29.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true } diff --git a/murrelet_draw/src/compass.rs b/murrelet_draw/src/compass.rs index 2043a76..4869752 100644 --- a/murrelet_draw/src/compass.rs +++ b/murrelet_draw/src/compass.rs @@ -1,26 +1,35 @@ #![allow(dead_code)] +use std::collections::HashMap; + +use glam::vec2; use glam::Vec2; +use itertools::Itertools; use lerpable::Lerpable; use murrelet_common::*; +use murrelet_gui::make_gui_angle; +use murrelet_gui::make_gui_vec2; +use murrelet_gui::MurreletGUI; +use murrelet_gui::MurreletGUISchema; +use murrelet_gui::ValueGUI; use murrelet_livecode_derive::Livecode; -use crate::{ - curve_drawer::{CurveArc, CurveDrawer, CurvePoints, CurveSegment}, - livecodetypes::anglepi::*, -}; +use crate::cubic::CubicBezier; +use crate::curve_drawer::ToCurveSegment; +use crate::curve_drawer::{CurveArc, CurveDrawer, CurvePoints, CurveSegment}; -#[derive(Debug, Clone, Copy, Livecode, Lerpable)] +#[derive(Debug, Clone, Copy, Livecode, MurreletGUI, Lerpable)] pub struct CurveStart { - #[lerpable(func = "lerpify_vec2")] + #[murrelet_gui(func = "make_gui_vec2")] loc: Vec2, - angle_pi: LivecodeAnglePi, + #[murrelet_gui(func = "make_gui_angle")] + angle_pi: AnglePi, } impl CurveStart { pub fn new(loc: Vec2, angle: A) -> Self { Self { loc, - angle_pi: LivecodeAnglePi::new(angle), + angle_pi: angle.as_angle_pi(), } } } @@ -29,61 +38,96 @@ fn empty_string() -> String { String::new() } -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct CompassDir { - angle_pi: LivecodeAnglePi, + #[murrelet_gui(func = "make_gui_angle")] + pub angle_pi: AnglePi, #[livecode(serde_default = "false")] - is_absolute: bool, + #[murrelet_gui(kind = "skip")] + pub is_absolute: bool, #[livecode(serde_default = "murrelet_livecode::livecode::empty_string")] - label: String, + #[murrelet_gui(kind = "skip")] + pub label: String, } impl CompassDir { pub fn new(angle: A, is_absolute: bool, label: String) -> Self { Self { - angle_pi: LivecodeAnglePi::new(angle), + angle_pi: angle.as_angle_pi(), is_absolute, label, } } } -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct CompassArc { - radius: f32, - arc_length: LivecodeAnglePi, + pub radius: f32, + #[murrelet_gui(func = "make_gui_angle")] + pub arc_length: AnglePi, #[livecode(serde_default = "false")] - is_absolute: bool, + #[murrelet_gui(kind = "skip")] + pub is_absolute: bool, #[livecode(serde_default = "murrelet_livecode::livecode::empty_string")] - label: String, + #[murrelet_gui(kind = "skip")] + pub label: String, } -// impl CompassArc { -// pub fn new(radius: f32, arc_length: f32, is_absolute: bool) -> Self { Self { radius, arc_length, is_absolute } } -// } +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] +pub struct CompassBezier { + dist: f32, + #[murrelet_gui(func = "make_gui_angle")] + angle: AnglePi, + #[murrelet_gui(func = "make_gui_vec2")] + strengths: Vec2, + #[livecode(serde_default = "murrelet_livecode::livecode::empty_string")] + #[murrelet_gui(kind = "skip")] + pub label: String, +} -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct CompassLine { - length: f32, // how far should we head in the current direction + pub length: f32, // how far should we head in the current direction #[livecode(serde_default = "murrelet_livecode::livecode::empty_string")] - label: String, + #[murrelet_gui(kind = "skip")] + pub label: String, +} + +pub fn make_gui_vec_vec2() -> MurreletGUISchema { + MurreletGUISchema::list(MurreletGUISchema::Val(ValueGUI::Vec2)) } -#[derive(Debug, Clone, Livecode, Lerpable)] -pub struct CompassRepeat { - times: usize, - what: Vec, +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] +pub struct CompassAbsPoints { + #[lerpable(func = "lerpify_vec_vec2")] + #[murrelet_gui(func = "make_gui_vec_vec2")] + pts: Vec, + #[livecode(serde_default = "murrelet_livecode::livecode::empty_string")] + #[murrelet_gui(kind = "skip")] + label: String, } -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub enum CompassAction { Angle(CompassDir), // abs Arc(CompassArc), Line(CompassLine), - Repeat(CompassRepeat), + Bezier(CompassBezier), + AbsPoints(CompassAbsPoints), // Repeat(CompassRepeat), // now this is in the control vec! } impl CompassAction { + pub fn qabspts(pts: &[Vec2]) -> CompassAction { + CompassAction::abspts(pts, "".to_string()) + } + + pub fn abspts(pts: &[Vec2], label: String) -> CompassAction { + CompassAction::AbsPoints(CompassAbsPoints { + pts: pts.to_vec(), + label, + }) + } + pub fn qangle(angle_pi: A) -> CompassAction { CompassAction::angle(angle_pi, false, "".to_string()) } @@ -98,7 +142,7 @@ impl CompassAction { pub fn angle(angle_pi: A, is_absolute: bool, label: String) -> CompassAction { CompassAction::Angle(CompassDir { - angle_pi: LivecodeAnglePi::new(angle_pi), + angle_pi: angle_pi.as_angle_pi(), is_absolute, label, }) @@ -112,7 +156,7 @@ impl CompassAction { ) -> CompassAction { CompassAction::Arc(CompassArc { radius, - arc_length: LivecodeAnglePi::new(arc_length_pi), + arc_length: arc_length_pi.as_angle_pi(), is_absolute, label, }) @@ -121,26 +165,47 @@ impl CompassAction { pub fn line(length: f32, label: String) -> CompassAction { CompassAction::Line(CompassLine { length, label }) } - - pub fn repeat(times: usize, what: Vec) -> CompassAction { - CompassAction::Repeat(CompassRepeat { times, what }) - } } impl Default for CompassAction { fn default() -> Self { CompassAction::Angle(CompassDir { - angle_pi: LivecodeAnglePi::ZERO, + angle_pi: AnglePi::ZERO, is_absolute: false, label: String::new(), }) } } +#[derive(Clone, Debug)] +pub enum LastActionNames { + Arc, + Line, + Bezier, + Start, +} +impl LastActionNames { + pub fn from_segment(c: &CurveSegment) -> LastActionNames { + match c { + CurveSegment::Arc(_) => LastActionNames::Arc, + CurveSegment::Points(_) => LastActionNames::Line, + CurveSegment::CubicBezier(_) => LastActionNames::Bezier, + } + } +} + pub struct InteractiveCompassBuilder { pen_down: bool, // if we've drawn one curr_loc: Vec2, curr_angle: AnglePi, so_far: Vec, + references: HashMap, + last_action: LastActionNames, +} + +impl Default for InteractiveCompassBuilder { + fn default() -> Self { + Self::new() + } } impl InteractiveCompassBuilder { @@ -150,6 +215,8 @@ impl InteractiveCompassBuilder { curr_loc: Vec2::ZERO, curr_angle: AnglePi::new(0.0), so_far: Vec::new(), + references: HashMap::new(), + last_action: LastActionNames::Start, } } @@ -162,8 +229,13 @@ impl InteractiveCompassBuilder { self.add_curve_start_simple(start.loc, start.angle_pi); } + pub fn add_curve_start_spot(&mut self, spot: SpotOnCurve) { + self.add_curve_start_simple(spot.loc, spot.angle); + } + pub fn new_segments(&mut self, dir: &CompassAction) -> Vec { // here we go! + match dir { CompassAction::Angle(x) => { self.set_angle(x); @@ -175,21 +247,39 @@ impl InteractiveCompassBuilder { CompassAction::Arc(x) => { vec![self.add_arc(x)] } - CompassAction::Repeat(x) => { - let mut n = Vec::new(); - for _ in 0..x.times { - for w in &x.what { - n.extend(self.new_segments(w)) - } - } - n + CompassAction::Bezier(x) => { + vec![self.add_bezier(x)] } + CompassAction::AbsPoints(x) => match self.add_abs_pts(x) { + Some(x) => vec![x], + None => vec![], + }, } } + pub fn add_qline(&mut self, length: f32) { + self.add_segment(&CompassAction::qline(length)); + } + + pub fn add_qangle(&mut self, angle: A) { + self.add_segment(&CompassAction::qangle(angle)); + } + + pub fn add_qarc(&mut self, rad: f32, arc: A) { + self.add_segment(&CompassAction::qarc(rad, arc)); + } + pub fn add_segment(&mut self, dir: &CompassAction) { - let r = { self.new_segments(dir) }; + let r = self.new_segments(dir); self.so_far.extend(r); + + self.last_action = match dir { + CompassAction::Angle(_) => self.last_action.clone(), // don't change the action + CompassAction::Arc(_) => LastActionNames::Arc, + CompassAction::Line(_) => LastActionNames::Line, + CompassAction::Bezier(_) => LastActionNames::Bezier, + CompassAction::AbsPoints(_) => LastActionNames::Line, + } } fn set_angle(&mut self, dir: &CompassDir) { @@ -203,7 +293,7 @@ impl InteractiveCompassBuilder { fn to_basic(&self) -> CurveStart { CurveStart { loc: self.curr_loc, - angle_pi: LivecodeAnglePi::new(self.curr_angle), + angle_pi: self.curr_angle.as_angle_pi(), } } @@ -212,62 +302,99 @@ impl InteractiveCompassBuilder { let mut points = vec![]; if !self.pen_down { points.push(self.curr_loc) + } else if !matches!(self.last_action, LastActionNames::Line) { + points.push(self.curr_loc) } // next point is going to take the current angle, and move in that direction - let movement = self.use_angle_and_length(self.curr_angle, x.length); + let movement = self.curr_angle.to_norm_dir() * x.length; self.curr_loc += movement; points.push(self.curr_loc); self.pen_down = true; - // trying granular to see if we can mask + if !x.label.is_empty() { + self.references.insert(x.label.clone(), self.to_basic()); + } + + // know there's at least one point so we can unwrap CurveSegment::Points(CurvePoints::new(points)) } - fn use_angle_and_length(&self, angle: A, length: f32) -> Vec2 { - angle.as_angle_pi().to_norm_dir() * length + fn curr_spot(&self) -> SpotOnCurve { + SpotOnCurve::new(self.curr_loc, self.curr_angle) + } + + fn add_bezier(&mut self, x: &CompassBezier) -> CurveSegment { + let first_spot = self.curr_spot(); + + // next point is going to take the current angle, and move in that direction + let last_spot = first_spot.rotate(x.angle).travel(x.dist); + + let bezier = + CubicBezier::from_spots_s(first_spot, last_spot, x.strengths * vec2(1.0, -1.0)); + + self.curr_loc = last_spot.loc; + self.curr_angle = last_spot.angle().as_angle_pi(); + + self.pen_down = true; + + if !x.label.is_empty() { + self.references.insert(x.label.clone(), self.to_basic()); + } + bezier.to_segment() } fn add_arc(&mut self, x: &CompassArc) -> CurveSegment { - let angle_pi = x.arc_length.as_angle_pi(); - let (arc_length, radius) = if angle_pi.is_neg() { - (-angle_pi, -x.radius) - } else { - (angle_pi, x.radius) - }; + let arc = CurveArc::from_spot( + SpotOnCurve::new(self.curr_loc, self.curr_angle), + x.radius, + x.arc_length, + ); + let new_spot = arc.last_spot(); + + self.curr_loc = new_spot.loc; + self.curr_angle = new_spot.angle().mod2(); + self.pen_down = true; - // starting at our current location, move at a right angle to our current angle - // negative goes to the left of the line - let loc = - self.curr_loc + self.use_angle_and_length(self.curr_angle + AnglePi::new(0.5), radius); - - // if radius is negative, go backwards - // end_angle is what we'll update curr angle to, it's always assuming positive radius - let (start, end, next_angle) = if radius < 0.0 { - let next_angle = self.curr_angle - arc_length; - ( - AnglePi::new(1.0) + self.curr_angle - AnglePi::new(0.5), - AnglePi::new(1.0) + next_angle - AnglePi::new(0.5), - next_angle, - ) - } else { - let next_angle = self.curr_angle + arc_length; - ( - self.curr_angle - AnglePi::new(0.5), - next_angle - AnglePi::new(0.5), - next_angle, - ) - }; + if !x.label.is_empty() { + self.references.insert(x.label.clone(), self.to_basic()); + } - let a = CurveArc::new(loc, radius.abs(), start, end); + CurveSegment::Arc(arc) + } + + fn add_abs_pts(&mut self, x: &CompassAbsPoints) -> Option { + match x.pts.as_slice() { + [.., penultimate, last] => { + let pt = PointToPoint::new(*penultimate, *last).angle().as_angle_pi(); + self.curr_angle = pt; + self.curr_loc = *last; + } + [last] => { + if last.distance(self.curr_loc) > 0.001 { + let penultimate = self.curr_loc; + let pt = PointToPoint::new(penultimate, *last).angle().as_angle_pi(); + self.curr_angle = pt; + self.curr_loc = *last; + } else { + // not enough points to update the angle... + self.curr_loc = *last; + } + } + _ => { + return None; // not enough items + } + }; - self.curr_loc = a.last_point(); - self.curr_angle = AnglePi::new(next_angle.angle_pi() % 2.0); self.pen_down = true; - CurveSegment::Arc(a) + if !x.label.is_empty() { + self.references.insert(x.label.clone(), self.to_basic()); + } + + Some(CurveSegment::Points(CurvePoints::new(x.pts.clone()))) } pub fn results(&self) -> Vec { @@ -278,8 +405,8 @@ impl InteractiveCompassBuilder { self.curr_loc } - pub fn curr_angle(&self) -> f32 { - self.curr_angle.angle_pi() + pub fn curr_angle(&self) -> AnglePi { + self.curr_angle } pub fn set_curr_loc(&mut self, curr_loc: Vec2) { @@ -289,9 +416,18 @@ impl InteractiveCompassBuilder { pub fn set_curr_angle(&mut self, curr_angle: A) { self.curr_angle = curr_angle.as_angle_pi(); } + + pub fn add_absolute_point(&mut self, loc: Vec2) { + self.so_far + .push(CurveSegment::Points(CurvePoints { points: vec![loc] })) + } + + pub fn references(&self) -> Vec<(String, CurveStart)> { + self.references.clone().into_iter().collect_vec() + } } -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct MurreletCompass { start: CurveStart, dirs: Vec, @@ -313,7 +449,7 @@ impl MurreletCompass { let start = self.start; builder.add_curve_start(start); for w in self.dirs.iter() { - builder.add_segment(&w) + builder.add_segment(w) } CurveDrawer::new(builder.results(), self.closed) diff --git a/murrelet_draw/src/cubic.rs b/murrelet_draw/src/cubic.rs new file mode 100644 index 0000000..8764d77 --- /dev/null +++ b/murrelet_draw/src/cubic.rs @@ -0,0 +1,171 @@ +use glam::Vec2; +use murrelet_common::{Angle, IsAngle, SpotOnCurve}; + +use crate::svg::glam_to_lyon; + +#[derive(Debug, Clone, Copy)] +pub struct CubicBezier { + pub from: Vec2, + pub ctrl1: Vec2, + pub ctrl2: Vec2, + pub to: Vec2, +} +impl CubicBezier { + pub fn safe_from_spots_s( + in_spot: SpotOnCurve, + out_spot: SpotOnCurve, + strength: Vec2, + ) -> Option { + if in_spot.loc.distance(out_spot.loc) < 1.0e-3 { + None + } else { + Some(Self::from_spots_s(in_spot, out_spot, strength)) + } + } + + pub fn from_spots_s(in_spot: SpotOnCurve, out_spot: SpotOnCurve, strength: Vec2) -> Self { + Self::from_spots(in_spot, strength.x, out_spot, strength.y) + } + + pub fn from_spots( + in_spot: SpotOnCurve, + in_strength: f32, + out_spot: SpotOnCurve, + out_strength: f32, + ) -> Self { + let norm_dist = in_spot.loc().distance(out_spot.loc()); + + let ctrl1 = in_spot.loc() + in_spot.angle().to_norm_dir() * in_strength * norm_dist; + let ctrl2 = out_spot.loc() + out_spot.angle().to_norm_dir() * out_strength * norm_dist; + + Self { + from: in_spot.loc(), + ctrl1, + ctrl2, + to: out_spot.loc(), + } + } + + pub fn new(from: Vec2, ctrl1: Vec2, ctrl2: Vec2, to: Vec2) -> Self { + Self { + from, + ctrl1, + ctrl2, + to, + } + } + + pub fn line(from: Vec2, to: Vec2) -> Self { + Self { + from, + ctrl1: from, + ctrl2: to, + to, + } + } + + pub fn split(&self, t: f32) -> (CubicBezier, CubicBezier) { + let mid1 = self.from.lerp(self.ctrl1, t); + let mid2 = self.ctrl1.lerp(self.ctrl2, t); + let mid3 = self.ctrl2.lerp(self.to, t); + + let mid12 = mid1.lerp(mid2, t); + let mid23 = mid2.lerp(mid3, t); + + let mid123 = mid12.lerp(mid23, t); + + ( + CubicBezier { + from: self.from, + ctrl1: mid1, + ctrl2: mid12, + to: mid123, + }, + CubicBezier { + from: mid123, + ctrl1: mid23, + ctrl2: mid3, + to: self.to, + }, + ) + } + + pub fn loc_at_pct(&self, t: f32) -> Vec2 { + let (a, _) = self.split(t); + a.to + } + + pub fn start_to_tangent(&self) -> (SpotOnCurve, f32) { + let ctrl_line = self.from - self.ctrl1; + let dir = Angle::new(ctrl_line.to_angle()) + .as_angle_pi() + .normalize_angle(); + + ( + SpotOnCurve { + loc: self.from, + angle: dir.into(), + }, + ctrl_line.length(), + ) + } + + pub fn end_to_tangent(&self) -> (SpotOnCurve, f32) { + let ctrl_line = self.ctrl2 - self.to; + let dir = Angle::new(ctrl_line.to_angle()) + .as_angle_pi() + .normalize_angle(); + + ( + SpotOnCurve { + loc: self.to, + angle: dir.into(), + }, + ctrl_line.length(), + ) + } + + pub fn tangent_at_pct(&self, pct: f32) -> SpotOnCurve { + let (start, _) = self.split(pct); + let (t, _a) = start.end_to_tangent(); + t + } + + pub fn reverse(&self) -> CubicBezier { + CubicBezier { + from: self.to, + ctrl1: self.ctrl2, + ctrl2: self.ctrl1, + to: self.from, + } + } + + pub fn tangent_at_pct_safe(&self, pct: f32) -> SpotOnCurve { + if pct < 0.01 { + self.start_to_tangent().0 + } else if pct > 0.99 { + self.end_to_tangent().0 + } else { + self.tangent_at_pct(pct) + } + } + + pub fn apply_vec2_tranform(&self, f: impl Fn(Vec2) -> Vec2) -> Self { + Self { + from: f(self.from), + ctrl1: f(self.ctrl1), + ctrl2: f(self.ctrl2), + to: f(self.to), + } + } + + pub fn approx_length(&self) -> f32 { + let lyon_cubic = lyon::geom::CubicBezierSegment { + from: glam_to_lyon(self.from), + ctrl1: glam_to_lyon(self.ctrl1), + ctrl2: glam_to_lyon(self.ctrl2), + to: glam_to_lyon(self.to), + }; + lyon_cubic.approximate_length(0.1) + } +} diff --git a/murrelet_draw/src/curve_drawer.rs b/murrelet_draw/src/curve_drawer.rs index 4f08659..a59ffd0 100644 --- a/murrelet_draw/src/curve_drawer.rs +++ b/murrelet_draw/src/curve_drawer.rs @@ -1,9 +1,171 @@ use glam::*; +use itertools::Itertools; use lerpable::Lerpable; +use lyon::path::Path; use murrelet_common::*; +use murrelet_livecode::types::{LivecodeError, LivecodeResult}; use murrelet_livecode_derive::*; +use serde::{Deserialize, Serialize}; +use svg::node::element::path::Data; -use crate::livecodetypes::anglepi::*; +use crate::{ + cubic::CubicBezier, + newtypes::*, + tesselate::{ + cubic_bezier_path_to_lyon, flatten_cubic_bezier_path, + flatten_cubic_bezier_path_with_tolerance, parse_svg_data_as_vec2, segment_arc, segment_vec, + ToVecVec2, + }, +}; + +#[derive(Debug, Clone, Default, Livecode, Lerpable, Serialize, Deserialize)] +pub enum CubicOptionVec2 { + #[default] + None, + Some(Vec2Newtype), +} +impl CubicOptionVec2 { + pub fn or_last(&self, anchor: Vec2, last_ctrl2: Vec2) -> Vec2 { + match self { + CubicOptionVec2::None => anchor * 2.0 - last_ctrl2, + CubicOptionVec2::Some(vec2_newtype) => vec2_newtype.vec2(), + } + } + + pub fn none() -> Self { + Self::None + } + + pub fn some(v: Vec2) -> Self { + Self::Some(Vec2Newtype::new(v)) + } +} + +#[derive(Debug, Clone, Default, Livecode, Lerpable, Serialize, Deserialize)] +pub struct CubicBezierTo { + pub ctrl1: CubicOptionVec2, + pub ctrl2: Vec2, + pub to: Vec2, +} + +impl CubicBezierTo { + pub fn new(ctrl1o: Option, ctrl2: Vec2, to: Vec2) -> Self { + let ctrl1 = match ctrl1o { + Some(c) => CubicOptionVec2::some(c), + None => CubicOptionVec2::none(), + }; + Self { ctrl1, ctrl2, to } + } +} + +#[derive(Debug, Clone, Default, Livecode, Lerpable, Serialize, Deserialize)] +pub struct CubicBezierPath { + pub from: Vec2, + pub ctrl1: Vec2, + pub curves: Vec, + pub closed: bool, +} +impl CubicBezierPath { + pub fn new(from: Vec2, ctrl1: Vec2, curves: Vec, closed: bool) -> Self { + Self { + from, + ctrl1, + curves, + closed, + } + } + + pub fn to_vec2_count(&self, count: usize) -> Vec { + let len = self.to_cd().length(); + let line_space = len / count as f32; + + let svg = self.to_data(); + let path = parse_svg_data_as_vec2(&svg, line_space); + + path.into_iter().map(|x| vec2(x.y, x.x)).collect_vec() + } + + pub fn to_cd(&self) -> CurveDrawer { + let mut cs = vec![]; + for c in self.to_cubic() { + cs.push(CurveSegment::cubic(c)); + } + CurveDrawer::new(cs, self.closed) + } + + pub fn to_cubic(&self) -> Vec { + let mut svg = vec![]; + + let mut from = self.from; + + let mut last_ctrl1 = self.ctrl1; + + let mut first_ctrl1_used: Option = None; + + for s in &self.curves { + let ctrl1 = s.ctrl1.or_last(from, last_ctrl1); + if first_ctrl1_used.is_none() { + first_ctrl1_used = Some(ctrl1); + } + svg.push(CubicBezier::new(from, ctrl1, s.ctrl2, s.to)); + last_ctrl1 = s.ctrl2; + from = s.to; + } + + if self.closed { + let ctrl1 = CubicOptionVec2::none().or_last(from, last_ctrl1); + let ctrl2_source = first_ctrl1_used.unwrap_or(self.ctrl1); + let ctrl2 = CubicOptionVec2::none().or_last(self.from, ctrl2_source); + svg.push(CubicBezier::new(from, ctrl1, ctrl2, self.from)); + } + + svg + } + + pub fn to_data(&self) -> Data { + let mut svg = svg::node::element::path::Data::new(); + + let x = self.from.x; + let y = self.from.y; + let start: svg::node::element::path::Parameters = vec![x, y].into(); + svg = svg.move_to(start); + + let mut last = self.from; + let mut last_ctrl1 = self.ctrl1; + + for s in &self.curves { + let ctrl1 = s.ctrl1.or_last(last, last_ctrl1); + + let cubic: svg::node::element::path::Parameters = + vec![ctrl1.x, ctrl1.y, s.ctrl2.x, s.ctrl2.y, s.to.x, s.to.y].into(); + svg = svg.cubic_curve_to(cubic); + + last_ctrl1 = s.ctrl2; + last = s.to; + } + svg + } + + pub fn to_lyon(&self) -> Option { + cubic_bezier_path_to_lyon(&self.to_cubic(), self.closed) + } + + pub fn to_vec2(&self) -> Option> { + flatten_cubic_bezier_path(&self.to_cubic(), self.closed) + } + + pub fn last_point(&self) -> Vec2 { + if let Some(last) = self.curves.last() { + last.to + } else { + self.from + } + } + + pub fn first_point(&self) -> Vec2 { + self.from + } +} #[derive(Debug, Clone, Livecode, Lerpable)] pub struct CurveDrawer { @@ -16,6 +178,13 @@ impl CurveDrawer { Self { segments, closed } } + pub fn new_closed(segments: Vec) -> Self { + Self { + segments, + closed: true, + } + } + pub fn is_closed(&self) -> bool { self.closed } @@ -74,19 +243,75 @@ impl CurveDrawer { pub fn noop() -> Self { Self::new(vec![], false) } + + pub fn first_point(&self) -> Option { + let first_command = self.segments().first()?; + Some(first_command.first_point()) + } + + pub fn last_point(&self) -> Option { + let last_command = self.segments().last()?; + Some(last_command.last_point()) + } + + pub fn length(&self) -> f32 { + self.segments.iter().map(|segment| segment.length()).sum() + } + + pub fn maybe_transform(&self, transform: &T) -> LivecodeResult { + let mut segments = vec![]; + + let transform = transform.to_simple_transform(); + + if !transform.is_similarity_transform() { + return Err(LivecodeError::raw("not a similarity transform")); + } + + for cd in &self.segments { + // we've ran our check, so we can just do it now.. + segments.push(cd.force_transform(&transform)); + } + + Ok(Self::new(segments, self.closed)) + } + + pub fn segments_pseudo_closed(&self) -> Vec { + let mut segments = self.segments().to_vec(); + // if it's closed, then add the first point, otherwise same as rest + if !self.closed { + segments + } else { + // if there's no first point, just return itself (which is empty...) + if let Some(first_point) = self.first_point() { + segments.push(CurveSegment::new_simple_point(first_point)); + } + segments + } + } } #[derive(Debug, Clone, Livecode, Lerpable)] pub enum CurveSegment { Arc(CurveArc), Points(CurvePoints), + CubicBezier(CurveCubicBezier), } impl CurveSegment { + pub fn arc(loc: Vec2, radius: f32, start_pi: A, end_pi: A) -> Self { + Self::Arc(CurveArc { + loc, + radius, + start_pi: start_pi.as_angle_pi(), + end_pi: end_pi.as_angle_pi(), + }) + } + pub fn first_point(&self) -> Vec2 { match self { CurveSegment::Arc(c) => c.first_point(), CurveSegment::Points(c) => c.first_point(), + CurveSegment::CubicBezier(c) => c.first_point(), } } @@ -94,6 +319,15 @@ impl CurveSegment { match self { CurveSegment::Arc(c) => c.last_point(), CurveSegment::Points(c) => c.last_point(), + CurveSegment::CubicBezier(c) => c.last_point(), + } + } + + pub fn reverse(&self) -> Self { + match self { + CurveSegment::Arc(curve_arc) => CurveSegment::Arc(curve_arc.reverse()), + CurveSegment::Points(curve_points) => CurveSegment::Points(curve_points.reverse()), + CurveSegment::CubicBezier(c) => CurveSegment::CubicBezier(c.reverse()), } } @@ -131,29 +365,187 @@ impl CurveSegment { match self { CurveSegment::Arc(_) => None, CurveSegment::Points(p) => Some(p.points.len()), + CurveSegment::CubicBezier(_) => None, + } + } + + pub fn first_angle(&self) -> Option { + self.first_spot().map(|x| x.angle().into()) // todo, return none if it's one point + } + + // if we have only one point, this is None + pub fn first_spot(&self) -> Option { + match self { + CurveSegment::Arc(arc) => { + let a = CurveArc::new(arc.loc, arc.radius, arc.start_pi, arc.end_pi); + Some(SpotOnCurve::new(a.first_point(), a.start_tangent_angle())) + } + CurveSegment::Points(points) => { + let vec2s = points.points(); + if vec2s.len() >= 2 { + let first = vec2s[0]; + let second = vec2s[1]; + + let angle = PointToPoint::new(first, second).angle().perp_to_left(); + Some(SpotOnCurve::new(first, angle)) + } else { + None + } + } + CurveSegment::CubicBezier(cubic_bezier) => { + Some(cubic_bezier.to_cubic().start_to_tangent().0) + } + } + } + + pub fn last_spot(&self) -> Option { + match self { + CurveSegment::Arc(arc) => Some(arc.last_spot()), + CurveSegment::Points(p) => { + if p.points().len() >= 2 { + let points = p.points(); + let end = *points.last().unwrap(); + let prev = *points.get(points.len() - 2).unwrap(); + let angle = PointToPoint::new(prev, end).angle(); + Some(SpotOnCurve::new(end, angle)) + } else { + None + } + } + CurveSegment::CubicBezier(c) => Some(c.to_cubic().end_to_tangent().0), + } + } + + pub fn cubic_bezier(from: Vec2, ctrl1: Vec2, ctrl2: Vec2, to: Vec2) -> Self { + Self::CubicBezier(CurveCubicBezier { + from, + ctrl1, + ctrl2, + to, + }) + } + + fn cubic(c: CubicBezier) -> CurveSegment { + Self::CubicBezier(CurveCubicBezier::from_cubic(c)) + } + + fn force_transform(&self, transform: &T) -> Self { + let transform = transform.to_simple_transform(); + match self { + CurveSegment::Arc(curve_arc) => { + CurveSegment::Arc(curve_arc.force_transform(&transform)) + } + CurveSegment::Points(curve_points) => { + CurveSegment::Points(curve_points.force_transform(&transform)) + } + CurveSegment::CubicBezier(curve_cubic_bezier) => { + CurveSegment::CubicBezier(curve_cubic_bezier.force_transform(&transform)) + } + } + } + + pub fn extend_before(&self, before_amount: f32) -> Self { + let mut c = self.clone(); + match &mut c { + CurveSegment::Arc(curve_arc) => { + let rads = Angle::new(before_amount / curve_arc.radius); + curve_arc.start_pi = curve_arc.start_pi - rads; + } + CurveSegment::Points(_) => todo!(), + CurveSegment::CubicBezier(_) => todo!(), + } + + c + } + + pub fn extend_after(&self, after_amount: f32) -> Self { + let mut c = self.clone(); + match &mut c { + CurveSegment::Arc(curve_arc) => { + let rads = Angle::new(after_amount / curve_arc.radius); + curve_arc.end_pi = curve_arc.end_pi + rads; + } + CurveSegment::Points(_) => todo!(), + CurveSegment::CubicBezier(_) => todo!(), + } + + c + } + + pub fn extend_both(&self, before_amount: f32, after_amount: f32) -> Self { + self.extend_before(before_amount).extend_after(after_amount) + } + + fn length(&self) -> f32 { + match self { + CurveSegment::CubicBezier(c) => c.length(), + CurveSegment::Points(p) => p.length(), + CurveSegment::Arc(a) => a.length(), + } + } + + fn split_segment(&self, dist: f32) -> (CurveSegment, CurveSegment) { + match self { + CurveSegment::CubicBezier(c) => c.split_segment(dist), + CurveSegment::Points(p) => p.split_segment(dist), + CurveSegment::Arc(a) => a.split_segment(dist), } } } -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Copy, Livecode, Lerpable)] pub struct CurveArc { #[livecode(serde_default = "zeros")] - #[lerpable(func = "lerpify_vec2")] pub loc: Vec2, // center of circle pub radius: f32, - pub start_pi: LivecodeAnglePi, - pub end_pi: LivecodeAnglePi, + pub start_pi: AnglePi, + pub end_pi: AnglePi, } impl CurveArc { pub fn new(loc: Vec2, radius: f32, start_pi: A1, end_pi: A2) -> Self { Self { loc, radius, - start_pi: LivecodeAnglePi::new(start_pi), - end_pi: LivecodeAnglePi::new(end_pi), + start_pi: start_pi.as_angle_pi(), + end_pi: end_pi.as_angle_pi(), } } + pub fn from_spot( + start_spot: SpotOnCurve, + raw_radius: f32, + raw_arc_length: A, + ) -> CurveArc { + let angle_pi = raw_arc_length.as_angle_pi(); + let (arc_length, radius) = if angle_pi.is_neg() { + (-angle_pi, -raw_radius) + } else { + (angle_pi, raw_radius) + }; + + // starting at our current location, move at a right angle to our current angle + // negative goes to the left of the line + let loc = start_spot.loc + start_spot.angle().perp_to_left().to_norm_dir() * radius; + + // if radius is negative, go backwards + // end_angle is what we'll update curr angle to, it's always assuming positive radius + let (start, end): (AnglePi, AnglePi) = if radius < 0.0 { + let next_angle = start_spot.angle - arc_length; + ( + AnglePi::new(1.0) + start_spot.angle - AnglePi::new(0.5), + AnglePi::new(1.0) + next_angle - AnglePi::new(0.5), + ) + } else { + let next_angle = start_spot.angle + arc_length; + ( + (start_spot.angle - AnglePi::new(0.5)).into(), + (next_angle - AnglePi::new(0.5)).into(), + ) + }; + + CurveArc::new(loc, radius.abs(), start, end) + } + pub fn is_in_arc(&self, angle: AnglePi) -> bool { let start_pi = self.start_pi.angle_pi(); let end_pi = self.end_pi.angle_pi(); @@ -176,9 +568,25 @@ impl CurveArc { self.end_pi.angle_pi() > self.start_pi.angle_pi() } + pub fn reverse(&self) -> Self { + Self { + start_pi: self.end_pi, + end_pi: self.start_pi, + ..*self + } + } + // useful for svg pub fn is_large_arc(&self) -> bool { - (self.end_pi.angle_pi() - self.start_pi.angle_pi()).abs() > 1.0 + let ccw_angular_distance = + (self.end_pi.angle_pi() - self.start_pi.angle_pi()).rem_euclid(2.0); + + if self.is_ccw() { + ccw_angular_distance > 1.0 + } else { + // for cw, the distance is the other way around the circle + (2.0 - ccw_angular_distance) > 1.0 + } } pub fn last_point(&self) -> Vec2 { @@ -204,12 +612,14 @@ impl CurveArc { fn end_angle(&self) -> Angle { self.end_pi.as_angle() - // AnglePi::new(self.end_pi).into() + } + + pub fn last_spot(&self) -> SpotOnCurve { + SpotOnCurve::new(self.last_point(), self.end_tangent_angle()) } fn start_angle(&self) -> Angle { self.start_pi.as_angle() - // AnglePi::new(self.start_pi).into() } pub fn start_tangent_angle(&self) -> Angle { @@ -219,6 +629,164 @@ impl CurveArc { self.start_angle().perp_to_right() } } + + pub fn set_radius(&self, radius: f32) -> CurveArc { + let mut m = *self; + m.radius = radius; + m + } + + pub fn svg_params(&self) -> [f32; 7] { + // assumes you've already moved to first_point + let last_point = self.last_point(); + [ + self.radius, + self.radius, // same as other rad because it's a circle + 0.0, // angle of ellipse doesn't matter, so 0 + if self.is_large_arc() { 1.0 } else { 0.0 }, // large arc flag + if self.is_ccw() { 1.0 } else { 0.0 }, // sweep-flag + last_point.x, + last_point.y, + ] + } + + pub fn is_full_circle(&self) -> bool { + (self.end_pi - self.start_pi).angle_pi().abs() >= 0.0 + && (self.end_pi - self.start_pi).angle_pi().rem_euclid(2.0) < 1e-3f32 + } + + pub fn is_full_circle_then_split(&self) -> Option<(CurveArc, CurveArc)> { + if self.is_full_circle() { + let start_angle = self.start_angle(); + let mid_angle = self.start_angle() + (self.end_angle() - self.start_angle()).scale(0.5); + let end_angle = self.end_angle(); + + let semi_circle1 = CurveArc::new(self.loc, self.radius, start_angle, mid_angle); + let semi_circle2 = CurveArc::new(self.loc, self.radius, mid_angle, end_angle); + Some((semi_circle1, semi_circle2)) + } else { + None + } + } + + // you should make sure that it's a similarity trnasform before you do this! + fn force_transform(&self, transform: &T) -> Self { + let transform = transform.to_simple_transform(); + Self { + loc: transform.transform_vec2(self.loc), + radius: transform.approx_scale() * self.radius, + start_pi: transform.approx_rotate() + self.start_pi, + end_pi: transform.approx_rotate() + self.end_pi, + } + } + + pub fn length(&self) -> f32 { + (self.radius * (self.end_pi - self.start_pi).angle()).abs() + } + + pub fn start_pi(&self) -> AnglePi { + self.start_pi + } + + pub fn end_pi(&self) -> AnglePi { + self.end_pi + } + + fn split_segment(&self, target_dist: f32) -> (CurveSegment, CurveSegment) { + // find out how far around the arc this pct is + let target_pct = target_dist / self.length(); + let split_pi = self.start_pi.lerpify(&self.end_pi, &target_pct); + let first_arc = CurveArc { + end_pi: split_pi, + ..*self + }; + + let second_arc = CurveArc { + start_pi: split_pi, + ..*self + }; + + (first_arc.to_segment(), second_arc.to_segment()) + } +} + +#[derive(Debug, Clone, Livecode, Lerpable)] +pub struct CurveCubicBezier { + from: Vec2, + ctrl1: Vec2, + ctrl2: Vec2, + to: Vec2, +} +impl CurveCubicBezier { + pub fn to_cubic(&self) -> CubicBezier { + CubicBezier { + from: self.from, + ctrl1: self.ctrl1, + ctrl2: self.ctrl2, + to: self.to, + } + } + + pub fn from_cubic(c: CubicBezier) -> Self { + Self { + from: c.from, + ctrl1: c.ctrl1, + ctrl2: c.ctrl2, + to: c.to, + } + } + + pub fn length(&self) -> f32 { + self.to_cubic().approx_length() + } + + pub fn first_point(&self) -> Vec2 { + self.from + } + + pub fn last_point(&self) -> Vec2 { + self.to + } + + pub fn reverse(&self) -> CurveCubicBezier { + Self::from_cubic(self.to_cubic().reverse()) + } + + pub fn as_points(&self) -> CurvePoints { + CurvePoints::new(self.to_cubic().to_vec2()) + } + + pub fn ctrl1(&self) -> Vec2 { + self.ctrl1 + } + + pub fn ctrl2(&self) -> Vec2 { + self.ctrl2 + } + + pub fn to(&self) -> Vec2 { + self.to + } + + pub fn flatten(&self, tolerance: f32) -> Vec { + flatten_cubic_bezier_path_with_tolerance(&[self.to_cubic()], false, tolerance).unwrap() + } + + pub fn to_pts(&self, tolerance: f32) -> CurveSegment { + CurveSegment::new_simple_points(self.flatten(tolerance)) + } + + fn force_transform(&self, transform: &T) -> CurveCubicBezier { + CurveCubicBezier::from_cubic( + self.to_cubic() + .apply_vec2_tranform(|x| transform.transform_vec2(x)), + ) + } + + fn split_segment(&self, target_dist: f32) -> (CurveSegment, CurveSegment) { + let v = self.to_cubic().to_vec2_line_space(1.0); // todo, figure out how to manage that + CurvePoints::new(v).split_segment(target_dist) + } } #[derive(Debug, Clone, Livecode, Lerpable)] @@ -232,6 +800,13 @@ impl CurvePoints { Self { points } } + pub fn length(&self) -> f32 { + self.points() + .windows(2) + .map(|pts| pts[0].distance(pts[1])) + .sum() + } + pub fn first_point(&self) -> Vec2 { *self.points.first().unwrap() } @@ -243,4 +818,526 @@ impl CurvePoints { pub fn points(&self) -> &Vec { &self.points } + + pub fn reverse(&self) -> Self { + CurvePoints::new(self.points.iter().cloned().rev().collect_vec()) + } + + fn force_transform(&self, transform: &SimpleTransform2d) -> CurvePoints { + CurvePoints { + points: transform.transform_many_vec2(self.points()).clone_to_vec(), + } + } + + fn split_segment(&self, target_dist: f32) -> (CurveSegment, CurveSegment) { + let mut add_left = true; + let mut so_far = 0.0; + let mut left = vec![self.first_point()]; + let mut right = vec![]; + for (curr, next) in self.points().curr_next_no_loop_iter() { + if add_left { + let dist = curr.distance(*next); + let dist_after_this_jump = so_far + dist; + if dist_after_this_jump > target_dist { + // split this one! + let split_pct = 1.0 - ((dist_after_this_jump - target_dist) / dist); + let lerp_point = PointToPoint::new(*curr, *next).pct(split_pct); + left.push(lerp_point); + right.push(lerp_point); + right.push(*next); + add_left = false; + } else { + so_far = dist_after_this_jump; + left.push(*next) + } + } else { + right.push(*next) + } + } + (left.to_segment(), right.to_segment()) + } + + fn push_pt_beginning(&mut self, pt: Vec2) { + self.points.insert(0, pt); + } + + fn extend_pts(&mut self, points: &[Vec2]) { + self.points.extend_from_slice(points); + } +} + +pub trait ToCurveSegment { + fn to_segment(&self) -> CurveSegment; +} + +impl ToCurveSegment for CubicBezier { + fn to_segment(&self) -> CurveSegment { + CurveSegment::cubic(*self) + } +} + +impl ToCurveSegment for Vec2 { + fn to_segment(&self) -> CurveSegment { + CurveSegment::new_simple_point(*self) + } +} + +impl ToCurveSegment for Vec { + fn to_segment(&self) -> CurveSegment { + CurveSegment::new_simple_points(self.clone()) + } +} + +impl ToCurveSegment for Polyline { + fn to_segment(&self) -> CurveSegment { + CurveSegment::new_simple_points(self.clone_to_vec()) + } +} + +impl ToCurveSegment for Circle { + fn to_segment(&self) -> CurveSegment { + CurveSegment::new_simple_circle(self.center, self.radius) + } +} + +impl ToCurveSegment for CurveArc { + fn to_segment(&self) -> CurveSegment { + CurveSegment::Arc(*self) + } +} + +impl ToCurveSegment for SpotOnCurve { + fn to_segment(&self) -> CurveSegment { + self.loc.to_segment() + } +} + +impl ToCurveSegment for PointToPoint { + fn to_segment(&self) -> CurveSegment { + CurveSegment::new_simple_points(vec![self.start(), self.end()]) + } +} + +pub fn simplify_curve_segments(segments: &[CurveSegment]) -> Vec { + let mut cleaned_segments = vec![]; + + // combine our points together.. + for segment in segments { + let segment_first_point = segment.first_point(); + let mut segment = segment.clone(); + if let CurveSegment::Points(curve_points) = &mut segment { + match cleaned_segments.last_mut() { + Some(CurveSegment::Points(pts)) => pts.extend_pts(curve_points.points()), + Some(s) => { + // make sure that the line starts where this one ends or else things + // like segmentation breaks down + if s.last_point().distance(segment_first_point) > 1e-2f32 { + curve_points.push_pt_beginning(s.last_point()); + } + + cleaned_segments.push(segment.clone()) + } + None => cleaned_segments.push(segment.clone()), + } + } else { + cleaned_segments.push(segment.clone()) + } + } + + cleaned_segments +} + +pub trait ToCurveDrawer { + fn to_segments(&self) -> Vec; + fn to_cd(&self, is_closed: bool) -> CurveDrawer { + CurveDrawer::new(simplify_curve_segments(&self.to_segments()), is_closed) + } + + // assume open + fn last_spot_cd(&self) -> Option { + let cdo = self.to_cd_open(); + let cd = cdo.segments(); + + if let Some(cd_last) = cd.last() { + if let Some(a) = cd_last.last_spot() { + return Some(a); + } else { + let last_point = cd_last.last_point(); + // if there's a second-from-last + if cd.len() >= 2 { + let second_from_last = cd[cd.len() - 2].last_point(); + + if last_point.distance(second_from_last) > 1.0e-6 { + return Some(PointToPoint::new(second_from_last, last_point).end_spot()); + } else { + // last test.. try to grab the last spot + return cd[cd.len() - 2].last_spot(); + } + } + } + } + None + } + + fn first_spot_cd(&self) -> Option { + let cdo = self.to_cd_open(); + let cd = cdo.segments(); + + if let Some(cd_first) = cd.first() { + if let Some(a) = cd_first.first_spot() { + return Some(a); + } else { + let first_point = cd_first.first_point(); + // if there's a second-from-first + if cd.len() >= 2 { + let second_spot = cd[1].first_point(); + if first_point.distance(second_spot) > 1.0e-6 { + return Some(PointToPoint::new(first_point, second_spot).start_spot()); + } else { + return cd[1].first_spot(); + } + } + } + } + None + } + + fn to_cd_closed(&self) -> CurveDrawer { + // CurveDrawer::new(self.to_segments(), true) + self.to_cd(true) + } + fn to_cd_open(&self) -> CurveDrawer { + // CurveDrawer::new(self.to_segments(), false) + self.to_cd(false) + } + + // trying out utility functions + fn to_approx_center(&self) -> Vec2 { + // turn it into rough points and then find the center. + // i'm not sure how to deal with tiny/big things.. + // i think we can assume it's not closed, because closing it won't + // change the bounds + let pts = self.to_rough_points(10.0); + let bm = BoundMetric::new_from_points(&pts); + bm.center() + } + + // chooses an arbitrary point on the path, like for a label + fn to_approx_point(&self) -> Vec2 { + let pts = self.to_rough_points(10.0); + if pts.is_empty() { + return vec2(0.0, 0.0); + } + pts[pts.len() / 2] + } + + fn to_rough_pct(&self, pct: f32) -> Option { + let length = self.to_cd_open().length(); + let dist = pct * length; + + self.to_rough_spots_count(dist, Some(1)).first().copied() + } + + // this one isn't evenly spaced yet + fn to_rough_points(&self, approx_spacing: f32) -> Vec { + let mut result = vec![]; + for s in &self.to_segments() { + let pts = match s { + CurveSegment::Arc(curve_arc) => { + let (s, _) = segment_arc(curve_arc, 0.0, approx_spacing, 0.0); + s.iter().map(|x| x.loc).collect_vec() + } + CurveSegment::Points(curve_points) => { + let mut v = vec![]; + for (curr, next) in curr_next_no_loop_iter(curve_points.points()) { + let (s, _) = segment_vec(*curr, *next, approx_spacing, 0.0); + v.extend(s); + } + v + } + CurveSegment::CubicBezier(curve_cubic_bezier) => curve_cubic_bezier + .to_cubic() + .to_vec2_line_space(approx_spacing), + }; + result.extend(pts) + } + result + } + + fn to_rough_spots(&self, approx_spacing: f32) -> Vec { + self.to_rough_spots_count(approx_spacing, None) + } + + fn even_split(&self, target_size: f32) -> (Vec, Vec) { + let cd = self.to_cd_open(); + let length = cd.length(); + + let how_many = (length / target_size).floor(); + let new_size = length / how_many; + + self.split(new_size) + } + + fn split(&self, pct: f32) -> (Vec, Vec) { + if pct <= 0.0 { + return (vec![], self.to_segments()); + } else if pct >= 1.0 { + return (self.to_segments(), vec![]); + } + + let cd = self.to_cd_open(); + let length = cd.length(); + + let target_dist = length * pct; + + let mut left = vec![]; + let mut right = vec![]; + let mut dist_traveled = 0.0; + let mut add_left = true; + + for segment in cd.segments() { + if add_left { + let segment_len = segment.length(); + let dist_traveled_after_this_segment = dist_traveled + segment_len; + + if dist_traveled_after_this_segment > target_dist { + // we need to split this next one! + let overshot_pct = + (dist_traveled_after_this_segment - target_dist) / segment_len; + let split_loc = (1.0 - overshot_pct) * segment_len; + + // points might not know about their starting place, so check for that + + let (left_s, right_s) = segment.split_segment(split_loc); + + left.push(left_s); + right.push(right_s); + add_left = false; + } else { + dist_traveled = dist_traveled_after_this_segment; + left.push(segment.clone()) + } + } else { + right.push(segment.clone()) + } + } + + (left, right) + } + + fn to_rough_spots_count( + &self, + approx_spacing: f32, + max_count: Option, + ) -> Vec { + let mut result = vec![]; + let mut curr_offset = 0.0; + for s in &self.to_segments() { + let pts = match s { + CurveSegment::Arc(curve_arc) => { + let (s, new_offset) = segment_arc(curve_arc, 0.0, approx_spacing, curr_offset); + curr_offset = new_offset; + s + } + CurveSegment::Points(curve_points) => { + let mut v = vec![]; + for (curr, next) in curr_next_no_loop_iter(curve_points.points()) { + let (s, new_offset) = + segment_vec(*curr, *next, approx_spacing, curr_offset); + let angle = PointToPoint::new(*curr, *next).angle(); + v.extend(s.iter().map(|x| SpotOnCurve::new(*x, angle))); + curr_offset = new_offset; + } + v + } + CurveSegment::CubicBezier(curve_cubic_bezier) => { + let curve_points = curve_cubic_bezier + .to_cubic() + .to_vec2_line_space(approx_spacing); + + // now treat it like a point, so we can get the offset and angle. + let mut v = vec![]; + for (curr, next) in curr_next_no_loop_iter(&curve_points) { + let (s, new_offset) = + segment_vec(*curr, *next, approx_spacing, curr_offset); + let angle = PointToPoint::new(*curr, *next).angle(); + v.extend(s.iter().map(|x| SpotOnCurve::new(*x, angle))); + curr_offset = new_offset; + } + v + } + }; + result.extend(pts); + + if let Some(mc) = max_count { + if result.len() > mc { + return result.take_count(mc); + } + } + } + result + } +} + +impl ToCurveDrawer for CurveSegment { + fn to_segments(&self) -> Vec { + vec![self.clone()] + } +} + +impl ToCurveDrawer for T +where + T: ToCurveSegment, +{ + fn to_segments(&self) -> Vec { + self.to_segment().to_segments() + } +} + +impl ToCurveDrawer for Vec { + fn to_segments(&self) -> Vec { + self.clone() + } +} + +impl ToCurveDrawer for CurveDrawer { + fn to_segments(&self) -> Vec { + self.segments_pseudo_closed() + } +} + +impl ToCurveDrawer for Option +where + T: ToCurveDrawer, +{ + fn to_segments(&self) -> Vec { + match self { + Some(t) => t.to_segments(), + None => vec![], + } + } +} + +impl ToCurveDrawer for Vec { + fn to_segments(&self) -> Vec { + vec![CurveSegment::new_simple_points( + self.iter().map(|x| x.loc()).collect_vec(), + )] + } +} + +impl ToCurveDrawer for Vec { + fn to_segments(&self) -> Vec { + self.map_iter_collect(|x| x.to_segment()) + } +} + +#[macro_export] +macro_rules! curve_segments { + ($($expr:expr),* $(,)?) => {{ + let mut v: Vec = Vec::new(); + $( + v.extend($expr.to_segments()); + )* + v + }}; +} + +// useful for drawnshape... +pub trait ToCurveDrawers { + fn to_curve_drawers(&self) -> Vec; +} + +impl ToCurveDrawers for CurveDrawer { + fn to_curve_drawers(&self) -> Vec { + vec![self.clone()] + } +} + +impl ToCurveDrawers for Vec +where + T: ToCurveDrawers + Clone, +{ + fn to_curve_drawers(&self) -> Vec { + self.iter().map(|x| x.to_curve_drawers()).concat() + } +} + +impl ToCurveDrawers for Option +where + T: ToCurveDrawers, +{ + fn to_curve_drawers(&self) -> Vec { + match self { + Some(t) => t.to_curve_drawers(), + None => vec![], + } + } +} + +#[macro_export] +macro_rules! curve_drawers { + ($($expr:expr),* $(,)?) => {{ + let mut v: Vec = Vec::new(); + $( + v.extend($expr.to_curve_drawers()); + )* + v + }}; +} + +#[macro_export] +macro_rules! mixed_drawable { + ($($expr:expr),* $(,)?) => {{ + let mut v: Vec = Vec::new(); + $( + v.extend($expr.to_mixed_drawables()); + )* + v + }}; +} + +// i get by with a little help from chatgpt +pub trait IntoIterOf { + type Iter: Iterator; + fn into_iter_of(self) -> Self::Iter; +} + +impl IntoIterOf for Vec { + type Iter = std::vec::IntoIter; + fn into_iter_of(self) -> Self::Iter { + self.into_iter() + } +} + +impl IntoIterOf for Option { + type Iter = std::option::IntoIter; + fn into_iter_of(self) -> Self::Iter { + self.into_iter() + } +} + +impl IntoIterOf for T { + type Iter = std::iter::Once; + fn into_iter_of(self) -> Self::Iter { + std::iter::once(self) + } +} + +pub fn extend_flat(out: &mut Vec, x: X) +where + X: IntoIterOf, +{ + out.extend(x.into_iter_of()); +} + +#[macro_export] +macro_rules! flatten { + ($($expr:expr),* $(,)?) => {{ + let mut v = Vec::new(); + $( + extend_flat(&mut v, $expr); + )* + v + }}; } diff --git a/murrelet_draw/src/draw.rs b/murrelet_draw/src/draw.rs index 6d68ed7..84ac8c8 100644 --- a/murrelet_draw/src/draw.rs +++ b/murrelet_draw/src/draw.rs @@ -1,9 +1,9 @@ // traits, that you can connect to things like Nannou draw -use glam::{vec2, Mat4, Vec2, Vec3}; -use itertools::Itertools; +use glam::{vec2, Vec2, Vec3}; + use lerpable::Lerpable; -use murrelet_common::{IsPolyline, MurreletColor, Polyline, SimpleTransform2d, TransformVec2}; +use murrelet_common::{MurreletColor, SimpleTransform2d, ToSimpleTransform, Transformable}; use murrelet_livecode::unitcells::UnitCellContext; use murrelet_livecode_derive::Livecode; use palette::{named::AQUAMARINE, LinSrgba, Srgb}; @@ -53,7 +53,7 @@ impl MurreletColorStyle { pub fn as_color(&self) -> MurreletColor { match self { - MurreletColorStyle::Color(c) => c.clone(), + MurreletColorStyle::Color(c) => *c, MurreletColorStyle::RgbaFill(c) => c.color(), MurreletColorStyle::SvgFill(_) => MurreletColor::white(), // default if we're not drawing the texture } @@ -78,8 +78,13 @@ impl MurreletColorStyle { MurreletColorStyle::SvgFill(c) => MurreletColorStyle::SvgFill(c.with_alpha(alpha)), } } + + fn hue(hue: f32) -> MurreletColorStyle { + MurreletColorStyle::Color(MurreletColor::hsva(hue, 1.0, 1.0, 1.0)) + } } +#[derive(Debug, Clone)] pub enum MurreletDrawPlan { Shader(StyledPathSvgFill), DebugPoints(PixelShape), @@ -129,6 +134,28 @@ impl MurreletStyle { } } + pub fn new_line_color(color: MurreletColor, stroke_weight: f32) -> MurreletStyle { + MurreletStyle { + closed: false, + filled: false, + color: MurreletColorStyle::Color(color), + stroke_weight, + stroke_color: MurreletColorStyle::Color(color), + ..Default::default() + } + } + + pub fn new_outline_color(color: MurreletColor, stroke_weight: f32) -> MurreletStyle { + MurreletStyle { + closed: true, + filled: false, + color: MurreletColorStyle::Color(color), + stroke_weight, + stroke_color: MurreletColorStyle::Color(color), + ..Default::default() + } + } + pub fn new_outline() -> MurreletStyle { MurreletStyle { closed: true, @@ -159,6 +186,22 @@ impl MurreletStyle { } } + pub fn white_font() -> Self { + MurreletStyle { + color: MurreletColorStyle::white(), + stroke_weight: 24.0, + ..Default::default() + } + } + + pub fn red_font() -> Self { + MurreletStyle { + color: MurreletColorStyle::hue(0.0), + stroke_weight: 24.0, + ..Default::default() + } + } + pub fn with_color(&self, c: MurreletColor) -> MurreletStyle { MurreletStyle { color: MurreletColorStyle::color(c), @@ -188,6 +231,20 @@ impl MurreletStyle { } } + pub fn with_stroke_color(&self, c: MurreletColor) -> MurreletStyle { + MurreletStyle { + stroke_color: MurreletColorStyle::Color(c), + ..*self + } + } + + pub fn with_fill(&self) -> MurreletStyle { + MurreletStyle { + filled: true, + ..*self + } + } + pub fn with_no_fill(&self) -> MurreletStyle { MurreletStyle { filled: false, @@ -233,6 +290,10 @@ pub trait Sdraw: Sized { self.with_svg_style(self.svg_style().with_stroke(w)) } + fn with_stroke_color(&self, line_color: MurreletColor) -> Self { + self.with_svg_style(self.svg_style().with_stroke_color(line_color)) + } + fn with_close(&self, closed: bool) -> Self { let svg_style = MurreletStyle { closed, @@ -273,27 +334,37 @@ pub trait Sdraw: Sized { self.with_svg_style(svg_style) } - fn transform(&self) -> Mat4; - fn set_transform(&self, m: Mat4) -> Self; + fn as_line(&self) -> Self { + let svg_style = MurreletStyle { + filled: false, + closed: false, + ..self.svg_style() + }; + self.with_svg_style(svg_style) + } + + fn transform(&self) -> SimpleTransform2d; + + fn set_transform(&self, m: &M) -> Self; - fn add_transform_after(&self, t: Mat4) -> Self { - let m = t * self.transform(); - self.set_transform(m) + fn add_transform_after(&self, t: &M) -> Self { + let m = self + .transform() + .add_transform_after(&t.to_simple_transform()); + self.set_transform(&m) } - fn add_transform_before(&self, t: Mat4) -> Self { - let m = self.transform() * t; - self.set_transform(m) + fn add_transform_before(&self, t: &M) -> Self { + let m = self + .transform() + .add_transform_before(&t.to_simple_transform()); + self.set_transform(&m) } - fn transform_points(&self, face: &F) -> Polyline; + fn transform_points(&self, face: &F) -> F; fn maybe_transform_vec2(&self, v: Vec2) -> Option { - self.transform_points(&vec![v]) - .into_iter_vec2() - .collect_vec() - .first() - .cloned() + Some(v.transform_with(&self.transform())) } fn line_space_multi(&self) -> f32; @@ -306,10 +377,10 @@ pub struct CoreSDrawCtxUnitCell { sdraw: CoreSDrawCtx, } impl Sdraw for CoreSDrawCtxUnitCell { - fn transform_points(&self, face: &F) -> Polyline { + fn transform_points(&self, face: &F) -> F { // let mut points = face.clone(); - let mut points = self.sdraw.transform.transform_many_vec2(face); + let mut points = face.transform_with(&self.sdraw.transform); // main difference! apply the unit cell transform points = if self.unit_cell_skew { @@ -337,13 +408,13 @@ impl Sdraw for CoreSDrawCtxUnitCell { } // this only changes the global transform, the unitcell one will still happen - fn transform(&self) -> Mat4 { - self.sdraw.transform + fn transform(&self) -> SimpleTransform2d { + self.sdraw.transform.clone() } - fn set_transform(&self, m: Mat4) -> Self { + fn set_transform(&self, m: &M) -> Self { let mut ctx = self.clone(); - ctx.sdraw.transform = m; + ctx.sdraw.transform = m.to_simple_transform(); ctx } } @@ -405,11 +476,11 @@ impl CoreSDrawCtxUnitCell { pub struct CoreSDrawCtx { svg_style: MurreletStyle, pub frame: f32, - transform: Mat4, // happens before the others + transform: SimpleTransform2d, // happens before the others } impl Sdraw for CoreSDrawCtx { - fn transform_points(&self, face: &F) -> Polyline { - self.transform.transform_many_vec2(face) + fn transform_points(&self, face: &F) -> F { + face.transform_with(&self.transform) } fn with_svg_style(&self, svg_style: MurreletStyle) -> Self { @@ -418,24 +489,24 @@ impl Sdraw for CoreSDrawCtx { ctx } - fn transform(&self) -> Mat4 { - self.transform + fn transform(&self) -> SimpleTransform2d { + self.transform.clone() } - fn set_transform(&self, m: Mat4) -> Self { + fn set_transform(&self, m: &M) -> Self { let mut sdraw = self.clone(); - sdraw.transform = m; + sdraw.transform = m.to_simple_transform(); sdraw } - fn add_transform_after(&self, t: Mat4) -> Self { + fn add_transform_after(&self, t: &M) -> Self { let mut ctx = self.clone(); - ctx.transform = t * ctx.transform; + ctx.transform = ctx.transform.add_transform_after(&t.to_simple_transform()); ctx } - fn add_transform_before(&self, t: Mat4) -> Self { + fn add_transform_before(&self, t: &M) -> Self { let mut ctx = self.clone(); - ctx.transform *= t; + ctx.transform = ctx.transform.add_transform_before(&t.to_simple_transform()); ctx } @@ -459,7 +530,7 @@ impl Sdraw for CoreSDrawCtx { } impl CoreSDrawCtx { - pub fn new(svg_style: MurreletStyle, frame: f32, transform: Mat4) -> Self { + pub fn new(svg_style: MurreletStyle, frame: f32, transform: SimpleTransform2d) -> Self { Self { svg_style, frame, diff --git a/murrelet_draw/src/drawable.rs b/murrelet_draw/src/drawable.rs new file mode 100644 index 0000000..e3e80dd --- /dev/null +++ b/murrelet_draw/src/drawable.rs @@ -0,0 +1,228 @@ +use glam::Vec2; +use itertools::Itertools; +use murrelet_common::{ToSimpleTransform, Transformable}; +use murrelet_livecode::types::LivecodeResult; + +use crate::{ + curve_drawer::{CurveDrawer, ToCurveDrawer}, + style::styleconf::StyleConf, + transform2d::Transform2d, +}; + +// hm, a new type that attaches a shape to its style, but keeps it agnostic to what is drawing. +#[derive(Clone, Debug)] +pub struct DrawnShape { + cds: Vec, + style: StyleConf, +} + +impl DrawnShape { + pub fn new_vecvec(shape: Vec>, style: StyleConf) -> DrawnShape { + let cds = shape + .into_iter() + .map(|x| CurveDrawer::new_simple_points(x, true)) + .collect_vec(); + Self::new_cds(&cds, style) + } + + pub fn new_cds(cds: &[CurveDrawer], style: StyleConf) -> DrawnShape { + Self { + cds: cds.to_vec(), + style: style.clone(), + } + } + + pub fn style(&self) -> StyleConf { + self.style.clone() + } + + pub fn set_style(&mut self, style: StyleConf) { + self.style = style; + } + + pub fn curves(&self) -> &[CurveDrawer] { + &self.cds + } + + pub fn maybe_transform(&self, transform: &Transform2d) -> LivecodeResult { + let mut new = vec![]; + for c in &self.cds { + new.push(c.maybe_transform(transform)?); + } + Ok(DrawnShape::new_cds(&new, self.style.clone())) + } +} + +pub trait ToDrawnShapeSegments { + fn to_drawn_shape_closed(&self, style: StyleConf) -> DrawnShape; + fn to_drawn_shape_open(&self, style: StyleConf) -> DrawnShape; + + fn to_drawn_shape_closed_r(&self, style: &StyleConf) -> DrawnShape { + self.to_drawn_shape_closed(style.clone()) + } + + fn to_drawn_shape_open_r(&self, style: &StyleConf) -> DrawnShape { + self.to_drawn_shape_open(style.clone()) + } +} + +impl ToDrawnShapeSegments for T +where + T: ToCurveDrawer, +{ + fn to_drawn_shape_closed(&self, style: StyleConf) -> DrawnShape { + DrawnShape::new_cds(&[self.to_cd_closed()], style) + } + + fn to_drawn_shape_open(&self, style: StyleConf) -> DrawnShape { + DrawnShape::new_cds(&[self.to_cd_open()], style) + } +} + +pub trait ToDrawnShape { + fn to_drawn_shape(&self, style: StyleConf) -> DrawnShape; + + fn to_drawn_shape_r(&self, style: &StyleConf) -> DrawnShape { + self.to_drawn_shape(style.clone()) + } +} + +impl ToDrawnShape for CurveDrawer { + fn to_drawn_shape(&self, style: StyleConf) -> DrawnShape { + DrawnShape::new_cds(&[self.clone()], style) + } +} + +impl ToDrawnShape for Vec { + fn to_drawn_shape(&self, style: StyleConf) -> DrawnShape { + DrawnShape::new_cds(self, style) + } +} + +impl Transformable for CurveDrawer { + fn transform_with(&self, t: &T) -> Self { + self.maybe_transform(t).unwrap_or_else(|_| self.clone()) + } +} + +impl Transformable for DrawnShape { + fn transform_with(&self, t: &T) -> Self { + DrawnShape::new_cds(&self.cds.transform_with(t), self.style.clone()) + } +} + +#[derive(Clone, Debug)] +pub struct PositionedText { + text: String, + loc: Vec2, +} +impl PositionedText { + pub fn new(text: &str, loc: Vec2) -> Self { + Self { + text: text.to_string(), + loc, + } + } + + pub fn with_style(&self, style: &StyleConf) -> MixedDrawableShape { + MixedDrawableShape::Text(DrawnTextShape { + text: vec![self.clone()], + style: style.clone(), + }) + } + + pub fn text(&self) -> &str { + &self.text + } + + pub fn loc(&self) -> Vec2 { + self.loc + } +} + +impl ToMixedDrawableWithStyle for Vec { + fn with_style(&self, style: &StyleConf) -> MixedDrawableShape { + MixedDrawableShape::Text(DrawnTextShape { + text: self.clone(), + style: style.clone(), + }) + } +} + +impl ToMixedDrawableWithStyle for Vec { + fn with_style(&self, style: &StyleConf) -> MixedDrawableShape { + MixedDrawableShape::Shape(self.to_drawn_shape_r(style)) + } +} + +pub trait ToMixedDrawableWithStyle { + fn with_style(&self, style: &StyleConf) -> MixedDrawableShape; +} + +#[derive(Clone, Debug)] +pub struct DrawnTextShape { + text: Vec, + style: StyleConf, +} +impl DrawnTextShape { + pub fn positions(&self) -> &[PositionedText] { + &self.text + } +} + +// ergh, need another type to hold type... +#[derive(Clone, Debug)] +pub enum MixedDrawableShape { + Shape(DrawnShape), + Text(DrawnTextShape), +} +impl MixedDrawableShape { + pub fn style(&self) -> StyleConf { + match self { + MixedDrawableShape::Shape(drawn_shape) => drawn_shape.style(), + MixedDrawableShape::Text(drawn_text_shape) => drawn_text_shape.style.clone(), + } + } +} + +pub trait ToMixedDrawable { + fn to_mix_drawable(&self) -> MixedDrawableShape; +} + +impl ToMixedDrawable for DrawnShape { + fn to_mix_drawable(&self) -> MixedDrawableShape { + MixedDrawableShape::Shape(self.clone()) + } +} + +pub trait ToMixedDrawables { + fn to_mixed_drawables(&self) -> Vec; +} + +impl ToMixedDrawables for MixedDrawableShape { + fn to_mixed_drawables(&self) -> Vec { + vec![self.clone()] + } +} + +impl ToMixedDrawables for Vec { + fn to_mixed_drawables(&self) -> Vec { + self.clone() + } +} + +impl ToMixedDrawables for DrawnShape { + fn to_mixed_drawables(&self) -> Vec { + vec![MixedDrawableShape::Shape(self.clone())] + } +} + +impl ToMixedDrawables for Vec { + fn to_mixed_drawables(&self) -> Vec { + let mut v = vec![]; + for x in self.iter() { + v.push(MixedDrawableShape::Shape(x.clone())); + } + v + } +} diff --git a/murrelet_draw/src/lib.rs b/murrelet_draw/src/lib.rs index 0c4b246..0853f4a 100644 --- a/murrelet_draw/src/lib.rs +++ b/murrelet_draw/src/lib.rs @@ -1,8 +1,13 @@ pub mod compass; +pub mod cubic; pub mod curve_drawer; pub mod draw; -pub mod livecodetypes; +pub mod drawable; pub mod newtypes; +pub mod scaffold; pub mod sequencers; +pub mod serialize; pub mod style; +pub mod svg; +pub mod tesselate; pub mod transform2d; diff --git a/murrelet_draw/src/livecodetypes.rs b/murrelet_draw/src/livecodetypes.rs deleted file mode 100644 index 04752a3..0000000 --- a/murrelet_draw/src/livecodetypes.rs +++ /dev/null @@ -1,31 +0,0 @@ -// place to put newtypes for livecode - -pub mod anglepi { - use lerpable::Lerpable; - use murrelet_common::{Angle, AnglePi, IsAngle}; - use murrelet_livecode_derive::Livecode; - - #[derive(Clone, Copy, Debug, Livecode, Lerpable, Default)] - pub struct LivecodeAnglePi(f32); - impl LivecodeAnglePi { - pub const ZERO: Self = LivecodeAnglePi(0.0); - - fn _to_angle_pi(&self) -> AnglePi { - AnglePi::new(self.0) - } - - pub fn new(f: A) -> Self { - Self(f.angle_pi()) - } - - pub fn from_angle_pi(angle_pi: f32) -> Self { - Self(angle_pi) - } - } - - impl From for Angle { - fn from(value: LivecodeAnglePi) -> Self { - value._to_angle_pi().as_angle() - } - } -} diff --git a/murrelet_draw/src/newtypes.rs b/murrelet_draw/src/newtypes.rs index b0ab1bd..2962fbb 100644 --- a/murrelet_draw/src/newtypes.rs +++ b/murrelet_draw/src/newtypes.rs @@ -5,6 +5,7 @@ use glam::{Vec2, Vec3}; use lerpable::Lerpable; use murrelet_common::*; use murrelet_livecode_derive::Livecode; +use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, Livecode, Lerpable, Default)] pub struct F32Newtype { @@ -17,7 +18,7 @@ impl F32Newtype { } } -#[derive(Copy, Clone, Debug, Livecode, Lerpable, Default)] +#[derive(Copy, Clone, Debug, Livecode, Lerpable, Default, Serialize, Deserialize)] pub struct Vec2Newtype { #[lerpable(func = "lerpify_vec2")] v: Vec2, @@ -71,8 +72,12 @@ impl RGBandANewtype { } pub fn with_alpha(&self, alpha: f32) -> Self { - let mut c = self.clone(); + let mut c = *self; c.a = alpha; c } + + pub fn alpha(&self) -> f32 { + self.a + } } diff --git a/murrelet_draw/src/scaffold.rs b/murrelet_draw/src/scaffold.rs new file mode 100644 index 0000000..ecb5120 --- /dev/null +++ b/murrelet_draw/src/scaffold.rs @@ -0,0 +1,237 @@ +#![allow(dead_code)] +use crate::{ + curve_drawer::{CurveDrawer, ToCurveDrawer}, + drawable::DrawnShape, +}; +use geo::{BooleanOps, BoundingRect, Contains}; +use glam::{vec2, Vec2}; +use itertools::Itertools; +use murrelet_common::SpotOnCurve; + +pub fn line_to_multipolygon(curves: &[Vec2]) -> geo::MultiPolygon { + geo::MultiPolygon::new(vec![line_to_polygon(curves)]) +} + +pub fn line_to_polygon(curves: &[Vec2]) -> geo::Polygon { + geo::Polygon::new(vec2_to_line_string(curves), vec![]) +} + +pub fn multipolygon_to_vec2(p: &geo::MultiPolygon) -> Vec> { + p.iter().map(polygon_to_vec2).collect_vec() +} + +pub fn polygon_to_vec2(p: &geo::Polygon) -> Vec { + let mut coords = p + .exterior() + .coords() + .map(|coord| coord.to_vec2()) + .collect_vec(); + + if coords.first() == coords.last() { + coords.pop(); + } + + coords +} + +trait ToVec2Griddable { + fn to_vec2(&self) -> Vec2; +} + +impl ToVec2Griddable for ::geo::Coord { + fn to_vec2(&self) -> Vec2 { + let (x, y) = self.x_y(); + vec2(x as f32, y as f32) + } +} + +trait ToCoord { + fn to_coord(&self) -> ::geo::Coord; +} + +impl ToCoord for Vec2 { + fn to_coord(&self) -> ::geo::Coord { + ::geo::coord! {x: self.x as f64, y: self.y as f64} + } +} + +#[derive(Debug, Clone)] +pub struct MaskCacheImpl { + bounding: geo::Rect, + polygon: geo::Polygon, +} +impl MaskCacheImpl { + fn center(&self) -> Vec2 { + 0.5 * (self.bounding.min().to_vec2() + self.bounding.max().to_vec2()) + } + + fn contains(&self, v: &Vec2) -> bool { + self.polygon.contains(&v.to_coord()) + } + + // left needs to be inside + fn last_point_containing(&self, left: &Vec2, right: &Vec2) -> Vec2 { + let mut left = *left; + let mut right = *right; + while left.distance(right) > 0.2 { + let midpoint = 0.5 * (left + right); + if self.contains(&midpoint) { + left = midpoint; + } else { + right = midpoint; + } + } + // finished! + 0.5 * (left + right) + } +} + +fn curve_segment_maker_to_line_string(curve: &CurveDrawer) -> geo::LineString { + vec2_to_line_string(&curve.to_rough_points(1.0)) +} + +fn vec2_to_line_string(vs: &[Vec2]) -> geo::LineString { + let coords = vs.iter().map(|x| x.to_coord()).collect_vec(); + geo::LineString::new(coords) +} + +#[derive(Debug, Clone)] +pub enum MaskCache { + Impl(MaskCacheImpl), + AlwaysTrue, +} + +impl MaskCache { + pub fn center(&self) -> Vec2 { + match self { + MaskCache::Impl(s) => s.center(), + MaskCache::AlwaysTrue => Vec2::ZERO, + } + } + + pub fn last_point_containing(&self, left: &Vec2, right: &Vec2) -> Vec2 { + match self { + MaskCache::Impl(s) => s.last_point_containing(left, right), + MaskCache::AlwaysTrue => *right, + } + } + + pub fn new_vec2(curves: &[Vec2]) -> Self { + let polygon = geo::Polygon::new(vec2_to_line_string(curves), vec![]); + + MaskCache::Impl(MaskCacheImpl { + bounding: polygon.bounding_rect().unwrap(), + polygon, + }) + } + + pub fn new_cd(cd: CurveDrawer) -> Self { + Self::new(&[cd]) + } + + pub fn new_interior(outline: CurveDrawer, interior: &[CurveDrawer]) -> Self { + // shh, just a wrapper + let s = [vec![outline], interior.to_vec()].concat(); + Self::new(&s) + } + + pub fn new(curves: &[CurveDrawer]) -> Self { + // first curve is external + let (first_curve, rest) = curves.split_first().unwrap(); + let first = curve_segment_maker_to_line_string(first_curve); + + let mut remaining = vec![]; + // add all our points to a hashmap + for curve_maker in rest { + remaining.push(curve_segment_maker_to_line_string(curve_maker)); + } + + let polygon = ::geo::Polygon::new(first, remaining); + + MaskCache::Impl(MaskCacheImpl { + bounding: polygon.bounding_rect().unwrap(), + polygon, + }) + } + + pub fn contains(&self, v: &Vec2) -> bool { + match self { + MaskCache::Impl(x) => x.contains(v), + MaskCache::AlwaysTrue => true, + } + } + + pub fn noop() -> MaskCache { + MaskCache::AlwaysTrue + } + + pub fn crop(&self, shape: &[Vec2]) -> Vec> { + match self { + MaskCache::Impl(x) => { + let other = line_to_polygon(shape); + let cropped = x.polygon.intersection(&other); + multipolygon_to_vec2(&cropped) + } + MaskCache::AlwaysTrue => vec![shape.to_vec()], + } + } + + // remove this object from all of the shapes + fn crop_inverse(&self, shape: &[Vec2]) -> Vec> { + match self { + MaskCache::Impl(x) => { + let other = line_to_polygon(shape); + let cropped = other.difference(&x.polygon); + multipolygon_to_vec2(&cropped) + } + MaskCache::AlwaysTrue => vec![shape.to_vec()], + } + } + + pub fn to_vec2(&self) -> Vec { + match self { + MaskCache::Impl(mask_cache_impl) => polygon_to_vec2(&mask_cache_impl.polygon), + MaskCache::AlwaysTrue => unreachable!(), + } + } + + pub fn crop_many(&self, v: &[DrawnShape]) -> Vec { + let mut cropped = vec![]; + for a in v.iter() { + let mut new_cds = vec![]; + for cd in a.curves() { + new_cds.extend(self.crop(&cd.to_rough_points(1.0))); + } + cropped.push(DrawnShape::new_vecvec(new_cds, a.style())); + } + cropped + } + + pub fn crop_inverse_many(&self, v: &[DrawnShape]) -> Vec { + let mut cropped = vec![]; + for a in v.iter() { + let mut new_cds = vec![]; + for cd in a.curves() { + new_cds.extend(self.crop_inverse(&cd.to_rough_points(1.0))); + } + cropped.push(DrawnShape::new_vecvec(new_cds, a.style())); + } + cropped + } + + pub fn ray_intersect(&self, spot: &SpotOnCurve) -> Vec2 { + self.last_point_containing(&spot.loc, &spot.line_to_spot(1000.0)) + } + pub fn rect(&self) -> murrelet_common::Rect { + match self { + MaskCache::Impl(mask_cache_impl) => { + let w = mask_cache_impl.bounding.width(); + let h = mask_cache_impl.bounding.height(); + let center = mask_cache_impl.bounding.center(); + + murrelet_common::Rect::from_xy_wh(center.to_vec2(), vec2(w as f32, h as f32)) + } + MaskCache::AlwaysTrue => todo!(), + } + } +} diff --git a/murrelet_draw/src/sequencers.rs b/murrelet_draw/src/sequencers.rs index 4271841..3c06977 100644 --- a/murrelet_draw/src/sequencers.rs +++ b/murrelet_draw/src/sequencers.rs @@ -1,11 +1,10 @@ +use crate::transform2d::{Transform2d, Transform2dStep}; use glam::*; use itertools::Itertools; use lerpable::Lerpable; use murrelet_common::*; -use murrelet_livecode::unitcells::{UnitCellContext, UnitCellCreator, UnitCellExprWorldContext}; -use murrelet_livecode_derive::*; - -use crate::transform2d::{Transform2d, Transform2dStep}; +use murrelet_livecode::unitcells::{UnitCellContext, UnitCellCreator, UnitCellIdx}; +use murrelet_livecode_derive::Livecode; const REFERENCE_SIZE: f32 = 100.0; @@ -84,7 +83,7 @@ fn make_grid( (0..y_usize).map(move |y| { let y_idx = IdxInRange::new(y, y_usize); let idx = IdxInRange2d::new_from_idx(x_idx, y_idx); - let ctx = UnitCellExprWorldContext::from_idx2d(idx, 1.0); + let ctx = UnitCellIdx::from_idx2d(idx, 1.0); let mut center = if offset_alternating { let mut center = idx.to_alternating_i().center_of_cell(); @@ -117,3 +116,57 @@ fn make_grid( }) .collect_vec() } + +#[derive(Clone, Debug, Livecode, Lerpable, Default)] +pub struct SimpleCountSequence(pub usize); +impl SimpleCountSequence { + pub fn val(&self) -> usize { + self.0 + } +} + +impl UnitCellCreator for SimpleCountSequence { + fn to_unit_cell_ctxs(&self) -> Vec { + (0..self.0) + .map(|x| { + let idx = IdxInRange::new(x, self.0); + let ctx = UnitCellIdx::from_idx1d(idx); + UnitCellContext::new(ctx, SimpleTransform2d::ident()) + }) + .collect_vec() + } +} + +#[derive(Clone, Debug, Default, Livecode, Lerpable)] +pub struct SimpleVec2Sequence(#[lerpable(func = "lerpify_vec2")] Vec2); +impl SimpleVec2Sequence { + pub fn max_x_usize(&self) -> usize { + self.0.x as usize + } + + pub fn max_y_usize(&self) -> usize { + self.0.y as usize + } + + pub fn to_vec2(&self) -> Vec2 { + self.0 + } +} + +impl UnitCellCreator for SimpleVec2Sequence { + fn to_unit_cell_ctxs(&self) -> Vec { + let x_usize = self.0.x as usize; + let y_usize = self.0.y as usize; + (0..x_usize) + .flat_map(|x| { + let x_idx = IdxInRange::new(x, x_usize); + (0..y_usize).map(move |y| { + let y_idx = IdxInRange::new(y, y_usize); + let idx = IdxInRange2d::new_from_idx(x_idx, y_idx); + let ctx = UnitCellIdx::from_idx2d(idx, 1.0); + UnitCellContext::new(ctx, SimpleTransform2d::ident()) + }) + }) + .collect_vec() + } +} diff --git a/murrelet_draw/src/serialize.rs b/murrelet_draw/src/serialize.rs new file mode 100644 index 0000000..f1e5480 --- /dev/null +++ b/murrelet_draw/src/serialize.rs @@ -0,0 +1,17 @@ +use murrelet_common::MurreletColor; +use serde::{ser::SerializeSeq, Serializer}; + +use anyhow::Result; + +pub fn serialize_color(x: &MurreletColor, serializer: S) -> Result +where + S: Serializer, +{ + let hsva = x.into_hsva_components(); + + let mut seq = serializer.serialize_seq(Some(hsva.len()))?; + for number in &hsva { + seq.serialize_element(number)?; + } + seq.end() +} diff --git a/murrelet_draw/src/style.rs b/murrelet_draw/src/style.rs index 87a7f14..b2aeb51 100644 --- a/murrelet_draw/src/style.rs +++ b/murrelet_draw/src/style.rs @@ -1,11 +1,19 @@ #![allow(dead_code)] -use crate::{curve_drawer::CurveDrawer, draw::*, transform2d::*}; +use crate::{ + curve_drawer::{CurveDrawer, ToCurveDrawer}, + draw::*, + svg::{SvgPathDef, SvgShape, TransformedSvgShape}, + tesselate::{parse_svg_path_as_vec2, ToLyonPath}, + transform2d::*, +}; use glam::*; use lerpable::Lerpable; use md5::{Digest, Md5}; use murrelet_common::*; -use murrelet_livecode::{lazy::ControlLazyNodeF32, livecode::ControlF32, types::ControlVecElement}; +use murrelet_gui::{CanMakeGUI, MurreletGUI}; +use murrelet_livecode::{lazy::ControlLazyMurreletColor, livecode::ControlF32}; use murrelet_livecode_derive::Livecode; +use styleconf::StyleConf; fn _black() -> [ControlF32; 4] { [ @@ -16,13 +24,8 @@ fn _black() -> [ControlF32; 4] { ] } -fn _black_lazy() -> Vec> { - vec![ - ControlVecElement::Single(ControlLazyNodeF32::Float(0.0)), - ControlVecElement::Single(ControlLazyNodeF32::Float(0.0)), - ControlVecElement::Single(ControlLazyNodeF32::Float(0.0)), - ControlVecElement::Single(ControlLazyNodeF32::Float(1.0)), - ] +fn _black_lazy() -> ControlLazyMurreletColor { + ControlLazyMurreletColor::new_default(0.0, 0.0, 0.0, 1.0) } #[derive(Copy, Clone, Debug, Livecode, Lerpable, Default)] @@ -34,6 +37,14 @@ pub struct MurreletStyleFilled { pub stroke_color: MurreletColor, } impl MurreletStyleFilled { + pub fn new(color: MurreletColor, stroke_weight: f32, stroke_color: MurreletColor) -> Self { + Self { + color, + stroke_weight, + stroke_color, + } + } + fn to_style(&self) -> MurreletStyle { MurreletStyle { closed: true, @@ -126,10 +137,14 @@ impl StyledPathSvgFill { } pub fn with_alpha(&self, alpha: f32) -> StyledPathSvgFill { - let mut p = self.clone(); + let mut p = *self; p.alpha = alpha; p } + + pub fn alpha(&self) -> f32 { + self.alpha + } } #[derive(Clone, Debug, Livecode, Lerpable)] @@ -251,6 +266,7 @@ impl MurreletStyleOutlined { closed: true, filled: false, color: MurreletColorStyle::color(self.color), + stroke_color: MurreletColorStyle::color(self.color), // should this be stroke color? stroke_weight: self.stroke_weight, ..Default::default() } @@ -328,7 +344,7 @@ impl MurreletStyleRGBAPoints { } } -#[derive(Copy, Clone, Debug, Livecode, Lerpable, Default)] +#[derive(Copy, Clone, Debug, Livecode, MurreletGUI, Lerpable, Default)] pub struct MurreletStyleLined { pub color: MurreletColor, // fill color #[livecode(serde_default = "zeros")] @@ -340,6 +356,7 @@ impl MurreletStyleLined { closed: false, filled: false, color: MurreletColorStyle::color(self.color), + stroke_color: MurreletColorStyle::color(self.color), stroke_weight: self.stroke_weight, ..Default::default() } @@ -360,8 +377,8 @@ pub mod styleconf { Texture(MurreletStyleFilledSvg), Fill(MurreletStyleFilled), Outline(MurreletStyleOutlined), - Points(MurreletStylePoints), Line(MurreletStyleLined), + Points(MurreletStylePoints), ThickLine, RGBAFill(MurreletStyleRGBAFill), RGBALine(MurreletStyleRGBALine), @@ -369,9 +386,12 @@ pub mod styleconf { RGBAPoints(MurreletStyleRGBAPoints), } impl StyleConf { + pub fn black_fill() -> Self { + Self::fill(MurreletColor::black()) + } + pub fn to_style(&self) -> MurreletStyle { match self { - // StyleConf::Verbose(a) => *a, StyleConf::Fill(a) => a.to_style(), StyleConf::Outline(a) => a.to_style(), StyleConf::Line(a) => a.to_style(), @@ -389,6 +409,10 @@ pub mod styleconf { self.to_style().color.as_color() } + pub fn stroke_weight(&self) -> f32 { + self.to_style().stroke_weight + } + pub fn outline(color: MurreletColor, stroke_weight: f32) -> Self { Self::Outline(MurreletStyleOutlined { color, @@ -396,6 +420,14 @@ pub mod styleconf { }) } + pub fn font(color: MurreletColor, font_size: f32) -> Self { + Self::Fill(MurreletStyleFilled { + color, + stroke_weight: font_size, + stroke_color: MurreletColor::transparent(), + }) + } + pub fn line(color: MurreletColor, stroke_weight: f32) -> Self { Self::Line(MurreletStyleLined { color, @@ -407,9 +439,25 @@ pub mod styleconf { Self::Fill(MurreletStyleFilled { color, stroke_weight: 0.0, - stroke_color: MurreletColor::black(), + stroke_color: MurreletColor::transparent(), }) } + + pub fn outlined_fill( + color: MurreletColor, + stroke_weight: f32, + stroke_color: MurreletColor, + ) -> Self { + Self::Fill(MurreletStyleFilled { + color, + stroke_weight, + stroke_color, + }) + } + + pub fn fill_color(&self) -> MurreletColor { + self.color() + } } impl Default for StyleConf { @@ -419,56 +467,105 @@ pub mod styleconf { } } +impl CanMakeGUI for StyleConf { + fn make_gui() -> murrelet_gui::MurreletGUISchema { + murrelet_gui::MurreletGUISchema::Val(murrelet_gui::ValueGUI::Style) + } +} + +#[derive(Debug, Clone)] +pub enum MurreletCurveKinds { + CD(CurveDrawer), + Svg(SvgPathDef), +} + // this one attaches a transform to the curve. // you can _try_ to apply it using to_curve_maker, but this // will act funny for non-affine #[derive(Debug, Clone)] pub struct MurreletCurve { - cd: CurveDrawer, - t: Mat4, + cd: MurreletCurveKinds, + t: SimpleTransform2d, } impl MurreletCurve { pub fn new(cd: CurveDrawer) -> Self { Self { - cd, - t: Mat4::IDENTITY, + cd: MurreletCurveKinds::CD(cd), + t: SimpleTransform2d::noop(), } } - pub fn transform_with_mat4_after(&self, t: Mat4) -> MurreletCurve { + pub fn transform_after(&self, t: &T) -> MurreletCurve { Self { cd: self.cd.clone(), - t: t * self.t, + t: self.t.add_transform_after(t), } } - pub fn transform_with_mat4_before(&self, t: Mat4) -> MurreletCurve { + pub fn transform_before(&self, t: &T) -> MurreletCurve { Self { cd: self.cd.clone(), - t: self.t * t, + t: self.t.add_transform_before(t), } } pub fn mat4(&self) -> Mat4 { - self.t + self.t.to_mat4() + } + + pub fn curve(&self) -> CurveDrawer { + match &self.cd { + MurreletCurveKinds::CD(cd) => cd.clone(), + MurreletCurveKinds::Svg(pts) => { + let s = parse_svg_path_as_vec2(pts, 1.0); + CurveDrawer::new_simple_points(s, false) + } + } } - pub fn curve(&self) -> &CurveDrawer { + fn new_transformed_svg(svg: &TransformedSvgShape) -> MurreletCurve { + Self { + cd: MurreletCurveKinds::Svg(svg.shape.as_path()), + t: svg.t.clone(), + } + } + + pub fn cd(&self) -> &MurreletCurveKinds { &self.cd } + + fn to_svg(&self) -> TransformedSvgShape { + let c = match &self.cd { + MurreletCurveKinds::CD(cd) => TransformedSvgShape::from_cd(cd), + MurreletCurveKinds::Svg(pts) => { + TransformedSvgShape::from_shape(crate::svg::SvgShape::Path(pts.clone())) + } + }; + c.transform_after(&self.t) + } + + pub fn transform(&self) -> SimpleTransform2d { + self.t.clone() + } } #[derive(Debug, Clone)] pub enum MurreletPath { Polyline(Polyline), Curve(MurreletCurve), + Svg(TransformedSvgShape), + MaskedCurve(MurreletCurve, MurreletCurve), // first is mask } impl MurreletPath { pub fn polyline(path: F) -> Self { Self::Polyline(path.as_polyline()) } + pub fn svg_circle(loc: Vec2, rad: f32) -> Self { + Self::Svg(TransformedSvgShape::from_shape(SvgShape::circle(loc, rad))) + } + pub fn curve(cd: CurveDrawer) -> Self { Self::Curve(MurreletCurve::new(cd)) } @@ -476,38 +573,138 @@ impl MurreletPath { pub fn as_curve(&self) -> MurreletCurve { match self { MurreletPath::Polyline(c) => MurreletCurve { - cd: CurveDrawer::new_simple_points(c.clone_to_vec(), false), - t: Mat4::IDENTITY, + cd: MurreletCurveKinds::CD(CurveDrawer::new_simple_points(c.clone_to_vec(), false)), + t: SimpleTransform2d::noop(), }, MurreletPath::Curve(c) => c.clone(), + MurreletPath::Svg(svg) => MurreletCurve::new_transformed_svg(svg), + MurreletPath::MaskedCurve(_mask, c) => c.clone(), } } - pub fn transform_with(&self, t: &T) -> Self { + pub fn transform_with(&self, t: &T) -> Self { match self { MurreletPath::Polyline(x) => MurreletPath::Polyline(x.transform_with(t)), - MurreletPath::Curve(_) => todo!(), // i'm not sure how i want to handle this yet + MurreletPath::Curve(cd) => MurreletPath::Svg(cd.to_svg().transform_after(t)), + MurreletPath::Svg(svg) => MurreletPath::Svg(svg.transform_after(t)), + MurreletPath::MaskedCurve(_, _) => todo!(), } } - pub fn transform_with_mat4_after(&self, t: Mat4) -> MurreletPath { + pub fn transform_with_mat4_after( + &self, + t: T, + ) -> MurreletPath { match self { MurreletPath::Polyline(_) => self.transform_with(&t), - MurreletPath::Curve(c) => MurreletPath::Curve(c.transform_with_mat4_after(t)), + MurreletPath::Curve(c) => MurreletPath::Curve(c.transform_after(&t)), + MurreletPath::Svg(c) => MurreletPath::Svg(c.transform_after(&t)), + MurreletPath::MaskedCurve(mask, curve) => { + MurreletPath::MaskedCurve(mask.transform_after(&t), curve.transform_after(&t)) + } } } - pub fn transform_with_mat4_before(&self, t: Mat4) -> MurreletPath { + pub fn transform_with_mat4_before(&self, t: &T) -> MurreletPath { match self { - MurreletPath::Polyline(_) => self.transform_with(&t), - MurreletPath::Curve(c) => MurreletPath::Curve(c.transform_with_mat4_before(t)), + MurreletPath::Polyline(_) => self.transform_with(t), + MurreletPath::Curve(c) => MurreletPath::Curve(c.transform_before(t)), + MurreletPath::Svg(c) => MurreletPath::Svg(c.transform_before(t)), + MurreletPath::MaskedCurve(mask, curve) => { + MurreletPath::MaskedCurve(mask.transform_before(t), curve.transform_before(t)) + } } } pub fn transform(&self) -> Option { match self { MurreletPath::Polyline(_) => None, - MurreletPath::Curve(c) => Some(c.t), + MurreletPath::Curve(c) => Some(c.t.to_mat4()), + MurreletPath::Svg(c) => Some(c.t.to_mat4()), + MurreletPath::MaskedCurve(_mask, c) => Some(c.t.to_mat4()), + } + } +} + +#[derive(Debug, Clone)] +pub struct MurreletPathAnnotation(Vec<(String, String)>); +impl MurreletPathAnnotation { + pub fn noop() -> MurreletPathAnnotation { + Self(vec![]) + } + + pub fn new(annotation: (String, String)) -> Self { + Self(vec![annotation]) + } + + pub fn vals(&self) -> &Vec<(String, String)> { + &self.0 + } + + fn new_many(annotations: Vec<(String, String)>) -> MurreletPathAnnotation { + Self(annotations) + } +} + +pub trait ShapeToStyledPath { + fn from_mstyle(&self, style: &MurreletStyle) -> StyledPath; + fn from_style(&self, style: &StyleConf) -> StyledPath { + self.from_mstyle(&style.to_style()) + } +} + +impl ShapeToStyledPath for Polyline { + fn from_mstyle(&self, style: &MurreletStyle) -> StyledPath { + StyledPath::new_from_path(MurreletPath::Polyline(self.clone()), *style) + } +} + +impl ShapeToStyledPath for CurveDrawer { + fn from_mstyle(&self, style: &MurreletStyle) -> StyledPath { + StyledPath::new_from_path( + MurreletPath::Curve(MurreletCurve::new(self.clone())), + *style, + ) + } +} + +impl ToLyonPath for MurreletPath { + fn approx_vertex_count(&self) -> usize { + match self { + MurreletPath::Polyline(p) => p.len(), + MurreletPath::Curve(cd) => match &cd.cd { + MurreletCurveKinds::CD(curve_drawer) => curve_drawer.approx_vertex_count(), + MurreletCurveKinds::Svg(_) => todo!(), + }, + MurreletPath::Svg(_) => todo!(), + MurreletPath::MaskedCurve(_, _) => todo!(), + } + } + + fn start(&self) -> Vec2 { + match self { + MurreletPath::Polyline(p) => *p.first().unwrap_or(&Vec2::ZERO), + MurreletPath::Curve(cd) => match &cd.cd { + MurreletCurveKinds::CD(curve_drawer) => curve_drawer.start(), + MurreletCurveKinds::Svg(_) => todo!(), + }, + MurreletPath::Svg(_) => todo!(), + MurreletPath::MaskedCurve(_, _) => todo!(), + } + } + + fn add_to_lyon( + &self, + builder: &mut B, + ) -> murrelet_livecode::types::LivecodeResult { + match self { + MurreletPath::Polyline(p) => p.to_cd_open().add_to_lyon(builder), + MurreletPath::Curve(cd) => match &cd.cd { + MurreletCurveKinds::CD(curve_drawer) => curve_drawer.add_to_lyon(builder), + MurreletCurveKinds::Svg(_) => todo!(), + }, + MurreletPath::Svg(_) => todo!(), + MurreletPath::MaskedCurve(_, _) => todo!(), } } } @@ -516,34 +713,65 @@ impl MurreletPath { pub struct StyledPath { pub path: MurreletPath, pub style: MurreletStyle, + pub annotations: MurreletPathAnnotation, // can be useful to attach information to a particular path, for interactions } impl StyledPath { pub fn new_from_path(path: MurreletPath, style: MurreletStyle) -> Self { - Self { path, style } + Self { + path, + style, + annotations: MurreletPathAnnotation::noop(), + } + } + + pub fn new_from_path_with_annotation( + path: MurreletPath, + style: MurreletStyle, + annotation: (String, String), + ) -> Self { + Self { + path, + style, + annotations: MurreletPathAnnotation::new(annotation), + } + } + + pub fn new_from_path_with_multiple_annotations( + path: MurreletPath, + style: MurreletStyle, + annotations: Vec<(String, String)>, + ) -> Self { + Self { + path, + style, + annotations: MurreletPathAnnotation::new_many(annotations), + } } pub fn new(path: F, style: MurreletStyle) -> Self { Self { path: MurreletPath::Polyline(path.as_polyline()), style, + annotations: MurreletPathAnnotation::noop(), } } - pub fn from_path(path: P) -> StyledPath { + pub fn from_path(path: P) -> StyledPath { StyledPath { - path: MurreletPath::Polyline(path.as_polyline()), + path: MurreletPath::Curve(MurreletCurve::new(path.to_cd_open())), style: MurreletStyle::default(), + annotations: MurreletPathAnnotation::noop(), } } - pub fn transform_path(&self, t: &T) -> Self { + pub fn transform_path(&self, t: &T) -> Self { StyledPath { path: self.path.transform_with(t), ..self.clone() } } - pub fn transform_with_mat4_after(&self, t: Mat4) -> Self { + pub fn transform_with_mat4_after(&self, t: T) -> Self { StyledPath { path: self.path.transform_with_mat4_after(t), ..self.clone() diff --git a/murrelet_draw/src/svg.rs b/murrelet_draw/src/svg.rs new file mode 100644 index 0000000..e17af2b --- /dev/null +++ b/murrelet_draw/src/svg.rs @@ -0,0 +1,398 @@ +// defines the SVG basic shapes + +use glam::Vec2; +use lerpable::Lerpable; +use lyon::geom::{euclid::Point2D, Point}; +use murrelet_common::{SimpleTransform2d, ToSimpleTransform}; +use murrelet_gui::MurreletGUI; +use murrelet_livecode_derive::Livecode; + +use crate::curve_drawer::{CurveArc, CurveDrawer}; + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub struct SvgRect { + #[livecode(serde_default = "0")] + pub x: f32, + #[livecode(serde_default = "0")] + pub y: f32, + #[livecode(serde_default = "0")] + pub rx: f32, // x corner radius + #[livecode(serde_default = "0")] + pub ry: f32, // y corner radius + pub width: f32, + pub height: f32, +} + +impl SvgRect { + pub fn new_centered(width: f32, height: f32) -> Self { + Self { + x: -width / 2.0, + y: -height / 2.0, + rx: 0.0, + ry: 0.0, + width, + height, + } + } + + pub fn new_at_loc(loc: Vec2, w_h: Vec2) -> Self { + Self { + x: loc.x - w_h.x / 2.0, + y: loc.y - w_h.y / 2.0, + rx: 0.0, + ry: 0.0, + width: w_h.x, + height: w_h.y, + } + } +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub struct SvgCircle { + #[livecode(serde_default = "0")] + pub x: f32, + #[livecode(serde_default = "0")] + pub y: f32, + #[livecode(serde_default = "1")] + pub r: f32, +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub struct SvgTo { + x: f32, + y: f32, +} +impl SvgTo { + pub fn params(&self) -> (f32, f32) { + (self.x, self.y) + } + + pub fn to(&self) -> Vec2 { + (self.x, self.y).into() + } +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub struct SvgCubicBezier { + ctrl1_x: f32, + ctrl1_y: f32, + ctrl2_x: f32, + ctrl2_y: f32, + to_x: f32, + to_y: f32, +} +impl SvgCubicBezier { + pub fn to(&self) -> Vec2 { + (self.to_x, self.to_y).into() + } + + pub fn params(&self) -> (f32, f32, f32, f32, f32, f32) { + ( + self.ctrl1_x, + self.ctrl1_y, + self.ctrl2_x, + self.ctrl2_y, + self.to_x, + self.to_y, + ) + } +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub struct SvgArc { + radius_x: f32, + radius_y: f32, + x_axis_rotation: f32, + large_arc_flag: f32, + sweep_flag: f32, + to_x: f32, + to_y: f32, +} + +impl SvgArc { + pub fn to(&self) -> Vec2 { + (self.to_x, self.to_y).into() + } + + pub fn new( + radius_x: f32, + radius_y: f32, + x_axis_rotation: f32, + large_arc_flag: f32, + sweep_flag: f32, + to_x: f32, + to_y: f32, + ) -> Self { + Self { + radius_x, + radius_y, + x_axis_rotation, + large_arc_flag, + sweep_flag, + to_x, + to_y, + } + } + + pub fn from_arc(a: &CurveArc) -> Self { + let [a, b, c, d, e, f, g] = a.svg_params(); + SvgArc::new(a, b, c, d, e, f, g) + } + + pub fn params(&self) -> (f32, f32, f32, f32, f32, f32, f32) { + ( + self.radius_x, + self.radius_y, + self.x_axis_rotation, + self.large_arc_flag, + self.sweep_flag, + self.to_x, + self.to_y, + ) + } +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub enum SvgCmd { + Line(SvgTo), + CubicBezier(SvgCubicBezier), + ArcTo(SvgArc), +} +impl SvgCmd { + pub fn to(&self) -> Vec2 { + match self { + SvgCmd::Line(svg_to) => svg_to.to(), + SvgCmd::CubicBezier(svg_cubic_bezier) => svg_cubic_bezier.to(), + SvgCmd::ArcTo(svg_arc) => svg_arc.to(), + } + } +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub struct SvgPathDef { + start: SvgTo, + v: Vec, +} + +impl SvgPathDef { + pub fn start(&self) -> Vec2 { + (self.start.x, self.start.y).into() + } + + pub fn new(start: Vec2) -> Self { + Self { + start: SvgTo { + x: start.x, + y: start.y, + }, + v: Vec::new(), + } + } + + pub fn add_line(&mut self, to: Vec2) { + self.v.push(SvgCmd::Line(SvgTo { x: to.x, y: to.y })); + } + + fn add_curve_points(&mut self, curve_points: &crate::curve_drawer::CurvePoints) { + for c in curve_points.points() { + self.add_line(*c); + } + } + + pub fn add_cubic_bezier(&mut self, ctrl1: Vec2, ctrl2: Vec2, to: Vec2) { + self.v.push(SvgCmd::CubicBezier(SvgCubicBezier { + ctrl1_x: ctrl1.x, + ctrl1_y: ctrl1.y, + ctrl2_x: ctrl2.x, + ctrl2_y: ctrl2.y, + to_x: to.x, + to_y: to.y, + })) + } + + fn add_curve_bezier(&mut self, curve_cubic_bezier: &crate::curve_drawer::CurveCubicBezier) { + self.add_cubic_bezier( + curve_cubic_bezier.ctrl1(), + curve_cubic_bezier.ctrl2(), + curve_cubic_bezier.to(), + ); + } + + fn add_curve_arc(&mut self, curve_arc: &CurveArc) { + if let Some((hemi1, hemi2)) = curve_arc.is_full_circle_then_split() { + self.v.push(SvgCmd::ArcTo(SvgArc::from_arc(&hemi1))); + self.v.push(SvgCmd::ArcTo(SvgArc::from_arc(&hemi2))); + } else { + self.v.push(SvgCmd::ArcTo(SvgArc::from_arc(curve_arc))); + } + } + + // fn add_curve_arc(&mut self, curve_arc: &CurveArc) { + // self.v.push(SvgCmd::ArcTo(SvgArc::from_arc(curve_arc))); + // } + + pub fn svg_move_to(&self) -> (f32, f32) { + (self.start.x, self.start.y) + } + + pub fn cmds(&self) -> &[SvgCmd] { + &self.v + } + + fn from_cd(cd: &CurveDrawer) -> SvgPathDef { + // if it's a weird cd, then just draw a dot at 0, 0 and be done... + // probably want to do better error handling here + let mut curr = cd.first_point().unwrap_or_default(); + let mut path = SvgPathDef::new(curr); + + for s in cd.segments() { + if curr != s.first_point() { + path.add_line(s.first_point()); + } + + match s { + crate::curve_drawer::CurveSegment::Arc(curve_arc) => { + path.add_curve_arc(curve_arc); + } + crate::curve_drawer::CurveSegment::Points(curve_points) => { + path.add_curve_points(curve_points); + } + crate::curve_drawer::CurveSegment::CubicBezier(curve_cubic_bezier) => { + path.add_curve_bezier(curve_cubic_bezier); + } + } + + curr = s.last_point(); + } + + path + } +} + +pub fn glam_to_lyon(vec: Vec2) -> Point2D { + Point::new(vec.x, vec.y) +} + +#[derive(Clone, Debug, Livecode, MurreletGUI, Lerpable)] +pub enum SvgShape { + Rect(SvgRect), + Circle(SvgCircle), + Path(SvgPathDef), +} + +impl SvgShape { + pub fn new_rect_at_loc(loc: Vec2, w_h: Vec2) -> Self { + SvgShape::Rect(SvgRect::new_at_loc(loc, w_h)) + } + + pub fn new_centered_rect(width: f32, height: f32) -> Self { + SvgShape::Rect(SvgRect::new_centered(width, height)) + } + pub fn new_centered_circle(radius: f32) -> Self { + SvgShape::Circle(SvgCircle { + x: 0.0, + y: 0.0, + r: radius, + }) + } + + pub fn transform(&self, t: &F) -> TransformedSvgShape { + TransformedSvgShape { + shape: self.clone(), + t: t.to_simple_transform(), + } + } + + pub fn as_transform(&self) -> TransformedSvgShape { + TransformedSvgShape { + shape: self.clone(), + t: SimpleTransform2d::noop(), + } + } + + pub(crate) fn as_path(&self) -> SvgPathDef { + match self { + SvgShape::Rect(_) => todo!(), + SvgShape::Circle(_) => todo!(), + SvgShape::Path(svg_path_def) => svg_path_def.clone(), + } + } + + pub(crate) fn circle(loc: Vec2, rad: f32) -> SvgShape { + SvgShape::Circle(SvgCircle { + x: loc.x, + y: loc.y, + r: rad, + }) + } +} + +#[derive(Clone, Debug)] +pub struct TransformedSvgShape { + pub shape: SvgShape, + pub t: SimpleTransform2d, +} +impl TransformedSvgShape { + pub fn from_shape(shape: SvgShape) -> Self { + Self { + shape, + t: SimpleTransform2d::noop(), + } + } + + pub fn from_cd(cd: &CurveDrawer) -> Self { + let shape = SvgShape::Path(SvgPathDef::from_cd(cd)); + + Self { + shape, + t: SimpleTransform2d::noop(), + } + } + + pub fn transform_after(&self, t: &F) -> Self { + Self { + shape: self.shape.clone(), + t: self.t.add_transform_after(t), + } + } + + pub fn transform_before(&self, t: &F) -> Self { + Self { + shape: self.shape.clone(), + t: self.t.add_transform_before(t), + } + } +} + +#[cfg(test)] +mod tests { + use glam::vec2; + use murrelet_common::AnglePi; + + use super::*; + + #[test] + fn test_curve_drawer_to_svg_to_curve_drawer_arc() { + let cd = CurveDrawer::new_simple_arc( + Vec2::new(20.0, 20.0), + 5.0, + AnglePi::new(-1.0), + AnglePi::new(0.0), + ); + + let first_point_before = cd.first_point().unwrap(); + let last_point_before = cd.last_point().unwrap(); + + assert_eq!(first_point_before, vec2(15.0, 20.0)); + assert_eq!(last_point_before, vec2(25.0, 20.0)); + + // Convert to SvgPathDef + let svg_path = SvgPathDef::from_cd(&cd); + + assert_eq!(svg_path.start(), first_point_before); + let cmds = svg_path.cmds().to_vec(); + assert_eq!(cmds.len(), 1); + + assert_eq!(cmds[0].to(), last_point_before); + } +} diff --git a/murrelet_draw/src/tesselate.rs b/murrelet_draw/src/tesselate.rs new file mode 100644 index 0000000..3dc4735 --- /dev/null +++ b/murrelet_draw/src/tesselate.rs @@ -0,0 +1,1163 @@ +use std::{collections::HashMap, f32::consts::PI, ops}; + +use crate::{ + cubic::CubicBezier, + curve_drawer::{ + CubicBezierPath, CurveArc, CurveCubicBezier, CurveDrawer, CurvePoints, CurveSegment, + }, + svg::SvgPathDef, +}; +use delaunator::Triangulation; +use glam::{vec2, Vec2, Vec2Swizzles}; +use itertools::Itertools; +use kurbo::BezPath; +use lyon::geom::vector; +use lyon::{geom::arc::Arc, math::Transform}; +use lyon::{geom::Angle, path::traits::Build}; +use lyon::{ + geom::{ + euclid::{Point2D, UnknownUnit}, + point, Point, + }, + path::{traits::PathBuilder, FillRule, Path}, + tessellation::{BuffersBuilder, FillOptions, FillTessellator, FillVertex}, +}; +use murrelet_common::{ + curr_next_no_loop_iter, triangulate::DefaultVertex, AnglePi, IsAngle, PointToPoint, Polyline, + SimpleTransform2d, SimpleTransform2dStep, SpotOnCurve, ToVec2, +}; +use murrelet_livecode::types::{LivecodeError, LivecodeResult}; + +pub trait ToVecVec2 { + fn to_vec2(&self) -> Vec; + + fn to_vec2_line_space(&self, _line_space: f32) -> Vec { + todo!() + } + + fn to_vec2_count(&self, _count: usize) -> Vec { + todo!() + } +} + +impl ToVecVec2 for CubicBezier { + fn to_vec2_line_space(&self, line_space: f32) -> Vec { + let mut svg = svg::node::element::path::Data::new(); + + let x = self.from.x; + let y = self.from.y; + let start: svg::node::element::path::Parameters = vec![x, y].into(); + svg = svg.move_to(start); + + let cubic: svg::node::element::path::Parameters = vec![ + self.ctrl1.x, + self.ctrl1.y, + self.ctrl2.x, + self.ctrl2.y, + self.to.x, + self.to.y, + ] + .into(); + svg = svg.cubic_curve_to(cubic); + + let mut path = parse_svg_data_as_vec2(&svg, line_space); + + if let Some(a) = path.last() { + if a.distance(self.to.yx()) > 1.0e-3 { + path.push(self.to.yx()) + } + } + + path.into_iter().map(|x| vec2(x.y, x.x)).collect_vec() + } + + fn to_vec2(&self) -> Vec { + self.to_vec2_line_space(1.0) + } +} + +impl ToVecVec2 for CubicBezierPath { + fn to_vec2(&self) -> Vec { + let svg = self.to_data(); + let path = parse_svg_data_as_vec2(&svg, 1.0); + + path.into_iter().map(|x| vec2(x.y, x.x)).collect_vec() + } +} + +pub trait AsLyonTransform { + fn to_lyon_transform(&self) -> Transform { + self.update_lyon_transform(Transform::identity()) + } + + fn update_lyon_transform(&self, t: Transform) -> Transform; +} + +impl AsLyonTransform for SimpleTransform2d { + fn update_lyon_transform(&self, t: Transform) -> Transform { + let mut aa = t; + for t in self.steps() { + aa = t.update_lyon_transform(aa); + } + aa + } +} + +impl AsLyonTransform for SimpleTransform2dStep { + fn update_lyon_transform(&self, t: Transform) -> Transform { + match self { + SimpleTransform2dStep::Translate(v) => t.then_translate(vector(v.x, v.y)), + SimpleTransform2dStep::Rotate(v, a) => { + let vv = vector(v.x, v.y); + let t = t.then_translate(-vv); + let t = t.then_rotate(Angle::radians(a.angle())); + + t.then_translate(vv) + } + SimpleTransform2dStep::Scale(v) => t.then_scale(v.x, v.y), + SimpleTransform2dStep::Skew(_, _) => unreachable!(), + } + } +} + +pub trait ToLyonPath { + const EPS: f32 = 1e-6f32; + + fn approx_vertex_count(&self) -> usize; + + fn to_lyon_with_transform( + &self, + t: &T, + ) -> LivecodeResult { + let transform = t.to_lyon_transform(); + + let mut lyon_builder = lyon::path::Path::builder().transformed(transform); + + let start = self.start(); + + lyon_builder.begin(v2p(start)); + let is_closed = self._add_to_lyon(start, &mut lyon_builder)?; + lyon_builder.end(is_closed); + + Ok(lyon_builder.build()) + } + + fn to_lyon(&self) -> LivecodeResult { + self.to_lyon_with_transform(&SimpleTransform2d::ident()) + } + + fn start(&self) -> Vec2; + + fn _start(&self) -> Point { + v2p(self.start()) + } + + fn _add_to_lyon(&self, start: Vec2, builder: &mut B) -> LivecodeResult { + // handles when "start" is too far away + if start.distance(self.start()) > Self::EPS { + builder.line_to(self._start()); + } + + self.add_to_lyon(builder) + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult; +} + +fn v2p(v: Vec2) -> Point { + point(v.x, v.y) +} + +impl ToLyonPath for CurvePoints { + fn start(&self) -> Vec2 { + self.first_point() + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult { + for p in self.points() { + if p.x.is_nan() || p.y.is_nan() { + return LivecodeError::rawr("nan in CurvePoints"); + } + + builder.line_to(v2p(*p)); + } + + Ok(false) + } + + fn approx_vertex_count(&self) -> usize { + self.points.len() + } +} + +impl ToLyonPath for CubicBezier { + fn start(&self) -> Vec2 { + self.from + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult { + if self.ctrl1.is_nan() || self.ctrl2.is_nan() || self.to.is_nan() || self.from.is_nan() { + return LivecodeError::rawr("nan in Bezier"); + } + + builder.cubic_bezier_to(v2p(self.ctrl1), v2p(self.ctrl2), v2p(self.to)); + + Ok(false) + } + + fn approx_vertex_count(&self) -> usize { + 4 + } +} + +impl ToLyonPath for CurveCubicBezier { + fn start(&self) -> Vec2 { + self.to_cubic().start() + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult { + self.to_cubic().add_to_lyon(builder)?; + + Ok(false) + } + + fn approx_vertex_count(&self) -> usize { + self.to_cubic().approx_vertex_count() + } +} + +// chatgpt +fn add_circular_arc(builder: &mut B, c: &CurveArc) { + let cx = c.loc.x; + let cy = c.loc.y; + let r = c.radius; + let start = c.start_pi().angle(); + let end: f32 = c.end_pi().angle(); + + // Angles are in radians. + let mut sweep = end - start; + + if c.is_ccw() { + if sweep < 0.0 { + sweep += 2.0 * PI; + } + } else if sweep > 0.0 { + sweep -= 2.0 * PI; + } + + let arc = Arc { + center: point(cx, cy), + radii: vector(r, r), + start_angle: Angle::radians(start), + sweep_angle: Angle::radians(sweep), + x_rotation: Angle::radians(0.0), + }; + + arc.for_each_cubic_bezier(&mut |c| { + builder.cubic_bezier_to(c.ctrl1, c.ctrl2, c.to); + }); +} + +impl ToLyonPath for CurveArc { + fn start(&self) -> Vec2 { + self.first_point() + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult { + if self.end_pi.angle_pi().is_nan() + || self.start_pi.angle_pi().is_nan() + || self.radius.is_nan() + || self.loc.x.is_nan() + || self.loc.y.is_nan() + { + return LivecodeError::rawr("nan in CurveArc"); + } + + add_circular_arc(builder, self); + + Ok(false) + } + + fn approx_vertex_count(&self) -> usize { + 4 + } +} + +impl ToLyonPath for CurveSegment { + fn start(&self) -> Vec2 { + self.first_point() + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult { + match self { + CurveSegment::Arc(c) => c.add_to_lyon(builder), + CurveSegment::Points(c) => c.add_to_lyon(builder), + CurveSegment::CubicBezier(c) => c.add_to_lyon(builder), + } + } + + fn approx_vertex_count(&self) -> usize { + match self { + CurveSegment::Arc(c) => c.approx_vertex_count(), + CurveSegment::Points(c) => c.approx_vertex_count(), + CurveSegment::CubicBezier(c) => c.approx_vertex_count(), + } + } +} + +impl ToLyonPath for CurveDrawer { + fn start(&self) -> Vec2 { + self.first_point().unwrap_or_default() //??? + } + + fn add_to_lyon(&self, builder: &mut B) -> LivecodeResult { + for s in self.segments() { + s.add_to_lyon(builder)?; + } + Ok(self.closed) + } + + fn approx_vertex_count(&self) -> usize { + let mut c = 0; + for s in self.segments() { + c += s.approx_vertex_count(); + } + c + } +} + +fn vec2_to_kurbo(v: Vec2) -> kurbo::Point { + kurbo::Point::new(v.x as f64, v.y as f64) +} + +fn vec2_to_pt(x: Vec2) -> lyon::geom::euclid::Point2D { + point(x.x, x.y) +} + +fn _point_from_params(params: &Vec<&f32>, idx: usize) -> Pt { + Pt::new(*params[idx * 2], *params[idx * 2 + 1]) +} + +fn point_from_param1(params: &Vec<&f32>) -> Pt { + _point_from_params(params, 0) +} + +fn point_from_param2(params: &Vec<&f32>) -> (Pt, Pt) { + (_point_from_params(params, 0), _point_from_params(params, 1)) +} + +fn point_from_param3(params: &Vec<&f32>) -> (Pt, Pt, Pt) { + ( + _point_from_params(params, 0), + _point_from_params(params, 1), + _point_from_params(params, 2), + ) +} + +pub fn many_pt2_to_vec2(ps: &Vec) -> Vec { + ps.iter().map(|p| p.as_vec2()).collect_vec() +} + +pub fn cubic_bezier_length(c: &CubicBezier) -> f32 { + let line = lyon::geom::CubicBezierSegment { + from: vec2_to_pt(c.from), + ctrl1: vec2_to_pt(c.ctrl1), + ctrl2: vec2_to_pt(c.ctrl2), + to: vec2_to_pt(c.to), + }; + line.approximate_length(0.1) +} + +pub fn segment_vec(from: Vec2, to: Vec2, line_space: f32, offset: f32) -> (Vec, f32) { + if to.distance(from) < 1e-7 { + return (vec![], offset); + } + + let mut dist_since_last = offset; // how far into this one we should start + dist_since_last += (to - from).length(); // add how far we will travel + + let backwards_dir = (to - from).normalize(); + + let mut lines = vec![]; + + while dist_since_last >= line_space { + // okay find how much we overshot it, and move backwards + // it should be within to and from.. or else we would've stopped before? + let overshot_amount = dist_since_last - line_space; + // figure out how to go backwards + // and now move backwards + let new_to = to - backwards_dir * overshot_amount; + lines.push(new_to); + dist_since_last = overshot_amount; + } + + (lines, dist_since_last) +} + +pub fn segment_arc( + curve: &CurveArc, + height: f32, + line_space: f32, + offset: f32, +) -> (Vec, f32) { + // going into this curve, we should be all caught up + let multi = if curve.is_ccw() { 1.0 } else { -1.0 }; + let radius = curve.radius.abs(); + + let increase_ratio = (radius + 0.5 * height) / radius; + let mut line_space = line_space / increase_ratio; + + // make sure we always have at least a few points + let max_delta_angle = AnglePi::new(0.1); + line_space = line_space.min(radius * max_delta_angle._angle()); + + // expect line_space to be > 0 + if line_space <= 0.0 { + return (vec![], offset); + } + + // okay! now if we already traveled some distance along this curve (e.g. dist_since_last > 0) + // we need to remove some of it + let diameter = AnglePi::new(2.0).scale(radius).angle(); + + let estimated_size = (diameter / line_space) as usize; + let mut vs = Vec::with_capacity(estimated_size); + + let mut residual = offset + curve.length(); + + // the last point was shifted back + let mut loc_on_arc = -offset; + + while residual >= line_space { + residual -= line_space; + loc_on_arc += line_space; + + let central_angle_from_start = AnglePi::new(2.0).scale(multi * loc_on_arc / diameter); + let curr_angle = curve.start_pi() + central_angle_from_start; + + let norm_vec = curr_angle.to_norm_dir(); + let curr_point = norm_vec * radius + curve.loc; + + let a = if curve.is_ccw() { + curr_angle.perp_to_right() + } else { + curr_angle.perp_to_left() + }; + + let s = SpotOnCurve::new(curr_point, a); + vs.push(s); + } + + (vs, residual / increase_ratio) +} + +pub fn evenly_split_cubic_bezier(c: &CubicBezier, count: usize) -> Vec { + // always include the start (and end) + let v = flatten_cubic_bezier_path_with_tolerance(&[*c], false, 0.1); + if count <= 1 { + let angle = PointToPoint::new(c.from, c.to).angle(); + return vec![c.from, c.to] + .into_iter() + .map(|x| SpotOnCurve::new(x, angle)) + .collect_vec(); + } + + if let Some(a) = v { + let mut length = 0.0; + for (curr, next) in curr_next_no_loop_iter(&a) { + length += curr.distance(*next); + } + + let spacing_for_dot = length / count as f32; + + let mut last_angle = None; + + let mut v = vec![]; + let mut dist = 0.0; + for (curr, next) in curr_next_no_loop_iter(&a) { + let (vs, a) = segment_vec(*curr, *next, spacing_for_dot, dist); + let angle = PointToPoint::new(*curr, *next).angle(); + + // if it's the very first item + if last_angle.is_none() { + v.push(SpotOnCurve::new(*curr, angle)) + } + + let new = vs + .into_iter() + .map(|x| SpotOnCurve::new(x, angle)) + .collect_vec(); + v.extend(new); + dist = a; + + last_angle = Some(angle); + } + + v + } else { + let angle = PointToPoint::new(c.from, c.to).angle(); + vec![c.from, c.to] + .into_iter() + .map(|x| SpotOnCurve::new(x, angle)) + .collect_vec() + } +} + +pub fn flatten_cubic_bezier_path_with_tolerance( + path: &[CubicBezier], + closed: bool, + tolerance: f32, +) -> Option> { + if path.is_empty() { + return None; + } + let mut kurbo_path = BezPath::new(); + + kurbo_path.move_to(vec2_to_kurbo(path[0].from)); + for c in path { + kurbo_path.curve_to( + vec2_to_kurbo(c.ctrl1), + vec2_to_kurbo(c.ctrl2), + vec2_to_kurbo(c.to), + ) + } + + if closed { + kurbo_path.close_path(); + } + + let mut points = Vec::new(); + kurbo::flatten(kurbo_path, tolerance as f64, |el| match el { + kurbo::PathEl::MoveTo(p) | kurbo::PathEl::LineTo(p) => { + points.push(vec2(p.x as f32, p.y as f32)); + } + kurbo::PathEl::ClosePath => { + if let Some(first) = points.first() { + points.push(*first); + } + } + _ => {} + }); + + Some(points) +} + +pub fn flatten_cubic_bezier_path(path: &[CubicBezier], closed: bool) -> Option> { + flatten_cubic_bezier_path_with_tolerance(path, closed, 0.01) +} + +pub fn cubic_bezier_path_to_lyon(path: &[CubicBezier], closed: bool) -> Option { + // let mut builder = Path::builder(); + + if path.is_empty() { + return None; + } + let mut kurbo_path = BezPath::new(); + + kurbo_path.move_to(vec2_to_kurbo(path[0].from)); + for c in path { + kurbo_path.curve_to( + vec2_to_kurbo(c.ctrl1), + vec2_to_kurbo(c.ctrl2), + vec2_to_kurbo(c.to), + ) + } + + if closed { + kurbo_path.close_path(); + } + + let tolerance = 0.01; + + let mut lyon_builder = lyon::path::Path::builder(); + kurbo::flatten(kurbo_path, tolerance, |el| { + match el { + kurbo::PathEl::MoveTo(p) => { + lyon_builder.begin(point(p.x as f32, p.y as f32)); + } + kurbo::PathEl::LineTo(p) => { + lyon_builder.line_to(point(p.x as f32, p.y as f32)); + } + kurbo::PathEl::ClosePath => lyon_builder.close(), + // The flatten iterator produces only MoveTo, LineTo, and ClosePath. + _ => {} + } + }); + let path = lyon_builder.build(); + Some(path) +} + +pub fn tesselate_lyon_vertex_with_steiner( + outline: &[DefaultVertex], + steiner: &[DefaultVertex], +) -> (Vec, Vec) { + let mut path_builder = Path::builder_with_attributes(5); + + // convert path to lyon + if let Some(first_vertex) = outline.first() { + path_builder.begin(vec2_to_pt(first_vertex.pos2d()), &first_vertex.attrs()); + for vertex in outline.iter().skip(1) { + path_builder.line_to(vec2_to_pt(vertex.pos2d()), &vertex.attrs()); + } + path_builder.close(); + } else { + return (Vec::new(), Vec::new()); + } + + let amount = 1e-6f32; + for s in steiner { + // now poke holes in it + let loc = s.pos2d(); + + let p0 = loc + vec2(amount, 0.0); + let p1 = loc + vec2(0.0, amount); + let p2 = loc + vec2(-amount, 0.0); + path_builder.begin(vec2_to_pt(p0), &s.attrs()); + path_builder.line_to(vec2_to_pt(p1), &s.attrs()); + path_builder.line_to(vec2_to_pt(p2), &s.attrs()); + path_builder.close() + } + + let path = path_builder.build(); + + let opts = FillOptions::default() + .with_fill_rule(FillRule::EvenOdd) + .with_intersections(true); + + let mut geometry: lyon::lyon_tessellation::VertexBuffers = + lyon::lyon_tessellation::VertexBuffers::new(); + let mut tess = FillTessellator::new(); + tess.tessellate_path( + path.as_slice(), + &opts, + &mut BuffersBuilder::new(&mut geometry, |mut v: FillVertex| { + let pos = v.position(); + let attrs = v.interpolated_attributes(); + + DefaultVertex { + position: [pos.x, pos.y, 0.0], + normal: [attrs[0], attrs[1], attrs[2]], + face_pos: [attrs[3], attrs[4]], + } + }), + ) + .expect("tessellation failed"); + + (geometry.indices, geometry.vertices) +} + +pub fn tesselate_lyon_vertex_simple(outline: &[DefaultVertex]) -> (Vec, Vec) { + let mut path_builder = Path::builder_with_attributes(5); + + // convert path to lyon + if let Some(first_vertex) = outline.first() { + path_builder.begin(vec2_to_pt(first_vertex.pos2d()), &first_vertex.attrs()); + for vertex in outline.iter().skip(1) { + path_builder.line_to(vec2_to_pt(vertex.pos2d()), &vertex.attrs()); + } + path_builder.close(); + } else { + return (Vec::new(), Vec::new()); + } + + let path = path_builder.build(); + + let opts = FillOptions::default() + .with_fill_rule(FillRule::EvenOdd) + .with_intersections(true); + + let mut geometry: lyon::lyon_tessellation::VertexBuffers = + lyon::lyon_tessellation::VertexBuffers::new(); + let mut tess = FillTessellator::new(); + tess.tessellate_path( + path.as_slice(), + &opts, + &mut BuffersBuilder::new(&mut geometry, |mut v: FillVertex| { + let pos = v.position(); + let attrs = v.interpolated_attributes(); + + DefaultVertex { + position: [pos.x, pos.y, 0.0], + normal: [attrs[0], attrs[1], attrs[2]], + face_pos: [attrs[3], attrs[4]], + } + }), + ) + .expect("tessellation failed"); + + (geometry.indices, geometry.vertices) +} + +pub fn tesselate_lyon(path: &Path) -> (Vec, Vec<[f32; 3]>) { + let opts = FillOptions::default() + .with_tolerance(0.1) + .with_fill_rule(FillRule::EvenOdd) + .with_intersections(true); + + let mut geometry: lyon::lyon_tessellation::VertexBuffers<[f32; 3], u32> = + lyon::lyon_tessellation::VertexBuffers::new(); + let mut tess = FillTessellator::new(); + tess.tessellate_path( + path.as_slice(), + &opts, + &mut BuffersBuilder::new(&mut geometry, |v: FillVertex| { + let p = v.position(); + [p.x, p.y, 0.0] + }), + ) + .expect("tessellation failed"); + + (geometry.indices, geometry.vertices) +} + +pub fn parse_svg_data_as_vec2(data: &svg::node::element::path::Data, line_space: f32) -> Vec { + parse_data(data, line_space) +} + +// svg loader +fn parse_data(data: &svg::node::element::path::Data, line_space: f32) -> Vec { + let mut segment_state = SegmentState::new_with_line_space(line_space); + + let mut from = Pt::new(0.0, 0.0); + + // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths + for command in data.iter() { + // println!("{:?}", command); + + match command { + svg::node::element::path::Command::Move(_pos, params) => { + let curve: Vec<&f32> = params.iter().collect(); + from = point_from_param1(&curve); + } + svg::node::element::path::Command::Line(pos, params) => { + for raw_curve in ¶ms.iter().chunks(2) { + let curve: Vec<&f32> = raw_curve.collect(); + + let to = point_for_position(pos, Pt::new(*curve[0], *curve[1]), from); + + let line = lyon::geom::LineSegment { + from: from.into(), + to: to.into(), + }; + + let length = line.length(); + + segment_state.add_segment(line, length); + + from = to; + } + } + svg::node::element::path::Command::HorizontalLine(pos, params) => { + for next_point in params.iter() { + let to = match pos { + svg::node::element::path::Position::Absolute => { + Pt::new(*next_point, from.y()) + } + svg::node::element::path::Position::Relative => { + Pt::new(next_point + from.x(), from.y()) + } + }; + + let line = lyon::geom::LineSegment { + from: from.into(), + to: to.into(), + }; + + let length = line.length(); + + segment_state.add_segment(line, length); + + from = to; + } + } + svg::node::element::path::Command::VerticalLine(pos, params) => { + for next_point in params.iter() { + let to = match pos { + svg::node::element::path::Position::Absolute => { + Pt::new(from.x(), *next_point) + } + svg::node::element::path::Position::Relative => { + Pt::new(from.x(), next_point + from.y()) + } + }; + + let line = lyon::geom::LineSegment { + from: from.into(), + to: to.into(), + }; + + let length = line.length(); + + segment_state.add_segment(line, length); + + from = to; + } + } + svg::node::element::path::Command::CubicCurve(pos, params) => { + for raw_curve in ¶ms.iter().chunks(6) { + let curve: Vec<&f32> = raw_curve.collect(); + let (raw_ctrl1, raw_ctrl2, raw_to) = point_from_param3(&curve); + + let ctrl1 = point_for_position(pos, raw_ctrl1, from); + let ctrl2 = point_for_position(pos, raw_ctrl2, from); + let to = point_for_position(pos, raw_to, from); + + let line = lyon::geom::CubicBezierSegment { + from: from.into(), + ctrl1: ctrl1.into(), + ctrl2: ctrl2.into(), + to: to.into(), + }; + + let length = line.approximate_length(0.1); + + segment_state.add_segment(line, length); + + // prev_ctrl = Some(raw_ctrl2); + + from = to; + } + } + svg::node::element::path::Command::SmoothCubicCurve( + svg::node::element::path::Position::Relative, + params, + ) => { + for raw_curve in ¶ms.iter().chunks(4) { + let curve: Vec<&f32> = raw_curve.collect(); + let (raw_ctrl2, raw_to) = point_from_param2(&curve); + + let ctrl2 = raw_ctrl2 + from; + let to = raw_to + from; + + let ctrl1 = Pt::new(from.x(), from.y()); // i'm.. surprised this works + + let line = lyon::geom::CubicBezierSegment { + from: from.into(), + ctrl1: ctrl1.into(), + ctrl2: ctrl2.into(), + to: to.into(), + }; + + let length = line.approximate_length(0.1); + + segment_state.add_segment(line, length); + + from = to; + } + } + svg::node::element::path::Command::Close => {} + svg::node::element::path::Command::QuadraticCurve(pos, params) => { + for raw_curve in ¶ms.iter().chunks(4) { + let curve: Vec<&f32> = raw_curve.collect(); + let (raw_ctrl, raw_to) = point_from_param2(&curve); + + let to = point_for_position(pos, raw_to, from); + let ctrl = point_for_position(pos, raw_ctrl, from); + + let line = lyon::geom::QuadraticBezierSegment { + from: from.into(), + ctrl: ctrl.into(), + to: to.into(), + }; + + let length = line.approximate_length(0.1); + + segment_state.add_segment(line, length); + + from = to; + } + } + svg::node::element::path::Command::SmoothQuadraticCurve(_, _) => todo!(), + svg::node::element::path::Command::EllipticalArc(_, _) => todo!(), + _ => todo!(), + }; + } + + // println!("processed {:?} pts", segment_state.vertices.len()); + + segment_state + .vertices + .into_iter() + .map(|x| x.as_vec2()) + .collect_vec() +} + +#[derive(Debug, Copy, Clone)] +pub struct Pt { + pt: Point2D, +} +impl Pt { + pub fn new(x: f32, y: f32) -> Pt { + Pt { + pt: Point2D::::new(x, y), + } + } + + fn x(&self) -> f32 { + self.pt.x + } + + fn y(&self) -> f32 { + self.pt.y + } + + pub fn as_vec2(&self) -> Vec2 { + Vec2::new(self.y(), self.x()) + } +} + +impl ops::Add for Pt { + type Output = Pt; + + fn add(self, rhs: Pt) -> Pt { + Pt::new(self.x() + rhs.x(), self.y() + rhs.y()) + } +} + +impl From for Point2D { + fn from(val: Pt) -> Self { + val.pt + } +} + +fn point_for_position(pos: &svg::node::element::path::Position, pt: Pt, from: Pt) -> Pt { + match pos { + svg::node::element::path::Position::Absolute => pt, + svg::node::element::path::Position::Relative => pt + from, + } +} + +pub struct SegmentState { + vertices: Vec, + line_space: f32, + dist_towards_next: f32, +} +impl Default for SegmentState { + fn default() -> Self { + Self::new() + } +} + +impl SegmentState { + pub fn new() -> SegmentState { + SegmentState { + vertices: Vec::::new(), + line_space: 5.0, + dist_towards_next: 0.0, + } + } + + pub fn new_with_line_space(line_space: f32) -> SegmentState { + SegmentState { + vertices: Vec::::new(), + line_space, + dist_towards_next: 0.0, + } + } + + fn update(&mut self, length: f32, new_vertices: Vec) { + self.dist_towards_next = (length + self.dist_towards_next) % self.line_space; + self.vertices.extend(new_vertices); + } + pub fn vertices(&self) -> Vec { + self.vertices.iter().map(|x| vec2(x.x(), x.y())).collect() + } + + pub fn add_segment(&mut self, segment: impl lyon::geom::Segment, length: f32) { + let mut vertices: Vec = Vec::::new(); + + let pt_count = ((length) / self.line_space) as u32; + + // println!("pt count {:?}", pt_count); + // println!("{:?}", self.dist_towards_next); + + // if it's an even number, we'll need one more. just include it, then + // trim it when t turns out > 1 + for pt_i in 0..=pt_count { + let t_n = (self.line_space * pt_i as f32) + self.dist_towards_next; + let t = t_n / length; + // println!("{:?} {:?}", t_n, t); + + if t <= 1.0 { + let x = segment.x(t); + let y = segment.y(t); + // println!("({:?}, {:?})", x, y); + vertices.push(Pt::new(x, y)); + } + } + + self.update(length, vertices) + } +} + +pub fn load_all_data(path: T, line_space: f32) -> HashMap>> +where + T: AsRef, +{ + let map = load_all_data_into_map(path); + + let r: HashMap>> = map + .iter() + .map(|(k, v)| { + // println!("processing {:?}", k); + ( + k.to_string(), + v.iter().map(|vv| parse_data(vv, line_space)).collect_vec(), + ) + }) + .collect(); + r +} + +pub fn load_all_data_into_map(path: T) -> HashMap> +where + T: AsRef, +{ + let mut content = String::new(); + + let mut maps: HashMap> = HashMap::new(); + + let mut recent_id: String = "".to_string(); // i hate this + + for event in svg::open(path, &mut content).unwrap() { + if let svg::parser::Event::Tag(_, _, attributes) = event { + if let Some(id) = attributes.get("id") { + recent_id = id.to_string(); + } + + if let Some(path_data) = attributes.get("d") { + let data = svg::node::element::path::Data::parse(path_data).unwrap(); + maps.entry(recent_id.to_owned()).or_default().push(data); + } + }; + } + + maps +} + +// SvgPathDef is a simplified svg thingy.. this just converts back to the full +// svg and then parses like usual +pub fn parse_svg_path_as_vec2(data: &SvgPathDef, line_space: f32) -> Vec { + let mut cmds = svg::node::element::path::Data::new(); + let (start_x, start_y) = data.svg_move_to(); + cmds = cmds.move_to(vec![start_x, start_y]); + + for cmd in data.cmds() { + match cmd { + crate::svg::SvgCmd::Line(svg_to) => { + let (x, y) = svg_to.params(); + cmds = cmds.line_to(vec![x, y]); + } + crate::svg::SvgCmd::CubicBezier(svg_cubic_bezier) => { + let (a, b, c, d, e, f) = svg_cubic_bezier.params(); + cmds = cmds.cubic_curve_to(vec![a, b, c, d, e, f]); + } + crate::svg::SvgCmd::ArcTo(svg_arc) => { + let (a, b, c, d, e, f, g) = svg_arc.params(); + cmds = cmds.elliptical_arc_to(vec![a, b, c, d, e, f, g]); + } + } + } + + parse_svg_data_as_vec2(&cmds, line_space) +} + +// todo, can i combine this with the output? +pub struct LayersFromSvg { + pub layers: HashMap>, +} +impl LayersFromSvg { + pub fn load(path: T) -> LayersFromSvg + where + T: AsRef, + { + let vecs = load_all_data(path, 5.0); + + let mut layers = HashMap::new(); + for (layer_name, vec) in &vecs { + let polylines = vec.iter().map(|x| Polyline::new(x.clone())).collect(); + layers.insert(layer_name.clone(), polylines); + } + + LayersFromSvg { layers } + } +} + +pub fn tesselate_delauney( + v: Vec, +) -> (Vec, Vec, Triangulation) { + let points: Vec<_> = v + .iter() + .map(|vertex| { + let loc = vertex.to_vec2(); + delaunator::Point { + x: loc.x as f64, + y: loc.y as f64, + } + }) + .collect(); + let triangulation = delaunator::triangulate(&points); + + // chatgpt + fn point_in_poly(x: f64, y: f64, poly: &[(f64, f64)]) -> bool { + let mut inside = false; + let n = poly.len(); + for i in 0..n { + let (xi, yi) = poly[i]; + let (xj, yj) = poly[(i + 1) % n]; + let intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if intersect { + inside = !inside; + } + } + inside + } + + let mut filtered_indices = Vec::new(); + for tri in triangulation.triangles.chunks(3) { + let a = &points[tri[0]]; + let b = &points[tri[1]]; + let c = &points[tri[2]]; + let cx = (a.x + b.x + c.x) / 3.0; + let cy = (a.y + b.y + c.y) / 3.0; + if point_in_poly( + cx, + cy, + &v.iter() + .map(|p| { + let loc = p.to_vec2(); + (loc.x as f64, loc.y as f64) + }) + .collect::>(), + ) { + filtered_indices.extend_from_slice(&[tri[0] as u32, tri[1] as u32, tri[2] as u32]); + } + } + + let vertices = v.clone(); + (filtered_indices, vertices, triangulation) +} + +pub fn tesselate_delauney_no_filter( + v: Vec, +) -> (Vec, Vec, Triangulation) { + let points: Vec<_> = v + .iter() + .map(|vertex| { + let loc = vertex.to_vec2(); + delaunator::Point { + x: loc.x as f64, + y: loc.y as f64, + } + }) + .collect(); + let triangulation = delaunator::triangulate(&points); + + let vertices = v.clone(); + let indices = triangulation + .triangles + .iter() + .map(|x| *x as u32) + .collect_vec(); + (indices, vertices, triangulation) +} diff --git a/murrelet_draw/src/transform2d.rs b/murrelet_draw/src/transform2d.rs index 15979e3..76159a2 100644 --- a/murrelet_draw/src/transform2d.rs +++ b/murrelet_draw/src/transform2d.rs @@ -4,13 +4,35 @@ use std::f32::consts::PI; use glam::*; use itertools::Itertools; use lerpable::Lerpable; -use murrelet_common::lerpify_vec2; use murrelet_common::{ a_pi, approx_eq_eps, mat4_from_mat3_transform, AnglePi, IsAngle, IsPolyline, Polyline, SimpleTransform2d, SimpleTransform2dStep, SpotOnCurve, TransformVec2, }; +use murrelet_common::{lerpify_vec2, ToSimpleTransform}; use murrelet_livecode_derive::Livecode; +pub trait ToMat4 { + fn change_to_mat4(&self) -> Mat4; +} + +impl ToMat4 for Transform2d { + fn change_to_mat4(&self) -> Mat4 { + self.to_mat4() + } +} + +impl ToMat4 for SimpleTransform2d { + fn change_to_mat4(&self) -> Mat4 { + self.to_mat4() + } +} + +impl ToMat4 for Mat4 { + fn change_to_mat4(&self) -> Mat4 { + *self + } +} + #[derive(Clone, Debug, Livecode, Lerpable, Default)] pub struct Transform2d(Vec); impl Transform2d { @@ -19,7 +41,11 @@ impl Transform2d { } pub fn prepend_action(&mut self, actions: &[Transform2dStep]) { - self.0 = vec![actions.to_vec(), self.0.clone()].concat(); + self.0 = [actions.to_vec(), self.0.clone()].concat(); + } + + pub fn prepend_one_action(&mut self, action: Transform2dStep) { + self.0 = [vec![action], self.0.clone()].concat(); } pub fn append_one_action(&mut self, action: Transform2dStep) { @@ -27,7 +53,7 @@ impl Transform2d { } pub fn append_action(&mut self, actions: &[Transform2dStep]) { - self.0 = vec![self.0.clone(), actions.to_vec()].concat(); + self.0 = [self.0.clone(), actions.to_vec()].concat(); } pub fn append_transform(&mut self, t: &Transform2d) { @@ -45,6 +71,13 @@ impl Transform2d { ))]) } + pub fn rotate_angle(angle_pi: A) -> Transform2d { + Transform2d::new(vec![Transform2dStep::Rotate(Rotate2::new( + Vec2::ZERO, + angle_pi, + ))]) + } + pub fn scale(scale_x: f32, scale_y: f32) -> Transform2d { Transform2d::new(vec![Transform2dStep::Scale(V2::new(vec2( scale_x, scale_y, @@ -106,11 +139,8 @@ impl Transform2d { mat4_from_mat3_transform(self.to_mat3()) } - pub fn rotate_around(angle_pi: f32, v: Vec2) -> Transform2d { - Transform2d::new(vec![Transform2dStep::Rotate(Rotate2::new( - v, - a_pi(angle_pi), - ))]) + pub fn rotate_around(angle_pi: A, v: Vec2) -> Transform2d { + Transform2d::new(vec![Transform2dStep::Rotate(Rotate2::new(v, angle_pi))]) } pub fn new_from_scale_rotate(s: f32, angle_pi: A) -> Transform2d { @@ -212,6 +242,50 @@ impl Transform2d { pub fn to_simple(&self) -> SimpleTransform2d { SimpleTransform2d::new(self.0.iter().map(|t| t.to_simple()).collect_vec()) } + + pub fn from_simple(simple: &SimpleTransform2d) -> Self { + Self::new( + simple + .steps() + .iter() + .map(|x| Transform2dStep::from_simple(x.clone())) + .collect_vec(), + ) + } + + pub fn steps(&self) -> &Vec { + &self.0 + } + + pub fn transform_vec_vec(&self, vs: &[Vec]) -> Vec> { + let mut vv = vec![]; + for line in vs { + let mut vvv = vec![]; + for v in line { + vvv.push(self.transform_vec2(*v)); + } + vv.push(vvv); + } + vv + } + + pub fn with_one_action(&self, action: Transform2dStep) -> Transform2d { + let mut c = self.clone(); + c.append_one_action(action); + c + } + + pub fn with_transform(&self, loc: Transform2d) -> Transform2d { + let mut c = self.clone(); + c.append_transform(&loc); + c + } +} + +impl ToSimpleTransform for Transform2d { + fn to_simple_transform(&self) -> SimpleTransform2d { + self.to_simple() + } } impl Default for ControlTransform2d { @@ -302,6 +376,10 @@ impl Transform2dStep { Self::Scale(V2::new(vec2(scale_x, scale_y))) } + pub fn scale_p(scale_x: f32) -> Self { + Self::Scale(V2::new(vec2(scale_x, scale_x))) + } + fn transform(&self) -> Mat3 { match self { Transform2dStep::Translate(t) => Mat3::from_translation(t.v), diff --git a/murrelet_gen/Cargo.toml b/murrelet_gen/Cargo.toml new file mode 100644 index 0000000..ebcdaeb --- /dev/null +++ b/murrelet_gen/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "murrelet_gen" +version = "0.1.2" +edition = "2021" + +[features] +default = [] + +[dependencies] +murrelet_gen_derive = { workspace = true } + +thiserror = "2.0.11" +serde = { version = "1.0.104", features = ["derive"] } +serde_json = "1.0.48" +rand = "0.8" +glam = { version = "0.28.0", features = ["serde"] } +murrelet_common = { workspace = true } + + +[[example]] +name = "tests" +path = "examples/tests.rs" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true } diff --git a/murrelet_gen/examples/tests.rs b/murrelet_gen/examples/tests.rs new file mode 100644 index 0000000..f6b43aa --- /dev/null +++ b/murrelet_gen/examples/tests.rs @@ -0,0 +1,187 @@ +use glam::Vec2; +use murrelet_common::MurreletColor; +use murrelet_gen::{CanSampleFromDist, MurreletGen}; + +// #[derive(Debug, MurreletGen)] +#[derive(Debug, MurreletGen)] +pub struct BasicTypes { + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 1.0)))] + a_number: f32, + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 1.0)))] + another_number: f32, + #[murrelet_gen(method(f32_fixed(val = 0.23)))] + fixed_number: f32, + #[murrelet_gen(method(f32_uniform(start = 1.0, end = 100.0)))] + another_number_wider_range: f32, + #[murrelet_gen(method(f32_uniform(start = -10.0, end = 0.0 )))] + a_neg_number: f32, + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 30.0)))] + a_usize: usize, + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 30.0)))] + c_number: u64, + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 30.0)))] + d_number: i32, + #[murrelet_gen(method(f32_uniform_pos_neg(start = 10.0, end = 30.0)))] + uniform_pos_neg: f32, + // #[murrelet_gen(method(f32_normal(mu = 10.0, sigma = 30.0)))] + // normal: f32, + #[murrelet_gen(method(bool_binomial(pct = 0.3)))] + bool: bool, + #[murrelet_gen(method(vec2_uniform_grid(x = 0.0, y = 0.0, width = 100.0, height = 100.0)))] + xy: Vec2, + #[murrelet_gen(method(vec2_circle(x = 0.0, y = 0.0, radius = 100.0)))] + other_xy: Vec2, + #[murrelet_gen(method(vec2_uniform_grid( + x = 500.0, + y = 500.0, + width = 200.0, + height = 200.0 + )))] + another_xy: Vec2, + #[murrelet_gen(method(color_normal))] + normal_color: MurreletColor, // 3 rn + #[murrelet_gen(method(color_transparency))] + transparent_color: MurreletColor, // 4 rn + #[murrelet_gen(method(vec_length(min = 4, max = 10)))] + #[murrelet_gen(method_inner(f32_uniform(start = 0.0, end = 1.0)))] + something: Vec, + #[murrelet_gen(method(string_choice(choices(a = 1.0, b = 1.0, c = 1.0))))] + s: String, +} + +#[derive(Debug, MurreletGen)] +pub struct Tiny { + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 1.0)))] + a_number: f32, + #[murrelet_gen(method(f32_uniform(start = 0.0, end = 1.0)))] + another_number: f32, +} + +#[derive(Debug, MurreletGen)] +pub struct OverridesAndRecursive { + #[murrelet_gen(method(recurse))] + basic: Tiny, + #[murrelet_gen(method(vec_length(min = 1, max = 4)))] + #[murrelet_gen(method_inner(recurse))] + something: Vec, +} + +#[derive(Debug, MurreletGen)] +enum EnumTest { + #[murrelet_gen(weight = 1.0)] + A, + #[murrelet_gen(weight = 1.0)] + B(#[murrelet_gen(method(recurse))] Tiny), + #[murrelet_gen(weight = 1.0)] + C(#[murrelet_gen(method(recurse))] Tiny), +} + +// #[derive(MurreletGen)] +// struct SimpleNewtype(#[murrelet_gen(method(f32_uniform(start = 0.0, end = 1.0)))] f32); + +fn main() { + println!("BasicTypes::rn_count() {:?}", BasicTypes::rn_count()); + + println!("BasicTypes::rn_names() {:?}", BasicTypes::rn_names()); + + let b = BasicTypes::gen_from_seed(1); + + println!("b {:?}", b); + println!("BasicTypes::to_dist() {:?}", b.to_dist()); + + println!("BasicTypes::to_dist_mask() {:?}", b.to_dist_mask()); + + // // let c = Vec2::ONE; + + assert_eq!(BasicTypes::rn_count(), b.to_dist().len()); + assert_eq!(BasicTypes::rn_count(), b.to_dist_mask().len()); + + println!("round trip {:?}", BasicTypes::sample_dist(&b.to_dist(), 0)); + + for i in b.to_dist() { + assert!(i >= 0.0); + assert!(i <= 1.0); + } + + let c = OverridesAndRecursive::gen_from_seed(13); + + println!("c {:?}", c); + + println!( + "OverridesAndRecursive::rn_names() {:?}", + OverridesAndRecursive::rn_names() + ); + + println!("OverridesAndRecursive::to_dist() {:?}", c.to_dist()); + println!( + "OverridesAndRecursive::to_dist_mask() {:?}", + c.to_dist_mask() + ); + + // println!("EnumTest::rn_names() {:?}", EnumTest::rn_names()); + + assert_eq!(BasicTypes::rn_count(), BasicTypes::rn_names().len()); + assert_eq!(Tiny::rn_count(), Tiny::rn_names().len()); + assert_eq!( + OverridesAndRecursive::rn_count(), + OverridesAndRecursive::rn_names().len() + ); + + let d = EnumTest::gen_from_seed(13); + + assert_eq!(EnumTest::rn_count(), EnumTest::rn_names().len()); + + println!("d {:?}", d); + + println!("EnumTest::rn_names() {:?}", EnumTest::rn_names()); + + println!("EnumTest::to_dist() {:?}", d.to_dist()); + println!("EnumTest::to_dist_mask() {:?}", d.to_dist_mask()); + + // // println!( + // // "OverridesAndRecursive::rn_count() {:?}", + // // OverridesAndRecursive::rn_count() + // // ); + + // assert_eq!(BasicTypes::rn_count(), 37); + + // println!( + // "OverridesAndRecursive::gen_from_seed(42) {:?}", + // OverridesAndRecursive::gen_from_seed(42) + // ); + + // assert_eq!(OverridesAndRecursive::rn_count(), 11); + // assert_eq!(EnumTest::rn_count(), 7); + + // for seed in 32..43 { + // let test_val = BasicTypes::gen_from_seed(seed); + + // // there's a small chance they will be equal, but we know for this seed they aren't + // assert!(test_val.a_number != test_val.another_number); + // assert!(test_val.another_number_wider_range > 1.0); + // assert_eq!(test_val.fixed_number, 0.23); + + // assert!(test_val.something.len() > 3); + // assert!(test_val.something.len() <= 10); + + // let test_val2 = OverridesAndRecursive::gen_from_seed(seed); + // assert!(test_val2.something.len() >= 1); + // assert!(test_val2.something.len() <= 4); + + // // println!("test_val {:?}", test_val); + + // let test_val = BasicTypes::gen_from_seed(seed); + // let test_val2 = OverridesAndRecursive::gen_from_seed(seed); + // let test_val3 = EnumTest::gen_from_seed(seed); + + // println!("test_val {:?}", test_val); + // // println!("test_val2 {:?}", test_val2); + // // println!("test_val3 {:?}", test_val3); + + // for i in b.to_dist() { + // assert!(i >= 0.0); + // assert!(i < 1.0); + // } + + // } +} diff --git a/murrelet_gen/src/lib.rs b/murrelet_gen/src/lib.rs new file mode 100644 index 0000000..b360f87 --- /dev/null +++ b/murrelet_gen/src/lib.rs @@ -0,0 +1,38 @@ +pub use murrelet_gen_derive::MurreletGen; + +use rand::rngs::StdRng; +use rand::Rng; +use rand::SeedableRng; + +pub trait CanSampleFromDist: Sized { + // returns the right number of rn needed to generate this. + fn rn_count() -> usize; + fn rn_names() -> Vec; + + // given rn of length ^, it'll generate! + fn sample_dist(rn: &[f32], start_idx: usize) -> Self; + + fn from_dist(rn: &[f32]) -> Self { + Self::sample_dist(rn, 0) + } + + // usually you'll call this one + fn gen_from_seed(seed: u64) -> Self { + let mut rng = StdRng::seed_from_u64(seed); + + let rns: Vec = (0..Self::rn_count()).map(|_| rng.gen()).collect(); + + Self::sample_dist(&rns, 0) + } + + // creates an arbitrary floats that should turn back into the same values + fn to_dist(&self) -> Vec; + fn to_dist_mask(&self) -> Vec; +} + +pub fn prefix_field_names(prefix: String, names: Vec) -> Vec { + names + .into_iter() + .map(|s| format!("{}.{}", prefix, s)) + .collect() +} diff --git a/murrelet_gen_derive/Cargo.toml b/murrelet_gen_derive/Cargo.toml new file mode 100644 index 0000000..baa98e8 --- /dev/null +++ b/murrelet_gen_derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "murrelet_gen_derive" +version = "0.1.2" +edition = "2021" + +[lib] +proc-macro = true + +[features] +debug_logging = ["log"] + +[dependencies] +syn = "2.0.15" +quote = "1.0.18" +proc-macro2 = "1.0.37" +darling = "0.20.3" + +log = { version = "0.4.25", optional = true } + diff --git a/murrelet_gen_derive/src/derive_gen.rs b/murrelet_gen_derive/src/derive_gen.rs new file mode 100644 index 0000000..7f3b228 --- /dev/null +++ b/murrelet_gen_derive/src/derive_gen.rs @@ -0,0 +1,503 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{gen_methods::GenMethod, parser::*}; + +pub(crate) struct FieldTokensGen { + pub(crate) for_rn_count: TokenStream2, + pub(crate) for_rn_names: TokenStream2, + pub(crate) for_make_gen: TokenStream2, + pub(crate) for_to_dist: TokenStream2, + pub(crate) for_to_dist_mask: TokenStream2, +} +impl GenFinal for FieldTokensGen { + // Something(f32) + fn make_newtype_struct_final( + idents: ParsedFieldIdent, + variants: Vec, + ) -> TokenStream2 { + let name = idents.name; + + let for_rn_count = variants.iter().map(|x| x.for_rn_count.clone()); + let for_rn_names = variants.iter().map(|x| x.for_rn_names.clone()); + let for_make_gen = variants.iter().map(|x| x.for_make_gen.clone()); + let for_to_dist = variants.iter().map(|x| x.for_to_dist.clone()); + let for_to_dist_mask = variants.iter().map(|x| x.for_to_dist_mask.clone()); + + quote! { + impl murrelet_gen::CanSampleFromDist for #name { + fn rn_count() -> usize { + #(#for_rn_count+)* + } + + fn rn_names() -> Vec { + #(#for_rn_names+)* + } + + fn sample_dist(rn: &[f32], start_idx: usize) -> Self { + Self(#(#for_make_gen,)*) + } + + fn to_dist(&self) -> Vec { + let val = self; + vec![#(#for_to_dist,)*].concat() + } + + fn to_dist_mask(&self) -> Vec { + let val = self; + vec![#(#for_to_dist_mask,)*].concat() + } + } + } + } + + fn make_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2 { + let name = idents.name; + + let for_rn_count = variants.iter().map(|x| x.for_rn_count.clone()); + let for_rn_names = variants.iter().map(|x| x.for_rn_names.clone()); + let for_make_gen = variants.iter().map(|x| x.for_make_gen.clone()); + let for_to_dist = variants.iter().map(|x| x.for_to_dist.clone()); + let for_to_dist_mask = variants.iter().map(|x| x.for_to_dist_mask.clone()); + + quote! { + impl murrelet_gen::CanSampleFromDist for #name { + fn rn_count() -> usize { + vec![ + #(#for_rn_count,)* + ].iter().sum() + } + + fn rn_names() -> Vec { + vec![ + #(#for_rn_names,)* + ].concat() + } + + fn sample_dist(rn: &[f32], start_idx: usize) -> Self { + let mut rn_start_idx = start_idx; + + Self { + #(#for_make_gen,)* + } + } + + fn to_dist(&self) -> Vec { + let val = self; + vec![#(#for_to_dist,)*].concat() + } + + fn to_dist_mask(&self) -> Vec { + let val = self; + vec![#(#for_to_dist_mask,)*].concat() + } + } + } + } + + fn make_enum_final( + idents: ParsedFieldIdent, + variants: Vec, + variant_receiver: &[LivecodeVariantReceiver], + ) -> TokenStream2 { + let name = idents.name; + let for_rn_count = variants.iter().map(|x| x.for_rn_count.clone()); + let for_rn_names_all = variants.iter().map(|x| x.for_rn_names.clone()); + let for_to_dist = variants.iter().map(|x| x.for_to_dist.clone()); + let for_to_dist_mask = variants.iter().map(|x| x.for_to_dist_mask.clone()); + + let mut weights = vec![]; + let mut for_rn_names: Vec = vec![]; + + for ((variant, receiver), names) in variants + .iter() + .zip(variant_receiver.iter()) + .zip(for_rn_names_all) + { + let create_variant = &variant.for_make_gen; + let receiver_name = receiver.ident.to_string(); + + for_rn_names.push(quote! { + murrelet_gen::prefix_field_names(#receiver_name.to_string(), vec![vec!["[weight]".to_string()], #names].concat()) + }); + + let weight = receiver.weight; + // hrm, might want to use closures if it's expensive + // also the create_variant will modify rn_start_idx for us. + weights.push(quote! { + let weight = #weight * rn[rn_start_idx]; + rn_start_idx += 1; + weighted_rns.push((weight, #create_variant)); + }); + } + + // one hot encoding, i might be off-by-one here for how many vars.. + let number_of_choices = variants.len(); + + quote! { + impl murrelet_gen::CanSampleFromDist for #name { + // we add up each one individually, and then add one more for the type + fn rn_count() -> usize { + vec![ + #(#for_rn_count,)* + ].iter().sum::() + #number_of_choices + } + + fn rn_names() -> Vec { + vec![ + #(#for_rn_names,)* + ].concat() + } + + fn sample_dist(rn: &[f32], start_idx: usize) -> Self { + let mut rn_start_idx = start_idx; + + let mut weighted_rns: Vec<(f32, _)> = vec![]; + #(#weights;)* + + // first choose which enum + let (_, comp) = weighted_rns.into_iter() + .max_by(|a, b| a.0.partial_cmp(&b.0).unwrap()) + .expect("no enum values?"); + + comp + } + + fn to_dist(&self) -> Vec { + let val = self; + let mut result: Vec = vec![]; + #(#for_to_dist;)* + result + } + + fn to_dist_mask(&self) -> Vec { + let val = self; + let mut result: Vec = vec![]; + #(#for_to_dist_mask;)* + result + } + } + } + } + + fn from_newtype_struct(idents: StructIdents, _parent_ident: syn::Ident) -> FieldTokensGen { + let ty = convert_vec_type(&idents.data.ty); + + let for_rn_count = quote! { #ty::rn_count() }; + + let for_rn_names = quote! { #ty::rn_names() }; + + let for_make_gen = quote! { + self.0.sample_dist(rn, rn_start_idx) + }; + + let for_to_dist = quote! { + self.0.to_dist() + }; + + let for_to_dist_mask = quote! { + self.0.to_dist_mask() + }; + + FieldTokensGen { + for_rn_count, + for_rn_names, + for_make_gen, + for_to_dist, + for_to_dist_mask, + } + } + + // e.g. TileAxisLocs::V(TileAxisVs) + fn from_unnamed_enum(idents: EnumIdents) -> FieldTokensGen { + let variant_ident = idents.data.ident; + let ty = convert_vec_type(&idents.data.fields.fields.first().unwrap().ty); + let name = idents.enum_name; + + let for_rn_count = quote! { #ty::rn_count() }; + + let for_rn_names = quote! { #ty::rn_names() }; + + let for_to_dist = quote! { + if let #name::#variant_ident(x) = &self { + result.push(1.0); + result.extend(x.to_dist().into_iter()) + } else { + result.push(0.0); + result.extend((0..#ty::rn_count()).map(|x| 0.5)); + } + }; + + let for_to_dist_mask = quote! { + if let #name::#variant_ident(x) = &self { + result.push(true); + result.extend((0..#ty::rn_count()).map(|x| true)); + } else { + result.push(true); + result.extend((0..#ty::rn_count()).map(|x| false)); + } + }; + + // hm, i'm not sure that the method in the enum is actually used + let for_make_gen = quote! { + { + let result = #name::#variant_ident(#ty::sample_dist(rn, rn_start_idx)); + rn_start_idx += #for_rn_count; + result + + } + }; + + FieldTokensGen { + for_rn_count, + for_rn_names, + for_make_gen, + for_to_dist, + for_to_dist_mask, + } + } + + // e.g. TileAxis::Diag + fn from_unit_enum(idents: EnumIdents) -> FieldTokensGen { + let variant_ident = idents.data.ident; + let name = idents.enum_name; + + // just the one-hot encoding + let for_rn_count = quote! { 0 }; + + let for_rn_names = quote! { vec![] }; + + let for_make_gen = quote! { #name::#variant_ident }; + + let for_to_dist = quote! { + if let #name::#variant_ident = &self { + result.push(1.0); + } else { + result.push(0.0); + } + }; + + let for_to_dist_mask = quote! { + if let #name::#variant_ident = &self { + result.push(true); + } else { + result.push(true); + } + }; + + FieldTokensGen { + for_rn_count, + for_rn_names, + for_make_gen, + for_to_dist, + for_to_dist_mask, + } + } + + // // skip + // fn from_noop_struct(idents: StructIdents) -> FieldTokensGen { + // let field_name = idents.data.ident.unwrap().to_string(); + // let ty = idents.data.ty; + + // let for_rn_count = quote! { 0 }; + // let for_rn_names = quote! { vec![] }; + // let for_make_gen = quote! { #field_name: #ty::default() }; + // let for_to_dist = quote! { vec![] }; + // let for_to_dist_mask = quote! { vec![] }; + + // FieldTokensGen { + // for_rn_count, + // for_make_gen, + // for_rn_names, + // for_to_dist, + // for_to_dist_mask, + // } + // } + + // f32, Vec2, etc + fn from_type_struct(idents: StructIdents, method: &GenMethod) -> FieldTokensGen { + let field_name = idents.data.ident.unwrap(); + let field_name_str = field_name.to_string(); + let ty = idents.data.ty; + + let (for_rn_count, for_rn_names, for_make_gen, for_to_dist) = + method.to_methods(ty, quote! {self.#field_name}, true); + + // for the mask. if we're here, we probably want to count it! + let rn_count = for_rn_count.clone(); + + FieldTokensGen { + for_make_gen: quote! { #field_name: #for_make_gen }, + for_rn_names: quote! { murrelet_gen::prefix_field_names(#field_name_str.to_string(), #for_rn_names)}, + for_rn_count, + for_to_dist, + for_to_dist_mask: quote! { (0..{#rn_count}).into_iter().map(|x| true).collect::>() }, + } + } + + fn from_type_recurse(idents: StructIdents, outer: &GenMethod, inner: &GenMethod) -> Self { + let field_name = idents.data.ident.unwrap(); + let ty = idents.data.ty; + + let (for_rn_count, for_rn_names, for_make_gen, for_to_dist, for_to_dist_mask) = match outer + { + GenMethod::VecLength { min, max } => { + let inside_type = nested_ident(&ty); + + let i = inside_type[1].clone(); + let inside_type_val: syn::Type = syn::parse_quote! { #i }; + + let ( + for_rn_count_per_item, + for_rn_names_per_item, + for_make_gen_per_item, + for_make_to_dist_per_item, + ) = inner.to_methods(inside_type_val, quote! {val.clone()}, false); + + let for_rn_count: TokenStream2 = quote! { + #for_rn_count_per_item * #max + 1 + }; + + let for_rn_names_all = (0..*max).map(|x| { + let i_name = x.to_string(); + quote! { murrelet_gen::prefix_field_names(#i_name.to_string(), #for_rn_names_per_item) } + }); + + let field_name_str = field_name.to_string(); + + let for_rn_names = quote! { + murrelet_gen::prefix_field_names( + #field_name_str.to_string(), + vec![ + vec!["[len]".to_string()], + #(#for_rn_names_all,)* + ].concat() + ) + }; + + // in this case, we _don't_ want one-hot, because it actually does make + // sense to interpolate between say, 3 and 6. + // i want to add extra indicators for everything between min and max + // but i'm not sure how to do that! because i'm just generating, + // not making the input data for something else... + let for_make_gen = quote! {{ + let range = (#max - #min + 1) as f32; + let how_many = (rn[rn_start_idx] * range) as usize + #min; + rn_start_idx += 1; + let mut v = vec![]; + // need to go through the fill list so we increment + // through the rns right + for i in 0..#max { + if i < how_many { + v.push(#for_make_gen_per_item); + } else { + // just run it + #for_make_gen_per_item; + } + } + v + }}; + + let for_to_dist = quote! {{ + let mut result = vec![]; + let x = self.#field_name.len() as f32; + let v = (x - #min as f32) / ((#max - #min) as f32); + result.push(v); + for val in self.#field_name.iter() { + // always extend it + let vv = #for_make_to_dist_per_item; + result.extend(vv.into_iter()); + } + + for _ in self.#field_name.len()..#max { + let vv: Vec = (0..#for_rn_count_per_item).into_iter().map(|_| 0.5f32).collect(); + result.extend(vv.into_iter()); + } + result + }}; + + let for_to_dist_mask = quote! {{ + let mut result: Vec = vec![]; + result.push(true); + for val in self.#field_name.iter() { + // always extend it + let vv: Vec = (0..#for_rn_count_per_item).into_iter().map(|_| true).collect(); + result.extend(vv.into_iter()); + } + + for _ in self.#field_name.len()..#max { + // these ones are all false! + let vv: Vec = (0..#for_rn_count_per_item).into_iter().map(|_| false).collect(); + result.extend(vv.into_iter()); + } + result + }}; + + ( + for_rn_count, + for_rn_names, + for_make_gen, + for_to_dist, + for_to_dist_mask, + ) + } + _ => unreachable!("not expecting an inner without a recursive outer"), + }; + + Self { + for_rn_count, + for_rn_names, + for_make_gen: quote! { #field_name: #for_make_gen }, + for_to_dist, + for_to_dist_mask, + } + } +} + +fn recursive_ident_from_path(t: &syn::Type, acc: &mut Vec) { + match t { + syn::Type::Path(syn::TypePath { path, .. }) => { + let s = path.segments.last().unwrap(); + let main_type = s.ident.clone(); + + acc.push(main_type); + + if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + args, + .. + }) = s.arguments.clone() + { + if let syn::GenericArgument::Type(other_ty) = args.first().unwrap() { + recursive_ident_from_path(other_ty, acc); + } else { + panic!("recursive ident not implemented yet {:?}", args); + } + } + } + x => panic!("no name for type {:?}", x), + } +} + +fn nested_ident(t: &syn::Type) -> Vec { + let mut acc = vec![]; + recursive_ident_from_path(t, &mut acc); + acc +} + +// we need to use turbofish to call an associated function +fn convert_vec_type(ty: &syn::Type) -> TokenStream2 { + if let syn::Type::Path(type_path) = ty { + if let Some(last_segment) = type_path.path.segments.last() { + if last_segment.ident == "Vec" { + if let syn::PathArguments::AngleBracketed(angle_bracketed) = &last_segment.arguments + { + if let Some(inner_arg) = angle_bracketed.args.first() { + return quote! { Vec:: < #inner_arg > }; + } + } + } + } + } + + quote! { #ty } +} diff --git a/murrelet_gen_derive/src/gen_methods.rs b/murrelet_gen_derive/src/gen_methods.rs new file mode 100644 index 0000000..160f905 --- /dev/null +++ b/murrelet_gen_derive/src/gen_methods.rs @@ -0,0 +1,370 @@ +use darling::FromMeta; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashMap; + +#[derive(Debug, Clone, FromMeta)] +pub enum GenMethod { + Default, + Recurse, + BoolBinomial { + pct: f32, // pct that is true + }, + F32Uniform { + start: syn::Expr, + end: syn::Expr, + }, + F32UniformPosNeg { + // includes between start and end as negative and positive + start: f32, + end: f32, + }, + F32Normal { + mu: syn::Expr, + sigma: syn::Expr, + }, + F32Fixed { + val: syn::Expr, + }, + Vec2UniformGridStart { + // chooses random points + start_x: syn::Expr, + start_y: syn::Expr, + width: f32, + height: f32, + }, + Vec2UniformGrid { + // chooses random points + x: syn::Expr, + y: syn::Expr, + width: f32, + height: f32, + }, + Vec2Circle { + // selects random points within a circle + x: syn::Expr, + y: syn::Expr, + radius: f32, + }, + VecLength { + // determines how long the vector will be + min: usize, + max: usize, + }, + VecLengthFixed { + val: syn::Expr, + }, + ColorNormal, // samples h, s, and v values + ColorTransparency, // same as ColorNormal, plus alpha + StringChoice { + choices: HashMap, // string mapped to its weight + }, + F32Lazy, // eval data is passed in through another method. + // BoolLazy, + // Vec2Lazy, +} +impl GenMethod { + pub(crate) fn to_methods( + &self, + ty: syn::Type, + name: TokenStream2, + convert: bool, + ) -> (TokenStream2, TokenStream2, TokenStream2, TokenStream2) { + let maybe_as = if convert { + quote! { as #ty } + } else { + quote! {} + }; + + match self { + GenMethod::Recurse => { + let for_rn_count = quote! { #ty::rn_count() }; + let for_rn_names = quote! { #ty::rn_names() }; + let for_make_gen = quote! {{ + let r = #ty::sample_dist(rn, rn_start_idx); + rn_start_idx += #for_rn_count; + r + }}; + let for_to_dist = quote! { #name.to_dist() }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::BoolBinomial { pct } => { + let for_rn_count = quote! { 1 }; + let for_rn_names = quote! { vec!["pct".to_string()] }; + let for_make_gen = quote! { { + let result = rn[rn_start_idx] > #pct; + rn_start_idx += #for_rn_count; + result + } }; + let for_to_dist = quote! { + if #name { + vec![1.0] + } else { + vec![0.0] + } + }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::F32Uniform { start, end } => { + let for_rn_count = quote! { 1 }; + let for_rn_names = quote! { vec!["uniform".to_string()] }; + let for_make_gen = quote! { { + let result = rn[rn_start_idx] * (#end - #start) + #start; + rn_start_idx += #for_rn_count; + result #maybe_as + } }; + let for_to_dist = quote! { vec![(#name as f32 - #start) / (#end - #start)] }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::F32UniformPosNeg { start, end } => { + let for_rn_count = quote! { 2 }; + let for_rn_names = quote! { vec!["uniform".to_string(), "sign".to_string()] }; + let for_make_gen = quote! { { + let sgn = if(rn[rn_start_idx] > 0.5) { 1.0 } else { -1.0 }; + let result = rn[rn_start_idx + 1] * (#end - #start) + #start; + rn_start_idx += #for_rn_count; + (sgn * result) #maybe_as + } }; + + let for_to_dist = quote! { vec![ + if #name > 0.0 { 1.0 } else { 0.0 }, + (#name.abs() - #start) / (#end - #start) + ] }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::F32Fixed { val } => ( + quote! { 0 }, + quote! {vec![]}, + quote! { #val #maybe_as }, + quote! {vec![]}, + ), + GenMethod::F32Normal { mu, sigma } => { + let for_rn_count = quote! { 2 }; + let for_rn_names = + quote! { vec![ "BoxMuller1".to_string(), "BoxMuller2".to_string()] }; + let for_make_gen = quote! { { + // avoid nans, make sure u1 is positive and non-zero + let u1 = rn[rn_start_idx].clamp(std::f32::MIN_POSITIVE, 1.0); + let u2 = rn[rn_start_idx + 1].clamp(0.0, 1.0); + rn_start_idx += 2; + + let r = (-2.0 * u1.ln()).sqrt(); + let theta = 2.0 * std::f32::consts::PI * u2; + + #mu + #sigma * r * theta.cos() #maybe_as + } }; + let for_to_dist = quote! { todo!() }; + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::Vec2UniformGridStart { + start_x, + start_y, + width, + height, + } => { + let for_rn_count = quote! { 2 }; + let for_rn_names = quote! { vec![ "x".to_string(), "y".to_string()] }; + let for_make_gen = quote! {{ + let width = rn[rn_start_idx] * #width; + let height = rn[rn_start_idx + 1] * #height; + + rn_start_idx += #for_rn_count; + + glam::vec2(#start_x, #start_y) + glam::vec2(width, height) + }}; + + let for_to_dist = quote! { { + // let c = (#name + 0.5 * glam::vec2(#width, #height)); + // vec![c.x / #width, c.y / #height] + + let c = #name + - glam::vec2(#start_x, #start_y) + + 0.5 * glam::vec2(#width, #height); + vec![c.x / #width, c.y / #height] + }}; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::Vec2UniformGrid { + x, + y, + width, + height, + } => { + let for_rn_count = quote! { 2 }; + let for_rn_names = quote! { vec![ "x".to_string(), "y".to_string()] }; + let for_make_gen = quote! {{ + let width = rn[rn_start_idx] * #width; + let height = rn[rn_start_idx + 1] * #height; + + rn_start_idx += #for_rn_count; + + glam::vec2(#x, #y) - 0.5 * glam::vec2(#width, #height) + glam::vec2(width, height) + }}; + + let for_to_dist = quote! { { + // let c = (#name + 0.5 * glam::vec2(#width, #height)); + // vec![c.x / #width, c.y / #height] + + let c = #name + - glam::vec2(#x, #y) + + 0.5 * glam::vec2(#width, #height); + vec![c.x / #width, c.y / #height] + }}; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::Vec2Circle { x, y, radius } => { + let for_rn_count = quote! { 2 }; + let for_rn_names = quote! { vec![ "theta".to_string(), "rad".to_string()] }; + + let for_make_gen = quote! {{ + let angle = rn[rn_start_idx] * 2.0 * std::f32::consts::PI; + let dist = rn[rn_start_idx + 1]; // sqrt it to even out the sampling + rn_start_idx += #for_rn_count; + glam::vec2(#x, #y) + glam::vec2(angle.cos(), angle.sin()) * #radius * dist.sqrt() + }}; + + let for_to_dist = quote! { { + let c = #name - glam::vec2(#x, #y); + let dist = (c.length() / #radius).powi(2); + let mut angle = c.to_angle(); + if angle <= 0.0 { + angle += 2.0 * std::f32::consts::PI + } + angle /= (2.0 * std::f32::consts::PI); + vec![angle, dist] + }}; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::ColorNormal => { + let for_rn_count = quote! { 3 }; + + let for_rn_names = + quote! { vec![ "hue".to_string(), "sat".to_string(), "val".to_string()] }; + + let for_make_gen = quote! {{ + let h = rn[rn_start_idx]; + let s = rn[rn_start_idx + 1]; + let v = rn[rn_start_idx + 2]; + rn_start_idx += #for_rn_count; + murrelet_common::MurreletColor::hsva(h, s, v, 1.0) + }}; + + let for_to_dist = quote! {{ + let [h, s, v, _] = #name.into_hsva_components(); + vec![ + h % 1.0, + s % 1.0, + v % 1.0, + ] + }}; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::ColorTransparency => { + let for_rn_count = quote! { 4 }; + + let for_rn_names = quote! { vec![ "hue".to_string(), "sat".to_string(), "val".to_string(), "alpha".to_string()] }; + + let for_make_gen = quote! { + { + let h = rn[rn_start_idx]; + let s = rn[rn_start_idx + 1]; + let v = rn[rn_start_idx + 2]; + let a = rn[rn_start_idx + 3]; + rn_start_idx += #for_rn_count; + murrelet_common::MurreletColor::hsva(h, s, v, a) + } + }; + + let for_to_dist = quote! { { + let [h, s, v, a] = #name.into_hsva_components(); + vec![ + h % 1.0, + s % 1.0, + v % 1.0, + a % 1.0, + ] + } }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::VecLength { .. } => { + // this is handled in the vec parser + unreachable!("this location of veclength isn't supported yet!") + } + GenMethod::VecLengthFixed { val: _ } => unreachable!(), + + GenMethod::StringChoice { choices } => { + let one_hot = choices.len(); + let for_rn_count = quote! { #one_hot }; + + // let for_rn_names = quote! { vec![ "hue", "sat", "val", "alpha"] }; + let rn_names = choices.iter().map(|(key, _)| quote! { #key.to_string() }); + let for_rn_names = quote! { vec![#(#rn_names,)*] }; + + let weighted_rns = choices + .iter() + .enumerate() + .map(|(i, (key, weight))| { + quote! { (#key.clone(), #weight * rn[rn_start_idx + #i]) } + }) + .collect::>(); + + let for_make_gen = quote! { { + let result = vec![#(#weighted_rns,)*].into_iter().max_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).expect("empty string choices??"); + rn_start_idx += #for_rn_count; + result.0.to_string() + } }; + + let for_to_dist_choices = choices.keys().map(|key| { + quote! { if #key.clone() == #name { result.push(1.0) } else { result.push(0.0) } } + }) + .collect::>(); + + let for_to_dist = quote! { { + let mut result = vec![]; + #(#for_to_dist_choices;)* + result + } }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::Default => { + let for_rn_count = quote! { 0 }; + + let for_rn_names = quote! { vec![] }; + + let for_make_gen = quote! { { + Default::default() + } }; + + let for_to_dist = quote! { vec![] }; + + (for_rn_count, for_rn_names, for_make_gen, for_to_dist) + } + GenMethod::F32Lazy => todo!(), + } + } + + // pub(crate) fn from_methods(&self) -> TokenStream2 { + // match self { + // GenMethod::Default => todo!(), + // GenMethod::Recurse => todo!(), + // GenMethod::BoolBinomial { pct } => { + // // put at extremes + + // }, + // GenMethod::F32Uniform { start, end } => { + + // }, + + // } + // } +} diff --git a/murrelet_gen_derive/src/lib.rs b/murrelet_gen_derive/src/lib.rs new file mode 100644 index 0000000..15b4d48 --- /dev/null +++ b/murrelet_gen_derive/src/lib.rs @@ -0,0 +1,19 @@ +extern crate proc_macro; + +use darling::FromDeriveInput; +use derive_gen::FieldTokensGen; +use parser::{GenFinal, LivecodeReceiver}; +use proc_macro::TokenStream; + +mod derive_gen; +mod gen_methods; +mod parser; + +use gen_methods::GenMethod; + +#[proc_macro_derive(MurreletGen, attributes(murrelet_gen))] +pub fn murrelet_livecode_derive_murrelet_gen(input: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(input as syn::DeriveInput); + let ast_receiver = LivecodeReceiver::from_derive_input(&ast).unwrap(); + FieldTokensGen::from_ast(ast_receiver).into() +} diff --git a/murrelet_gen_derive/src/parser.rs b/murrelet_gen_derive/src/parser.rs new file mode 100644 index 0000000..904e305 --- /dev/null +++ b/murrelet_gen_derive/src/parser.rs @@ -0,0 +1,206 @@ +use darling::{ast, FromDeriveInput, FromField, FromVariant}; +use proc_macro2::TokenStream as TokenStream2; + +use crate::GenMethod; + +#[derive(Debug)] +pub(crate) struct ParsedFieldIdent { + pub(crate) name: syn::Ident, +} + +// trait and helpers needed to parse a variety of objects +pub(crate) trait GenFinal +where + Self: Sized, +{ + fn from_newtype_struct(_idents: StructIdents, parent_ident: syn::Ident) -> Self; + fn from_unnamed_enum(idents: EnumIdents) -> Self; + fn from_unit_enum(idents: EnumIdents) -> Self; + fn from_type_struct(idents: StructIdents, how_to_control_this_type: &GenMethod) -> Self; + fn from_type_recurse( + idents: StructIdents, + how_to_control_outer_type: &GenMethod, + how_to_control_inner_type: &GenMethod, + ) -> Self; + + fn from_ast(ast_receiver: LivecodeReceiver) -> TokenStream2 { + match ast_receiver.data { + ast::Data::Enum(_) => Self::make_enum(&ast_receiver), + ast::Data::Struct(ast::Fields { + style: ast::Style::Tuple, + .. + }) => Self::make_newtype(&ast_receiver), + ast::Data::Struct(_) => Self::make_struct(&ast_receiver), + } + } + // fn from_override_struct( + // idents: StructIdents, + // func: &str, + // rn_names: Vec, + // rn_count: usize, + // ) -> Self; + + fn make_enum_final( + idents: ParsedFieldIdent, + variants: Vec, + variants_receiver: &[LivecodeVariantReceiver], + ) -> TokenStream2; + fn make_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2; + fn make_newtype_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2; + + fn make_struct(s: &LivecodeReceiver) -> TokenStream2 { + let name = s.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_struct {}", Self::classname(), name.to_string()); + + // shouldn't be calling this with something that's not a struct.. + let fields = s.data.clone().take_struct().unwrap(); + + let livecodable_fields = fields + .iter() + .map(|field| { + let idents = StructIdents { + data: field.clone(), + }; + + match field.how_to_control_this() { + // HowToControlThis::Override(func, names, count) => { + // Self::from_override_struct(idents, &func, names, count) + // } + HowToControlThis::Type(how_to_control_this_type) => { + Self::from_type_struct(idents, &how_to_control_this_type) + } + HowToControlThis::Recurse(outer, inner) => { + Self::from_type_recurse(idents, &outer, &inner) + } + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_struct_final(idents, livecodable_fields) + } + + fn make_enum(e: &LivecodeReceiver) -> TokenStream2 { + let name = e.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_enum {}", Self::classname(), name.to_string()); + + let variants_receiver = e.data.clone().take_enum().unwrap(); + + // just go through and find ones that wrap around a type, and make sure those types are + let variants = variants_receiver + .iter() + .map(|variant| { + let ident = EnumIdents { + enum_name: name.clone(), + data: variant.clone(), + }; + + match variant.fields.style { + ast::Style::Tuple => Self::from_unnamed_enum(ident), + ast::Style::Struct => panic!("enum named fields not supported yet"), + ast::Style::Unit => Self::from_unit_enum(ident), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_enum_final(idents, variants, &variants_receiver) + } + + fn make_newtype(s: &LivecodeReceiver) -> TokenStream2 { + let name = s.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_newtype {}", Self::classname(), name.to_string()); + + // shouldn't be calling this with something that's not a struct.. + let fields = s.data.clone().take_struct().unwrap(); + + let livecodable_fields = fields + .iter() + .map(|field| { + let idents = StructIdents { + data: field.clone(), + }; + + match field.how_to_control_this() { + HowToControlThis::Type(_how_to_control_this_type) => { + // Self::from_type_struct(idents, &how_to_control_this_type) + Self::from_newtype_struct(idents, name.clone()) + } + HowToControlThis::Recurse(_outer, _inner) => { + // Self::from_type_recurse(idents, &outer, &inner) + Self::from_newtype_struct(idents, name.clone()) + } // HowToControlThis::Override(func, labels, count) => { + // Self::from_override_struct(idents, &func, labels, count) + // } + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_newtype_struct_final(idents, livecodable_fields) + } +} + +#[derive(Debug, FromField, Clone)] +#[darling(attributes(murrelet_gen))] +pub(crate) struct LivecodeFieldReceiver { + pub(crate) ident: Option, + pub(crate) ty: syn::Type, + pub(crate) method: GenMethod, + #[darling(default)] + pub(crate) method_inner: Option, +} +impl LivecodeFieldReceiver { + fn how_to_control_this(&self) -> HowToControlThis { + if let Some(r) = &self.method_inner { + HowToControlThis::Recurse(self.method.clone(), r.clone()) + } else if matches!(self.method, GenMethod::VecLength { .. }) { + panic!("vec missing inner") + } else { + HowToControlThis::Type(self.method.clone()) + } + } +} + +// for enums +#[derive(Debug, FromVariant, Clone)] +#[darling(attributes(murrelet_gen))] +pub(crate) struct LivecodeVariantReceiver { + pub(crate) ident: syn::Ident, + pub(crate) fields: ast::Fields, + pub(crate) weight: f32, // either each field needs something +} + +#[derive(Debug, Clone, FromDeriveInput)] +#[darling(attributes(murrelet_gen), supports(any))] +pub(crate) struct LivecodeReceiver { + ident: syn::Ident, + data: ast::Data, +} +impl LivecodeReceiver {} + +// represents an enum +pub(crate) struct EnumIdents { + pub(crate) enum_name: syn::Ident, + pub(crate) data: LivecodeVariantReceiver, +} + +#[derive(Clone, Debug)] +pub struct StructIdents { + pub(crate) data: LivecodeFieldReceiver, +} + +#[derive(Clone, Debug)] +pub(crate) enum HowToControlThis { + Type(GenMethod), + Recurse(GenMethod, GenMethod), +} diff --git a/murrelet_gpu/Cargo.toml b/murrelet_gpu/Cargo.toml index 9c2e6f7..3ea97c8 100644 --- a/murrelet_gpu/Cargo.toml +++ b/murrelet_gpu/Cargo.toml @@ -18,20 +18,22 @@ schemars = [ "murrelet_perform/schemars", ] + [dependencies] wgpu_for_latest = { package = "wgpu", version = "0.20.1", optional = true } wgpu_for_nannou = { package = "wgpu", version = "0.17.1", optional = true } +naga = { version = "0.20.0", features = ["wgsl-in"] } -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } -murrelet_livecode = { version = "0.1.2", path = "../murrelet_livecode/", default-features = false } -murrelet_draw = { version = "0.1.2", path = "../murrelet_draw/", default-features = false } -murrelet_livecode_macros = { version = "0.1.2", path = "../murrelet_livecode_macros/" } -murrelet_livecode_derive = { version = "0.1.2", path = "../murrelet_livecode_macros/murrelet_livecode_derive/" } -murrelet_perform = { version = "0.1.2", path = "../murrelet_perform/", default-features = false } +murrelet_common = { workspace = true } +murrelet_livecode = { workspace = true, default-features = false } +murrelet_draw = { workspace = true, default-features = false } +murrelet_livecode_macros = { workspace = true } +murrelet_livecode_derive = { workspace = true } +murrelet_perform = { workspace = true, default-features = false } -lerpable = "0.0.2" +lerpable = { version = "0.0.3" } -glam = "0.28.0" +glam = { version = "0.28.0", features = ["serde"] } palette = "0.7.6" serde = { version = "1.0.104", features = ["derive"] } @@ -48,3 +50,7 @@ image = "0.25.2" bytemuck = { version = "1.16.1", features = ["derive"] } schemars = { version = "0.8.21", optional = true } +murrelet_gui = { workspace = true, features = ["glam"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true } diff --git a/murrelet_gpu/src/compute.rs b/murrelet_gpu/src/compute.rs new file mode 100644 index 0000000..198abb3 --- /dev/null +++ b/murrelet_gpu/src/compute.rs @@ -0,0 +1,507 @@ +use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc}; + +use bytemuck::Pod; +use glam::Vec2; +use itertools::Itertools; +#[cfg(feature = "nannou")] +use wgpu_for_nannou as wgpu; + +#[cfg(not(feature = "nannou"))] +use wgpu_for_latest as wgpu; + +use wgpu::util::DeviceExt; + +use crate::{ + device_state::{DeviceState, DeviceStateForRender}, + graphics_ref::{shader_from_path, GraphicsRefCustom, TextureAndDesc, DEFAULT_TEXTURE_FORMAT}, + uniforms::BasicUniform, + window::GraphicsWindowConf, +}; + +pub struct ComputeBindings { + input: wgpu::Buffer, + cell_offsets: wgpu::Buffer, + cell_indices: wgpu::Buffer, + uniforms: wgpu::Buffer, +} +impl ComputeBindings { + fn update_csr_and_data(&mut self, c: &GraphicsWindowConf, csr: CSR, data: Vec) { + let device = c.device(); + + let mut offsets = csr.offsets; + if offsets.is_empty() { + offsets = vec![0; 2] + }; + let mut indices = csr.indices; + if indices.is_empty() { + indices = vec![0; 2] + }; + + self.input = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&data), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + self.cell_indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&indices), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + self.cell_offsets = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&offsets), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + } +} + +pub struct CSR { + offsets: Vec, // size is N + 1, contains the start/end of each group! fence post + indices: Vec, +} +impl CSR { + fn empty() -> CSR { + Self { + offsets: vec![0, 0], // need to put something... + indices: vec![0], + } + } +} + +pub struct CSRData { + cells: Vec>, +} +impl CSRData { + pub fn new(cells: Vec>) -> Self { + Self { cells } + } + + fn for_buffers(self) -> CSR { + let mut offsets = Vec::with_capacity(self.cells.len() + 1); + let mut indices = Vec::new(); + offsets.push(0); + for v in self.cells { + indices.extend_from_slice(&v); + offsets.push(indices.len() as u32); + } + CSR { offsets, indices } + } + + fn from_data(nx: u32, ny: u32, data: &[T]) -> Self { + let cell_count = (nx * ny) as usize; + let mut orig_cells = vec![vec![]; cell_count]; + + // helpers + // let clamp01 = |v: f32| v.max(0.0).min(1.0); + let cell_id = |x_i: u32, y_i: u32| -> usize { (y_i * nx + x_i) as usize }; + let ix_clamp = |x: i32| -> u32 { x.max(0).min(nx as i32 - 1) as u32 }; + let iy_clamp = |y: i32| -> u32 { y.max(0).min(ny as i32 - 1) as u32 }; + + for (idx, d) in data.iter().enumerate() { + let bounds = d.to_aabb(); + + // Convert to cell-range (inclusive) + let ix0 = ix_clamp((bounds.min.x * nx as f32).floor() as i32); + let ix1 = ix_clamp((bounds.max.x * nx as f32).floor() as i32); + let iy0 = iy_clamp((bounds.min.y * ny as f32).floor() as i32); + let iy1 = iy_clamp((bounds.max.y * ny as f32).floor() as i32); + + for iy in iy0..=iy1 { + for ix in ix0..=ix1 { + orig_cells[cell_id(ix, iy)].push(idx as u32); + } + } + } + + // now go through the cells and append the neighboring cells! + let mut cells = vec![vec![]; cell_count]; + + for x_i in 0..nx { + for y_i in 0..ny { + let mut cell_val = HashSet::new(); + + for offset_x in -1..=1 { + for offset_y in -1..=1 { + let xii = ix_clamp(x_i as i32 + offset_x); + let yii = iy_clamp(y_i as i32 + offset_y); + + for c in &orig_cells[cell_id(xii, yii)] { + cell_val.insert(*c); + } + } + } + cells[cell_id(x_i, y_i)] = cell_val.into_iter().collect_vec(); + } + } + + CSRData { cells } + } +} + +pub struct AABB { + pub min: Vec2, + pub max: Vec2, +} + +pub trait ToAABB { + fn to_aabb(&self) -> AABB; +} + +// like Graphics, what's needed to create a compute pipeline +pub struct ComputeGraphicsToTexture { + name: String, + pub uniforms: BasicUniform, + pub buffers: ComputeBindings, + #[allow(dead_code)] + texture: TextureAndDesc, + bind_group_layout: wgpu::BindGroupLayout, + pipeline: wgpu::ComputePipeline, + dims: [u32; 2], +} +impl ComputeGraphicsToTexture { + fn sync_data( + &mut self, + c: &GraphicsWindowConf, + nx: u32, + ny: u32, + data: &[T], + ) { + // the data should already be scaled 0.0 to 1.0 + + // make sure we use the same vars on both sides + self.update_uniforms_other(c, [nx as f32, ny as f32, 0.0, 0.0], [0.0; 4]); + + // build csr + let csr = CSRData::from_data(nx, ny, data).for_buffers(); + + let data = data.to_vec(); + + self.buffers.update_csr_and_data(c, csr, data); + } + + pub fn init<'a, T: Pod + ToAABB + Clone>( + name: String, + c: &GraphicsWindowConf<'a>, + compute_shader: &str, + data: Vec, + ) -> ComputeGraphicsToTextureRef { + ComputeGraphicsToTextureRef::new(Rc::new(RefCell::new(Self::new( + name, + c, + compute_shader, + BasicUniform::from_dims(c.dims), + CSR::empty(), + data, + )))) + } + + pub fn new<'a, T: Pod + ToAABB + Clone>( + name: String, + c: &GraphicsWindowConf<'a>, + shader_data: &str, + initial_uniform: BasicUniform, + csr: CSR, // helps limit what data you need to check per cell + data: Vec, + ) -> Self { + let device: &wgpu::Device = c.device.device(); + + let input_data_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&data), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let cell_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&csr.indices), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let cell_offsets_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&csr.offsets), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let uniforms_buffer = initial_uniform.to_buffer(device); + + // now create the layout! + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("compute bind group layout"), + entries: &[ + // 0: input + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // 21: CSR offsets + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // 3: CSR indices + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // 4: uniforms + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // and the output texture + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: wgpu::TextureFormat::Rgba16Float, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + // // add back for cpu output! + // wgpu::BindGroupLayoutEntry { + // binding: 1, + // visibility: wgpu::ShaderStages::COMPUTE, + // ty: wgpu::BindingType::Buffer { + // ty: wgpu::BufferBindingType::Storage { read_only: false }, + // has_dynamic_offset: false, + // min_binding_size: None, + // }, + // count: None, + // }, + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + // let out_texture = output_texture + // .graphics() + // .graphics + // .borrow() + // .texture_and_desc + // .texture + // .create_view(&Default::default()); + + let desc = wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: c.dims[0], + height: c.dims[1], + depth_or_array_layers: 1, + }, + format: DEFAULT_TEXTURE_FORMAT, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + label: None, + view_formats: &[], + }; + + let texture = TextureAndDesc { + texture: Arc::new(device.create_texture(&desc)), + desc, + }; + + let buffers = ComputeBindings { + uniforms: uniforms_buffer, + input: input_data_buffer, + cell_offsets: cell_offsets_buffer, + cell_indices: cell_indices_buffer, + }; + + // now bind the buffers! + // let bind_group = Self::bind_group( + // device, + // &bind_group_layout, + // &buffers, + // &texture.default_view(), + // ); + + let shader = shader_from_path(device, shader_data); + + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + module: &shader, + entry_point: "main", + #[cfg(not(feature = "nannou"))] + compilation_options: wgpu::PipelineCompilationOptions::default(), + }); + + let dims = c.dims; + + Self { + name, + // data, + pipeline, + buffers, + uniforms: initial_uniform, + bind_group_layout, + texture, + dims, + // csr: CSR::empty(), + } + } + + // from https://github.com/gfx-rs/wgpu/blob/1cbebdcffe64c05e8ed14db7331333425f6feb65/examples/standalone/01_hello_compute/src/main.rs + + pub fn render(&self, d: &DeviceState, output_texture_view: &wgpu::TextureView) { + // first compute + let device = d.device(); + let queue = d.queue(); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("compute encoder"), + }); + + { + // update our output texture + let bind_group = Self::bind_group( + device, + &self.bind_group_layout, + &self.buffers, + output_texture_view, + ); + + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("compute pass"), + #[cfg(not(feature = "nannou"))] + timestamp_writes: None, + }); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &bind_group, &[]); + + // dispatch over image size (stored in uniforms) + let [w, h] = self.dims; // ideally could get this from the texture... + const WGX: u32 = 8; + const WGY: u32 = 8; + let gx = w.div_ceil(WGX); + let gy = h.div_ceil(WGY); + + pass.dispatch_workgroups(gx, gy, 1); + } // drop(pass) + + queue.submit(Some(encoder.finish())); + + // now texture has teh output + } + + pub fn update_uniforms_other( + &mut self, + c: &GraphicsWindowConf, + more_info: [f32; 4], + more_info_other: [f32; 4], + ) { + let queue = &c.device.queue(); + self.uniforms.more_info = more_info; + self.uniforms.more_info_other = more_info_other; + + // println!("{:?}", self.uniform.more_info); + queue.write_buffer(&self.buffers.uniforms, 0, self.uniforms.as_bytes()); + } + + fn bind_group( + device: &wgpu::Device, + bind_group_layout: &wgpu::BindGroupLayout, + buffers: &ComputeBindings, + texture: &wgpu::TextureView, + ) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buffers.input.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: buffers.cell_offsets.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: buffers.cell_indices.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: buffers.uniforms.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: wgpu::BindingResource::TextureView(texture), + }, + ], + }) + } +} + +#[derive(Clone)] +pub struct ComputeGraphicsToTextureRef { + pub graphics: Rc>, +} +impl ComputeGraphicsToTextureRef { + fn new(graphics: Rc>) -> Self { + Self { graphics } + } + + pub fn name(&self) -> String { + self.graphics.borrow().name.clone() + } + + pub fn render( + &self, + device_state_for_render: &DeviceStateForRender, + other: &GraphicsRefCustom, + ) { + let view = &other.graphics.borrow_mut().input_texture_view; + self.graphics + .borrow() + .render(device_state_for_render.device_state(), view) + } + + pub fn sync_data( + &self, + c: &GraphicsWindowConf, + nx: u32, + ny: u32, + segments: &[T], + ) { + if !segments.is_empty() { + self.graphics.borrow_mut().sync_data(c, nx, ny, segments) + } else { + println!("segments is empty, not doing anything"); + } + } +} diff --git a/murrelet_gpu/src/device_state.rs b/murrelet_gpu/src/device_state.rs index 55bb14c..2de4bc1 100644 --- a/murrelet_gpu/src/device_state.rs +++ b/murrelet_gpu/src/device_state.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; #[allow(dead_code)] use std::path::PathBuf; @@ -53,11 +54,11 @@ impl<'a> DeviceState<'a> { } pub fn device(&self) -> &wgpu::Device { - &self.device + self.device } pub fn queue(&self) -> &wgpu::Queue { - &self.queue + self.queue } } @@ -105,7 +106,11 @@ impl OwnedDeviceState { // borrowing from bevy pub fn align_byte_size(value: u32) -> u32 { - value + (wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - (value % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)) + if !value.is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT) { + value + (wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - (value % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT)) + } else { + value + } } pub fn check_img_size(path: &PathBuf) -> Result<(Vec, u32, u32), Box> { @@ -123,7 +128,11 @@ fn write_png_to_texture( texture: &wgpu::Texture, ) -> Result<(), Box> { // Load the image + println!("loading file {:?}", path); let img = image::open(path)?; + + println!("img.color() {:?}", img.color()); + let img_rgba = img.to_rgba8(); let (img_width, img_height) = img.dimensions(); @@ -135,10 +144,11 @@ fn write_png_to_texture( println!("img_width {:?}", img_width); println!("img_height {:?}", img_height); + println!("padded_row {:?}", padded_row); println!("buffer_rows {:?}", buffer_rows); // just get the name to name the texture - let p = path.file_name().map(|x| x.to_str()).flatten().unwrap_or(""); + let p = path.file_name().and_then(|x| x.to_str()).unwrap_or(""); // bah, uh, okay copy this to a buffer of the right length let mut padded_img = vec![0; (padded_row * buffer_rows).try_into().unwrap()]; @@ -149,6 +159,13 @@ fn write_png_to_texture( padded_img[start..end].copy_from_slice(data); } + let mut hist = HashMap::new(); + for value in &padded_img { + *hist.entry(value).or_insert(0) += 1; + } + + println!("hist {:?}", hist); + // buffer for loading the png let buffer = device_state .device() @@ -256,57 +273,6 @@ impl GraphicsAssets { } } -#[derive(Clone, Debug)] -pub struct GraphicsWindowConf<'a> { - pub device: &'a DeviceState<'a>, - pub dims: [u32; 2], - pub assets_path: GraphicsAssets, -} -impl<'a> GraphicsWindowConf<'a> { - pub fn new( - device: &'a DeviceState, - dims: [u32; 2], - assets_path: GraphicsAssets, - ) -> GraphicsWindowConf<'a> { - GraphicsWindowConf { - device, - dims, - assets_path, - } - } - - pub fn multi(&self, multiplier: f32) -> GraphicsWindowConf { - let [x, y] = self.dims; - GraphicsWindowConf { - device: self.device, - dims: [ - (x as f32 * multiplier) as u32, - (y as f32 * multiplier) as u32, - ], - assets_path: GraphicsAssets::Nothing, - } - } - - pub fn dims(&self) -> [u32; 2] { - self.dims - } - - pub fn device(&self) -> &wgpu::Device { - &self.device.device() - } - - pub fn with_dims(&self, dims: [u32; 2]) -> Self { - Self { - dims, - ..self.clone() - } - } - - pub fn queue(&self) -> &wgpu::Queue { - self.device.queue() - } -} - // new type just to pull in things available at render time pub struct DeviceStateForRender<'a> { device_state: DeviceState<'a>, @@ -320,7 +286,7 @@ impl<'a> DeviceStateForRender<'a> { } } - pub fn device_state(&self) -> &DeviceState { + pub fn device_state(&self) -> &DeviceState<'_> { &self.device_state } diff --git a/murrelet_gpu/src/editable_shaders.rs b/murrelet_gpu/src/editable_shaders.rs new file mode 100644 index 0000000..8893b26 --- /dev/null +++ b/murrelet_gpu/src/editable_shaders.rs @@ -0,0 +1,154 @@ +use std::collections::HashMap; + +use crate::{ + build_shader_custom_vertex, gpu_macros::ShaderStr, graphics_ref::GraphicsVertex, + window::GraphicsWindowConf, +}; +use lerpable::Lerpable; +use murrelet_common::triangulate::DefaultVertex; +use murrelet_livecode_derive::Livecode; +use naga; +#[cfg(feature = "nannou")] +use wgpu_for_nannou as wgpu; + +#[cfg(not(feature = "nannou"))] +use wgpu_for_latest as wgpu; + +use crate::{ + build_shader, build_shader_2tex, + graphics_ref::{GraphicsCreator, GraphicsRefCustom}, +}; + +#[derive(Debug, Clone, Livecode, Lerpable)] +pub struct ShaderStrings { + #[livecode(kind = "none")] + #[lerpable(method = "skip")] + shaders: HashMap, +} +impl ShaderStrings { + fn shader_str(shader: &str) -> String { + Self::shader_custom_prefix(shader, VertexKind::fragment_prefix()) + } + + fn shader(shader: &str) -> String { + build_shader! { + ( + raw shader; + ) + } + } + + fn shader_custom_prefix(shader: &str, prefix: &str) -> String { + build_shader_custom_vertex! { + ( + prefix prefix; + raw shader; + ) + } + } + + fn shader2tex(shader: &str) -> String { + build_shader_2tex! { + ( + raw shader; + ) + } + } + + pub fn get_shader_str(&self, _c: &GraphicsWindowConf, name: &str) -> Option { + self.shaders.get(name).map(|str| Self::shader(str)) + } + + pub fn get_shader_str_2tex(&self, _c: &GraphicsWindowConf, name: &str) -> Option { + self.shaders.get(name).map(|str| Self::shader2tex(str)) + } + + pub fn get_shader_str_custom_prefix( + &self, + _c: &GraphicsWindowConf, + name: &str, + prefix: &str, + ) -> Option { + self.shaders + .get(name) + .map(|str| Self::shader_custom_prefix(str, prefix)) + } + + pub fn get_graphics_ref( + &self, + c: &GraphicsWindowConf, + name: &str, + ) -> Option> { + self.shaders.get(name).map(|str| { + GraphicsCreator::::default() + .with_mag_filter(wgpu::FilterMode::Nearest) + .to_graphics_ref(c, name, &Self::shader(str)) + }) + } + + pub fn get_graphics_ref_2tex( + &self, + c: &GraphicsWindowConf, + name: &str, + ) -> Option> { + self.shaders.get(name).map(|str| { + GraphicsCreator::::default() + .with_mag_filter(wgpu::FilterMode::Nearest) + .with_second_texture() + .to_graphics_ref(c, name, &Self::shader2tex(str)) + }) + } + + pub fn has_changed(&self, other: &ControlShaderStrings) -> bool { + self.shaders != other.shaders + } + + pub fn naga_if_needed( + &self, + prev_shaders: &ControlShaderStrings, + ) -> bool { + if self.has_changed(prev_shaders) { + let mut all_success = true; + + for (name, shader_str) in self.shaders.iter() { + let t = ShaderStrings::shader_str::(shader_str); + if let Err(err) = naga::front::wgsl::parse_str(&t) { + println!( + "error with shader {:?}, {:?}, not updating until it works!", + name, err + ); + all_success = false; + } + } + + all_success + } else { + false + } + } +} + +impl ControlShaderStrings { + fn to_normal(&self) -> ShaderStrings { + ShaderStrings { + shaders: self.shaders.clone(), + } + } + + pub fn should_update( + &self, + prev: &ControlShaderStrings, + force_reload: bool, + ) -> Option { + let shaders = self.to_normal(); + + let shader_changed_and_compiles = shaders.naga_if_needed::(prev); + + if force_reload || shader_changed_and_compiles { + // just in case there's lerp, be sure to use the one we tested + Some(shaders) + } else { + None + } + } +} diff --git a/murrelet_gpu/src/gpu_livecode.rs b/murrelet_gpu/src/gpu_livecode.rs index 0077313..0760cb8 100644 --- a/murrelet_gpu/src/gpu_livecode.rs +++ b/murrelet_gpu/src/gpu_livecode.rs @@ -1,31 +1,61 @@ #![allow(dead_code)] use glam::*; use lerpable::Lerpable; -use murrelet_common::*; +use murrelet_common::{triangulate::DefaultVertex, *}; use murrelet_draw::newtypes::*; use murrelet_livecode_derive::Livecode; -use crate::{device_state::GraphicsWindowConf, graphics_ref::GraphicsRef}; +use crate::{ + graphics_ref::{GraphicsRefCustom, GraphicsRefWithControlFn, GraphicsVertex}, + window::GraphicsWindowConf, +}; pub trait ControlGraphics { - fn update_graphics(&self, c: &GraphicsWindowConf, g: &GraphicsRef) { - g.update_uniforms_other_tuple(c, self.more_info_other_tuple()); + fn more_info_other_tuple(&self) -> ([f32; 4], [f32; 4]); +} + +pub trait AnyControlRef { + fn update(&self, c: &GraphicsWindowConf); +} + +impl AnyControlRef for ControlGraphicsRef +where + VertexKind: GraphicsVertex + 'static, +{ + fn update(&self, c: &GraphicsWindowConf) { + // use the stored GraphicsRef inside ControlGraphicsRef + self.update_graphics(c); + } +} + +impl ControlProvider + for GraphicsRefWithControlFn +where + VertexKind: GraphicsVertex + 'static, +{ + fn make_controls(&self, conf: &GraphicsConf) -> Vec> { + self.control_graphics(conf) + .into_iter() + .map(|c| Box::new(c) as Box) + .collect() } +} - fn more_info_other_tuple(&self) -> ([f32; 4], [f32; 4]); +pub trait ControlProvider { + fn make_controls(&self, conf: &GraphicsConf) -> Vec>; } -pub struct ControlGraphicsRef { +pub struct ControlGraphicsRef { pub label: &'static str, pub control: Box, - graphics: GraphicsRef, + graphics: GraphicsRefCustom, } -impl ControlGraphicsRef { +impl ControlGraphicsRef { pub fn new( label: &'static str, control: Box, - graphics: Option, - ) -> Vec { + graphics: Option>, + ) -> Vec> { // using a vec here to make it easier to concat with other lists if let Some(gg) = graphics { vec![ControlGraphicsRef { @@ -39,8 +69,10 @@ impl ControlGraphicsRef { vec![] } } + pub fn update_graphics(&self, c: &GraphicsWindowConf) { - self.control.update_graphics(c, &self.graphics); + self.graphics + .update_uniforms_other_tuple(c, self.control.more_info_other_tuple()); } } @@ -82,7 +114,7 @@ impl GPUNoise { ) } - pub fn shader(c: &GraphicsWindowConf) -> GraphicsRef { + pub fn shader(c: &GraphicsWindowConf) -> GraphicsRefCustom { prebuilt_shaders::new_shader_noise(c) } } @@ -143,18 +175,28 @@ impl ControlGraphics for GPURGBAGradient { pub mod prebuilt_shaders { + use murrelet_common::triangulate::DefaultVertex; + use crate::{ - device_state::GraphicsWindowConf, gpu_macros::ShaderStr, - graphics_ref::{GraphicsCreator, GraphicsRef}, + graphics_ref::{GraphicsCreator, GraphicsRefCustom}, + window::GraphicsWindowConf, *, }; - pub fn new_shader_basic(c: &GraphicsWindowConf, name: &str, shader: &str) -> GraphicsRef { + pub fn new_shader_basic( + c: &GraphicsWindowConf, + name: &str, + shader: &str, + ) -> GraphicsRefCustom { GraphicsCreator::default().to_graphics_ref(c, name, shader) } - pub fn new_shader_2tex(c: &GraphicsWindowConf, name: &str, shader: &str) -> GraphicsRef { + pub fn new_shader_2tex( + c: &GraphicsWindowConf, + name: &str, + shader: &str, + ) -> GraphicsRefCustom { let name = format!("{} {:?}", name, c.dims); GraphicsCreator::default() .with_second_texture() @@ -173,7 +215,7 @@ pub mod prebuilt_shaders { /// - 1.y: max value for noise /// ## Returns /// - GraphicsRef - pub fn new_shader_noise(c: &GraphicsWindowConf) -> GraphicsRef { + pub fn new_shader_noise(c: &GraphicsWindowConf) -> GraphicsRefCustom { let shader: String = build_shader_2tex! { ( raw r###" diff --git a/murrelet_gpu/src/gpu_macros.rs b/murrelet_gpu/src/gpu_macros.rs index 72d3fa2..1b059d6 100644 --- a/murrelet_gpu/src/gpu_macros.rs +++ b/murrelet_gpu/src/gpu_macros.rs @@ -2,17 +2,19 @@ use std::{cell::RefCell, collections::HashMap, fs, path::PathBuf, rc::Rc}; use glam::Vec2; -use murrelet_common::MurreletTime; +use murrelet_common::{triangulate::DefaultVertex, MurreletTime}; use serde::Serialize; use crate::{ - device_state::{DeviceStateForRender, GraphicsAssets, GraphicsWindowConf}, - gpu_livecode::ControlGraphicsRef, + compute::ComputeGraphicsToTextureRef, + device_state::{DeviceStateForRender, GraphicsAssets}, + gpu_livecode::{AnyControlRef, ControlProvider}, graphics_ref::{ - BasicUniform, Graphics, GraphicsCreator, GraphicsRef, GraphicsRefWithControlFn, + AnyGraphicsRef, Graphics, GraphicsCreator, GraphicsRefCustom, GraphicsVertex, DEFAULT_LOADED_TEXTURE_FORMAT, }, shader_str::*, + window::GraphicsWindowConf, }; #[cfg(feature = "nannou")] @@ -42,6 +44,60 @@ const DEFAULT_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16F /// use "ray_march" /// } /// +/// + +#[macro_export] +macro_rules! build_shader_custom_vertex { + (@parse ()) => {{""}}; // done! + + // for raw things in the prefix + (@parse (raw $raw:expr;$($tail:tt)*)) => { + { + let rest = build_shader!(@parse ($($tail)*)); + format!("{}\n{}", $raw, rest) + } + }; + + (@parsecode ()) => {{""}}; // done! + + (@parsecode (raw $raw:expr;$($tail:tt)*)) => { + { + let rest = build_shader!(@parsecode ($($tail)*)); + format!("{}\n{}", $raw, rest) + } + }; + + // wrap the main code itself in () + (@parse ((prefix $prefix:expr; $($tail:tt)*))) => { + { + //let prefix = ShaderStr::Prefix.to_str(); + let rest = build_shader!(@parsecode ($($tail)*)); + let suffix = ShaderStr::Suffix.to_str(); + format!("{}\n{}\n{}", $prefix, rest, suffix) + } + }; // includes + + // arm for funky parsing + (@parse $($raw:tt)*) => { + { + println!("???"); + "???" + // unreachable!(); + } + }; + + // capture the initial one + ($($raw:tt)*) => { + { + format!( + "{}\n{}\n{}", + ShaderStr::Binding1Tex.to_str(), + ShaderStr::Includes.to_str(), + build_shader_custom_vertex!(@parse ($($raw)*)), + ) + } + }; +} #[macro_export] macro_rules! build_shader { @@ -110,19 +166,23 @@ pub enum ShaderStr { Binding1Tex, Binding2Tex, Binding3d, + Compute, Includes, Prefix, Suffix, + ComputeFormatStr, } impl ShaderStr { pub fn to_str(&self) -> &str { match self { ShaderStr::Binding1Tex => BINDING_1TEX, ShaderStr::Binding2Tex => BINDING_2TEX, + ShaderStr::Compute => COMPUTE_TEX, ShaderStr::Includes => INCLUDES, ShaderStr::Prefix => PREFIX, ShaderStr::Suffix => SUFFIX, ShaderStr::Binding3d => BINDING_3D, + ShaderStr::ComputeFormatStr => COMPUTE_FORMAT_STR, } } } @@ -156,6 +216,32 @@ macro_rules! build_shader_3d { }; } +// input_structure can be +// struct Input { +// a: vec2; +// b: vec2; +// } + +//COMPUTE_FORMAT_STR.replace("#PREFIX_CODEHERE", prefix).replace("#FORLOOP_CODEHERE#", forloop).replace("#SUFFIX_CODEHERE#", suffix); + +#[macro_export] +macro_rules! build_compute_shader { + // capture the initial one + ($input_structure:tt, $prefix:tt, $forloop:tt, $suffix:tt) => {{ + format!( + "{}\n{}\n{}\n{}", + $input_structure, + ShaderStr::Compute.to_str(), + ShaderStr::Includes.to_str(), + ShaderStr::ComputeFormatStr + .to_str() + .replace("#PREFIX_CODEHERE#", $prefix) + .replace("#FORLOOP_CODEHERE#", $forloop) + .replace("#SUFFIX_CODEHERE#", $suffix) + ) + }}; +} + #[derive(Serialize)] pub struct RenderDebugPrint { pub src: String, @@ -172,7 +258,8 @@ pub trait RenderTrait { fn render(&self, device_state_for_render: &DeviceStateForRender); fn debug_print(&self) -> Vec; - fn dest(&self) -> Option; + fn dest_view(&self) -> Option; + fn dest_view_other(&self) -> Option; // hmm, i don't know how to do this with the boxes fn is_choice(&self) -> bool { @@ -182,23 +269,34 @@ pub trait RenderTrait { fn adjust_choice(&mut self, _choice_val: usize) {} } -pub struct SimpleRender { - pub source: GraphicsRef, - pub dest: GraphicsRef, +pub struct SimpleRender { + pub source: GraphicsRefCustom, + pub dest: GraphicsRefCustom, } -impl SimpleRender { - pub fn new_box(source: GraphicsRef, dest: GraphicsRef) -> Box { +impl + SimpleRender +{ + pub fn new_box( + source: GraphicsRefCustom, + dest: GraphicsRefCustom, + ) -> Box> { Box::new(SimpleRender { source, dest }) } - fn dest(&self) -> Option { - Some(self.dest.clone()) + fn dest(&self) -> Option { + Some(self.dest.texture_view()) } } -impl RenderTrait for SimpleRender { +impl RenderTrait + for SimpleRender +where + VertexKindSource: GraphicsVertex, + VertexKindDest: GraphicsVertex, +{ fn render(&self, device: &DeviceStateForRender) { - self.source.render(device.device_state(), &self.dest); + self.source + .render(device.device_state(), &self.dest.texture_view()); } fn debug_print(&self) -> Vec { @@ -208,22 +306,32 @@ impl RenderTrait for SimpleRender { }] } - fn dest(&self) -> Option { - Some(self.dest.clone()) + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + self.dest.texture_view_other() + } + + fn is_choice(&self) -> bool { + false } + + fn adjust_choice(&mut self, _choice_val: usize) {} } -pub struct TwoSourcesRender { - pub source_main: GraphicsRef, - pub source_other: GraphicsRef, - pub dest: GraphicsRef, +pub struct TwoSourcesRender { + pub source_main: GraphicsRefCustom, + pub source_other: GraphicsRefCustom, + pub dest: GraphicsRefCustom, } -impl TwoSourcesRender { +impl TwoSourcesRender { pub fn new_box( - source_main: GraphicsRef, - source_other: GraphicsRef, - dest: GraphicsRef, - ) -> Box { + source_main: GraphicsRefCustom, + source_other: GraphicsRefCustom, + dest: GraphicsRefCustom, + ) -> Box> { Box::new(TwoSourcesRender { source_main, source_other, @@ -231,16 +339,21 @@ impl TwoSourcesRender { }) } - fn dest(&self) -> Option { - Some(self.dest.clone()) + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + self.dest.texture_view_other() } } -impl RenderTrait for TwoSourcesRender { +impl RenderTrait for TwoSourcesRender { fn render(&self, device: &DeviceStateForRender) { - self.source_main.render(device.device_state(), &self.dest); + self.source_main + .render(device.device_state(), &self.dest_view().unwrap()); self.source_other - .render_2tex(device.device_state(), &self.dest); + .render(device.device_state(), &self.dest_view_other().unwrap()); } fn debug_print(&self) -> Vec { @@ -256,23 +369,33 @@ impl RenderTrait for TwoSourcesRender { ] } - fn dest(&self) -> Option { - Some(self.dest.clone()) + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + self.dest.texture_view_other() } } // holds a gpu pipeline :O -pub struct PipelineRender { - pub source: GraphicsRef, +pub struct PipelineRender< + GraphicsConf, + VertexKindSource: GraphicsVertex, + VertexKindDest: GraphicsVertex, +> { + pub source: GraphicsRefCustom, pub pipeline: GPUPipelineRef, - pub dest: GraphicsRef, + pub dest: GraphicsRefCustom, } -impl PipelineRender { +impl + PipelineRender +{ pub fn new_box( - source: GraphicsRef, + source: GraphicsRefCustom, pipeline: GPUPipelineRef, - dest: GraphicsRef, + dest: GraphicsRefCustom, ) -> Box { Box::new(Self { source, @@ -281,7 +404,9 @@ impl PipelineRender { }) } } -impl RenderTrait for PipelineRender { +impl RenderTrait + for PipelineRender +{ fn render(&self, device_state_for_render: &DeviceStateForRender) { // write source to pipeline source self.source.render( @@ -295,19 +420,26 @@ impl RenderTrait for PipelineRender { self.pipeline.debug_print() } - fn dest(&self) -> Option { - Some(self.dest.clone()) + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + self.dest.texture_view_other() } } // given a list of inputs, choose which one to use -pub struct ChoiceRender { - pub sources: Vec, - pub dest: GraphicsRef, +pub struct ChoiceRender { + pub sources: Vec>, + pub dest: GraphicsRefCustom, choice: usize, } -impl ChoiceRender { - pub fn new_box(sources: Vec, dest: GraphicsRef) -> Box { +impl ChoiceRender { + pub fn new_box( + sources: Vec>, + dest: GraphicsRefCustom, + ) -> Box> { Box::new(ChoiceRender { sources, dest, @@ -316,11 +448,11 @@ impl ChoiceRender { } } -impl RenderTrait for ChoiceRender { +impl RenderTrait for ChoiceRender { fn render(&self, device: &DeviceStateForRender) { let source = &self.sources[self.choice % self.sources.len()]; let dest = &self.dest; - source.render(device.device_state(), dest); + source.render(device.device_state(), &dest.texture_view()); } fn debug_print(&self) -> Vec { @@ -330,10 +462,6 @@ impl RenderTrait for ChoiceRender { todo!() } - fn dest(&self) -> Option { - Some(self.dest.clone()) - } - fn is_choice(&self) -> bool { true } @@ -342,27 +470,43 @@ impl RenderTrait for ChoiceRender { fn adjust_choice(&mut self, choice_val: usize) { self.choice = choice_val % self.sources.len() } + + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + None + } } -pub struct PingPongRender { +pub struct PingPongRender { pub k: usize, - pub ping: GraphicsRef, // it'll end up here - pub pong: GraphicsRef, + pub ping: GraphicsRefCustom, // it'll end up here + pub pong: GraphicsRefCustom, } -impl PingPongRender { - pub fn new_box(k: usize, ping: GraphicsRef, pong: GraphicsRef) -> Box { +impl PingPongRender { + pub fn new_box( + k: usize, + ping: GraphicsRefCustom, + pong: GraphicsRefCustom, + ) -> Box> { Box::new(PingPongRender { k, ping, pong }) } } -impl RenderTrait for PingPongRender { +impl RenderTrait for PingPongRender { fn render(&self, device: &DeviceStateForRender) { - let ping = &self.ping; - let pong = &self.pong; + let ping_texture = &self.ping.texture_view(); + let pong_texture = &self.pong.texture_view(); for _ in 0..self.k { - ping.render(device.device_state(), &pong); - pong.render(device.device_state(), &ping); + self.ping + .graphics() + .render(device.device_state(), pong_texture); + self.pong + .graphics() + .render(device.device_state(), ping_texture); } } @@ -389,51 +533,66 @@ impl RenderTrait for PingPongRender { ] } - fn dest(&self) -> Option { - Some(self.pong.clone()) + fn dest_view(&self) -> Option { + Some(self.pong.texture_view()) } -} -pub struct TextureViewRender { - pub source: GraphicsRef, - pub dest: wgpu::TextureView, + fn dest_view_other(&self) -> Option { + None + } +} +pub struct ComputeTextureRender { + pub source: ComputeGraphicsToTextureRef, + pub dest: GraphicsRefCustom, } -impl TextureViewRender { - pub fn new_box(source: GraphicsRef, dest: wgpu::TextureView) -> Box { - Box::new(TextureViewRender { source, dest }) +impl ComputeTextureRender { + pub fn new_box( + source: ComputeGraphicsToTextureRef, + dest: GraphicsRefCustom, + ) -> Box { + Box::new(Self { source, dest }) } } -impl RenderTrait for TextureViewRender { - fn render(&self, device: &DeviceStateForRender) { - let source = &self.source; - source.render_to_texture(device.device_state(), &self.dest); +impl RenderTrait for ComputeTextureRender { + // whenver it's called, it'll increment! check if it's overdue before rendering! + fn render(&self, device_state_for_render: &DeviceStateForRender) { + let source_texture = &self.source; + let dest = &self.dest; + source_texture.render(device_state_for_render, dest); } + fn debug_print(&self) -> Vec { let source = &self.source; + let dest = &self.dest; vec![RenderDebugPrint { src: source.name(), - dest: "texture view!".to_string(), + dest: dest.name(), }] } - fn dest(&self) -> Option { + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + // todo!() None } } -pub struct DisplayRender { - pub source: GraphicsRef, +pub struct DisplayRender { + pub source: GraphicsRefCustom, } -impl DisplayRender { - pub fn new_box(source: GraphicsRef) -> Box { +impl DisplayRender { + pub fn new_box(source: GraphicsRefCustom) -> Box> { Box::new(DisplayRender { source }) } } -impl RenderTrait for DisplayRender { +impl RenderTrait for DisplayRender { fn render(&self, device: &DeviceStateForRender) { let source = &self.source; source.render_to_texture(device.device_state(), device.display_view()); @@ -446,7 +605,11 @@ impl RenderTrait for DisplayRender { }] } - fn dest(&self) -> Option { + fn dest_view(&self) -> Option { + None + } + + fn dest_view_other(&self) -> Option { None } } @@ -454,8 +617,8 @@ impl RenderTrait for DisplayRender { pub struct GPUPipeline { pub dag: Vec>, choices: Vec, - names: HashMap, // todo, do i need this with ctrl? - ctrl: Vec>, + names: HashMap>, // todo, do i need this with ctrl? + ctrl: Vec>>, source: Option, } @@ -470,20 +633,19 @@ impl GPUPipeline { } } - pub fn add_control_graphics( - &mut self, - _label: &str, - control_graphics_fn: GraphicsRefWithControlFn, - ) { - self.ctrl.push(control_graphics_fn) + pub fn add_control_graphics

(&mut self, _label: &str, provider: P) + where + P: ControlProvider + 'static, + { + self.ctrl.push(Box::new(provider)); } - pub fn control_graphics(&self, t: &GraphicConf) -> Vec { - let mut v = vec![]; - for c in &self.ctrl { - v.extend(c.control_graphics(t).into_iter()); + pub fn control_graphics(&self, t: &GraphicConf) -> Vec> { + let mut out = Vec::new(); + for p in &self.ctrl { + out.extend(p.make_controls(t)); } - v + out } pub fn set_source(&mut self, src: &str) { @@ -503,12 +665,15 @@ impl GPUPipeline { self.dag.push(d); } - pub fn add_label(&mut self, name: &str, g: GraphicsRef) { - self.names.insert(name.to_string(), g); + pub fn add_label(&mut self, name: &str, g: GraphicsRefCustom) + where + VertexKind: GraphicsVertex + 'static, + { + self.names.insert(name.to_string(), Box::new(g)); } - pub fn get_graphic(&self, name: &str) -> Option { - self.names.get(name).cloned() + pub fn get_graphic(&self, name: &str) -> Option<&dyn AnyGraphicsRef> { + self.names.get(name).map(|g| g.as_ref()) } // no-op if it doesn't exist @@ -529,14 +694,15 @@ impl GPUPipeline { self.dag.iter().flat_map(|x| x.debug_print()).collect() } - fn source(&self) -> GraphicsRef { + fn source(&self) -> wgpu::TextureView { // hm this should happen on start let name = self .source .as_ref() .expect("should have set a source if you're gonna get it source"); - self.get_graphic(&name) - .expect(&format!("gave a source {} that doesn't exist", name)) + self.get_graphic(name) + .unwrap_or_else(|| panic!("gave a source {} that doesn't exist", name)) + .texture_view() } } @@ -562,26 +728,25 @@ impl GPUPipelineRef { self.0.borrow().debug_print() } - pub fn source(&self) -> GraphicsRef { + pub fn source(&self) -> wgpu::TextureView { self.0.borrow().source() } - pub fn get_graphic(&self, name: &str) -> Option { - self.0.borrow().get_graphic(name) - } - - pub fn control_graphics(&self, conf: &GraphicsConf) -> Vec { + pub fn control_graphics(&self, conf: &GraphicsConf) -> Vec> { self.0.borrow().control_graphics(conf) } } pub struct SingleTextureRender { pub source: ImageTextureRef, - pub dest: GraphicsRef, + pub dest: GraphicsRefCustom, } impl SingleTextureRender { - pub fn new_box(source: ImageTextureRef, dest: GraphicsRef) -> Box { + pub fn new_box( + source: ImageTextureRef, + dest: GraphicsRefCustom, + ) -> Box { Box::new(SingleTextureRender { source, dest }) } } @@ -591,7 +756,7 @@ impl RenderTrait for SingleTextureRender { fn render(&self, device_state_for_render: &DeviceStateForRender) { let source_texture = &self.source; let dest = &self.dest; - source_texture.render(device_state_for_render, dest); + source_texture.render(device_state_for_render, &dest.texture_view()); } fn debug_print(&self) -> Vec { @@ -603,8 +768,12 @@ impl RenderTrait for SingleTextureRender { }] } - fn dest(&self) -> Option { - Some(self.dest.clone()) + fn dest_view(&self) -> Option { + Some(self.dest.texture_view()) + } + + fn dest_view_other(&self) -> Option { + None } } @@ -660,7 +829,6 @@ macro_rules! build_shader_pipeline { $dest.graphics(), ) ); - // pipeline_add_label!($pipeline, $source); pipeline_add_label!($pipeline, $dest); build_shader_pipeline!(@parse $pipeline ($($tail)*)); @@ -760,6 +928,23 @@ macro_rules! build_shader_pipeline { } }; + // compute shaders: =a -> T + (@parse $pipeline:ident (=$source:ident -> $dest:ident;$($tail:tt)*)) => { + { + println!("add compute"); + $pipeline.add_step( + ComputeTextureRender::new_box( + $source.clone(), + $dest.graphics() + ) + ); + // pipeline_add_label!($pipeline, $source); + pipeline_add_label!($pipeline, $dest); + + build_shader_pipeline!(@parse $pipeline ($($tail)*)); + } + }; + // one source to output: a -> t (@parse $pipeline:ident ($source:ident -> $dest:ident;$($tail:tt)*)) => { { @@ -799,7 +984,7 @@ macro_rules! build_shader_pipeline { #[derive(Clone)] pub struct ImageTextureRef(Rc>); impl ImageTextureRef { - pub fn render(&self, device_state_for_render: &DeviceStateForRender, dest: &GraphicsRef) { + pub fn render(&self, device_state_for_render: &DeviceStateForRender, dest: &wgpu::TextureView) { self.0.borrow().render(device_state_for_render, dest) } pub fn name(&self) -> String { @@ -815,7 +1000,7 @@ impl ImageTextureRef { pub struct VideoTextureRef(Rc>); pub struct VideoTexture { name: String, - pub graphics: GraphicsRef, + pub graphics: GraphicsRefCustom, pub binds: Vec, // path to pngs, probably keep it smapp pub fps: u64, last_time: Option, @@ -938,7 +1123,7 @@ impl VideoTexture { ) }; - let _uniforms = BasicUniform::from_dims(c.dims); + // let _uniforms = BasicUniform::from_dims(c.dims); let conf = GraphicsCreator::default() .with_first_texture_format(DEFAULT_TEXTURE_FORMAT) @@ -946,7 +1131,7 @@ impl VideoTexture { .with_mag_filter(wgpu::FilterMode::Linear) .with_address_mode(wgpu::AddressMode::Repeat); - let graphics = GraphicsRef::new(name, c, &gradient_shader, &conf); + let graphics = GraphicsRefCustom::new(name, c, &gradient_shader, &conf); graphics.update_uniforms_other( c, [1.0, 0.0, 0.0, 0.0], @@ -963,8 +1148,11 @@ impl VideoTexture { .iter() .map(|path| { // let texture = wgpu::Texture::from_path(c.window, path).unwrap(); // load the path - let texture_and_desc = - Graphics::texture(source_dims, c.device(), DEFAULT_LOADED_TEXTURE_FORMAT); + let texture_and_desc = Graphics::::texture( + source_dims, + c.device(), + DEFAULT_LOADED_TEXTURE_FORMAT, + ); GraphicsAssets::LocalFilesystem(path.to_path_buf()) .maybe_load_texture(c.device, &texture_and_desc.texture); let texture_view = @@ -994,11 +1182,15 @@ impl VideoTexture { pub struct ImageTexture { name: String, - pub graphics: GraphicsRef, + pub graphics: GraphicsRefCustom, } impl ImageTexture { - pub fn render(&self, device_state_for_render: &DeviceStateForRender, other: &GraphicsRef) { + pub fn render( + &self, + device_state_for_render: &DeviceStateForRender, + other: &wgpu::TextureView, + ) { self.graphics .render(device_state_for_render.device_state(), other); } @@ -1083,7 +1275,7 @@ impl ImageTexture { .with_mag_filter(wgpu::FilterMode::Nearest) .with_address_mode(address_mode); - let graphics = GraphicsRef::new_with_src( + let graphics = GraphicsRefCustom::new_with_src( name, c, // gets dims from here &repeat_img, @@ -1126,7 +1318,7 @@ impl ImageTexture { .with_mag_filter(wgpu::FilterMode::Nearest) .with_address_mode(wgpu::AddressMode::ClampToEdge); - let graphics = GraphicsRef::new_with_src( + let graphics = GraphicsRefCustom::new_with_src( name, c, &repeat_img, diff --git a/murrelet_gpu/src/graphics_ref.rs b/murrelet_gpu/src/graphics_ref.rs index aae66bb..8fabff5 100644 --- a/murrelet_gpu/src/graphics_ref.rs +++ b/murrelet_gpu/src/graphics_ref.rs @@ -1,8 +1,10 @@ #![allow(dead_code)] use std::{cell::RefCell, sync::Arc}; -use bytemuck::{Pod, Zeroable}; -use glam::{Mat4, Vec3}; +use bytemuck::NoUninit; +use glam::Mat4; + +use murrelet_common::triangulate::{DefaultVertex, Triangulate}; use std::rc::Rc; #[cfg(feature = "nannou")] @@ -17,7 +19,9 @@ use wgpu::TextureDescriptor; use crate::device_state::*; use crate::gpu_livecode::{ControlGraphics, ControlGraphicsRef}; -use crate::shader_str::{VERTEX_SHADER, VERTEX_SHADER_3D}; +use crate::shader_str::{PREFIX, VERTEX_SHADER, VERTEX_SHADER_3D}; +use crate::uniforms::{BasicUniform, UniformsPair}; +use crate::window::GraphicsWindowConf; #[cfg(not(feature = "nannou"))] pub const DEFAULT_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; @@ -29,61 +33,54 @@ pub const DEFAULT_LOADED_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureForm #[cfg(feature = "nannou")] pub const DEFAULT_LOADED_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; -fn shader_from_path(device: &wgpu::Device, data: &str) -> wgpu::ShaderModule { +pub fn shader_from_path(device: &wgpu::Device, data: &str) -> wgpu::ShaderModule { device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(data.into()), }) } -// for each vertex, this is what we'll pass in +pub trait GraphicsVertex: NoUninit + Copy + Clone + std::fmt::Debug + 'static { + fn vertex_shader() -> &'static str; + fn vertex_shader_3d() -> &'static str { + unimplemented!() + } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct Vertex { - position: [f32; 3], - normal: [f32; 3], - face_pos: [f32; 2], + fn fragment_prefix() -> &'static str; } -impl Vertex { - pub fn new(position: [f32; 3], normal: [f32; 3], face_pos: [f32; 2]) -> Self { - Self { - position, - normal, - face_pos, - } +impl GraphicsVertex for DefaultVertex { + fn vertex_shader() -> &'static str { + VERTEX_SHADER } - pub fn pos(&self) -> [f32; 3] { - self.position + + fn vertex_shader_3d() -> &'static str { + VERTEX_SHADER_3D } - pub fn pos_vec3(&self) -> Vec3 { - glam::vec3(self.position[0], self.position[1], self.position[2]) + fn fragment_prefix() -> &'static str { + PREFIX } } -unsafe impl Zeroable for Vertex {} -unsafe impl Pod for Vertex {} - // in the default vertex shader, z is dropped -pub const VERTICES: [Vertex; 4] = [ - Vertex { +pub const VERTICES: [DefaultVertex; 4] = [ + DefaultVertex { position: [-1.0, 1.0, 0.0], normal: [0.0, 0.0, 0.0], face_pos: [1.0, 0.0], }, - Vertex { + DefaultVertex { position: [-1.0, -1.0, 0.0], normal: [0.0, 0.0, 0.0], face_pos: [0.0, 0.0], }, - Vertex { + DefaultVertex { position: [1.0, 1.0, 0.0], normal: [0.0, 0.0, 0.0], face_pos: [1.0, 1.0], }, - Vertex { + DefaultVertex { position: [1.0, -1.0, 0.0], normal: [0.0, 0.0, 0.0], face_pos: [1.0, 0.0], @@ -137,79 +134,18 @@ pub struct Scene { // this is the conf that you'll interface with #[derive(Debug, Clone)] -pub struct Triangulate { - vertices: Vec, - order: Vec, -} - -impl Triangulate { - pub fn new() -> Self { - Triangulate { - vertices: vec![], - order: vec![], - } - } - - pub fn vertices(&self) -> &[Vertex] { - &self.vertices - } - - pub fn add_vertex(&mut self, v: [f32; 3], n: [f32; 3], face_pos: [f32; 2]) -> u16 { - let vv = Vertex::new(v, n, face_pos); - self.vertices.push(vv); - (self.vertices.len() - 1) as u16 - } - - // alternatively can add vertices and then add teh vec - pub fn add_rect(&mut self, v: &[Vec3; 4], flip: bool) { - let edge1 = v[0] - v[1]; - let edge2 = v[3] - v[1]; - let normal = edge1.cross(edge2).normalize().to_array(); - - let v0 = self.add_vertex(v[0].to_array(), normal, [1.0, 0.0]); - let v1 = self.add_vertex(v[1].to_array(), normal, [0.0, 0.0]); - let v2 = self.add_vertex(v[2].to_array(), normal, [1.0, 1.0]); - let v3 = self.add_vertex(v[3].to_array(), normal, [0.0, 1.0]); - - if !flip { - self.order.extend([v0, v2, v1, v1, v2, v3]) - } else { - self.order.extend([v0, v1, v2, v1, v3, v2]) - } - } - - fn order(&self) -> &[u16] { - &self.order - } - - pub fn indices(&self) -> &[u16] { - &self.order - } -} - -// this is the conf that you'll interface with -#[derive(Debug, Clone)] -pub struct InputVertexConf { +pub struct InputVertexConf { is_3d: bool, // todo, maybe can simplify now that i have this, e.g. vs_mod vs_mod: &'static str, view: VertexUniforms, topology: wgpu::PrimitiveTopology, - vertices: Vec, - order: Vec, + tri: Triangulate, // vertices: Vec, + // order: Vec, } -impl InputVertexConf { - pub fn buffer_slice(&self) -> &[u16] { - self.order.as_slice() - } - - pub fn from_triangulate(t: &Triangulate) -> Self { - let mut c = Self::default(); - c.is_3d = true; - c.vs_mod = VERTEX_SHADER_3D; - c.vertices = t.vertices.clone(); - c.order = t.order.clone(); - c +impl InputVertexConf { + pub fn buffer_slice(&self) -> &[u32] { + self.tri.order.as_slice() } pub fn set_view(mut self, view: Mat4, light: Mat4) -> Self { @@ -234,28 +170,52 @@ fn main(@location(0) position: vec3) -> @builtin(position) vec4 { ) } - pub fn with_custom_vertices(mut self, tri: &Triangulate) -> Self { - self.vertices = tri.vertices.clone(); + pub fn with_custom_vertices(mut self, tri: &Triangulate) -> Self { + self.tri = tri.clone(); self.topology = wgpu::PrimitiveTopology::TriangleList; - self.order = tri.order.clone(); self } pub fn indices(&self) -> u32 { - self.order.len() as u32 + self.tri.order.len() as u32 } +} - pub fn default() -> Self { - Self { - vs_mod: VERTEX_SHADER, +impl InputVertexConf { + pub fn default() -> InputVertexConf { + InputVertexConf { + vs_mod: DefaultVertex::vertex_shader(), view: VertexUniforms::identity(), topology: wgpu::PrimitiveTopology::TriangleList, - vertices: VERTICES.to_vec(), - order: vec![0, 1, 2, 1, 3, 2], + tri: Triangulate:: { + vertices: VERTICES.to_vec(), + order: vec![0, 1, 2, 1, 3, 2], + }, is_3d: false, } } } +impl InputVertexConf { + pub fn from_triangulate_2d(tri: Triangulate) -> InputVertexConf { + InputVertexConf { + vs_mod: VertexKind::vertex_shader(), + view: VertexUniforms::identity(), + topology: wgpu::PrimitiveTopology::TriangleList, + tri, + is_3d: false, + } + } + + pub fn from_triangulate(tri: Triangulate) -> InputVertexConf { + InputVertexConf { + vs_mod: VertexKind::vertex_shader_3d(), + view: VertexUniforms::identity(), + topology: wgpu::PrimitiveTopology::TriangleList, + tri, + is_3d: true, + } + } +} #[derive(Debug, Clone)] pub struct ShaderOptions { @@ -292,7 +252,7 @@ impl ShaderOptions { } } - fn as_sampler_desc(&self) -> wgpu::SamplerDescriptor { + fn as_sampler_desc(&self) -> wgpu::SamplerDescriptor<'_> { wgpu::SamplerDescriptor { address_mode_u: self.sampler_address_mode_u, address_mode_v: self.sampler_address_mode_v, @@ -338,15 +298,16 @@ struct TextureCreator { } #[derive(Debug, Clone)] -pub struct GraphicsCreator { +pub struct GraphicsCreator { first_texture: TextureCreator, second_texture: Option, details: ShaderOptions, color_blend: wgpu::BlendComponent, dst_texture: TextureCreator, - input_vertex: InputVertexConf, // defaults to the square + input_vertex: InputVertexConf, // defaults to the square + blend_state: wgpu::BlendState, } -impl Default for GraphicsCreator { +impl Default for GraphicsCreator { fn default() -> Self { GraphicsCreator { first_texture: TextureCreator { @@ -362,10 +323,46 @@ impl Default for GraphicsCreator { format: DEFAULT_TEXTURE_FORMAT, }, input_vertex: InputVertexConf::default(), + blend_state: wgpu::BlendState::REPLACE, + } + } +} +impl GraphicsCreator { + pub fn with_custom_triangle(mut self, t: &Triangulate, is_3d: bool) -> Self { + if is_3d { + self.input_vertex = InputVertexConf::from_triangulate(t.clone()); + } else { + self.input_vertex = InputVertexConf::from_triangulate_2d(t.clone()); } + self } } -impl GraphicsCreator { + +impl GraphicsCreator { + pub fn default_with_custom_vertex(t: &Triangulate, is_3d: bool) -> Self { + let input_vertex = if is_3d { + InputVertexConf::from_triangulate(t.clone()) + } else { + InputVertexConf::from_triangulate_2d(t.clone()) + }; + GraphicsCreator { + first_texture: TextureCreator { + format: DEFAULT_TEXTURE_FORMAT, + }, + second_texture: None, + details: ShaderOptions::new_with_options( + wgpu::FilterMode::Linear, + wgpu::AddressMode::ClampToEdge, + ), + color_blend: wgpu::BlendComponent::REPLACE, + dst_texture: TextureCreator { + format: DEFAULT_TEXTURE_FORMAT, + }, + input_vertex, + blend_state: wgpu::BlendState::REPLACE, + } + } + pub fn with_first_texture_format(mut self, format: wgpu::TextureFormat) -> Self { self.first_texture = TextureCreator { format }; self @@ -378,11 +375,6 @@ impl GraphicsCreator { self } - pub fn with_custom_triangle(mut self, t: &Triangulate) -> Self { - self.input_vertex = InputVertexConf::from_triangulate(t); - self - } - pub fn with_second_texture_format(mut self, format: wgpu::TextureFormat) -> Self { self.second_texture = Some(TextureCreator { format }); self @@ -418,130 +410,43 @@ impl GraphicsCreator { self } + pub fn with_blend_state(mut self, blend_state: wgpu::BlendState) -> Self { + self.blend_state = blend_state; + self + } + pub fn to_graphics_ref<'a>( &self, c: &GraphicsWindowConf<'a>, name: &str, fs_shader: &str, - ) -> GraphicsRef { + ) -> GraphicsRefCustom { if self.color_blend != wgpu::BlendComponent::REPLACE && self.dst_texture.format == wgpu::TextureFormat::Rgba32Float { panic!("can't blend with float32 textures"); } - GraphicsRef::new(name, c, fs_shader, self) + GraphicsRefCustom::new(name, c, fs_shader, self) } fn is_3d(&self) -> bool { self.input_vertex.is_3d } -} - -#[repr(C)] -#[derive(Copy, Clone, Debug)] -pub struct BasicUniform { - dims: [f32; 4], - more_info: [f32; 4], - more_info_other: [f32; 4], -} - -unsafe impl Zeroable for BasicUniform {} -unsafe impl Pod for BasicUniform {} - -impl BasicUniform { - fn empty_4() -> [f32; 4] { - [0.0, 0.0, 0.0, 0.0] - } - - pub fn from_empty() -> BasicUniform { - BasicUniform { - dims: BasicUniform::empty_4(), - more_info: BasicUniform::empty_4(), - more_info_other: BasicUniform::empty_4(), - } - } - fn _dims_to_more_info(w: f32, h: f32) -> [f32; 4] { - [w, h, 1.0 / w, 1.0 / h] - } - - pub fn from_dims([w, h]: [u32; 2]) -> BasicUniform { - let w_f32 = w as f32; - let h_f32 = h as f32; - let dims = BasicUniform::_dims_to_more_info(w_f32, h_f32); - BasicUniform { - dims, - more_info: BasicUniform::empty_4(), - more_info_other: BasicUniform::empty_4(), - } - } - - pub fn from_dims_and_more([w, h]: [u32; 2], more_info: [f32; 4]) -> BasicUniform { - let w_f32 = w as f32; - let h_f32 = h as f32; - let dims = BasicUniform::_dims_to_more_info(w_f32, h_f32); - BasicUniform { - dims, - more_info, - more_info_other: BasicUniform::empty_4(), - } - } - - pub fn update_more_info(&mut self, more_info: [f32; 4]) { - self.more_info = more_info - } - - pub fn update_more_info_other(&mut self, more_info: [f32; 4]) { - self.more_info_other = more_info - } - - fn as_bytes(&self) -> &[u8] { - bytemuck::bytes_of(self) - } - - fn uniforms_size(&self) -> u64 { - std::mem::size_of::() as wgpu::BufferAddress - } - - fn to_buffer(&self, device: &wgpu::Device) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: None, - size: self.uniforms_size(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }) - } - - fn copy_to_buffer( - &self, - dest: &wgpu::Buffer, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) { - encoder.copy_buffer_to_buffer(&self.to_buffer(device), 0, dest, 0, self.uniforms_size()); - } -} - -pub struct UniformsPair { - more_info: [f32; 4], - more_info_other: [f32; 4], -} -impl UniformsPair { - pub fn new(more_info: [f32; 4], more_info_other: [f32; 4]) -> UniformsPair { - UniformsPair { - more_info, - more_info_other, - } + pub fn blend_state(&self) -> wgpu::BlendState { + self.blend_state } } #[derive(Clone)] -pub struct GraphicsRef { - pub graphics: Rc>, +pub struct GraphicsRefCustom { + pub graphics: Rc>>, } -impl GraphicsRef { +pub type GraphicsRef = GraphicsRefCustom; + +impl GraphicsRefCustom { pub fn name(&self) -> String { self.graphics.borrow().name.clone() } @@ -557,9 +462,10 @@ impl GraphicsRef { name: &str, c: &GraphicsWindowConf<'a>, fs_shader: &str, - conf: &GraphicsCreator, + conf: &GraphicsCreator, assets: GraphicsAssets, ) -> Self { + println!("name {:?}", name); let graphics = Graphics::new_mut( name.to_string(), c, @@ -568,6 +474,7 @@ impl GraphicsRef { assets, conf.clone(), ); + println!("done name {:?}", name); Self { graphics } } @@ -575,7 +482,7 @@ impl GraphicsRef { name: &str, c: &GraphicsWindowConf<'a>, fs_shader: &str, - conf: &GraphicsCreator, + conf: &GraphicsCreator, ) -> Self { Self::new_with_src(name, c, fs_shader, conf, GraphicsAssets::Nothing) } @@ -590,19 +497,18 @@ impl GraphicsRef { graphics_rc.update_uniforms_other_tuple(c, more_info) } + #[deprecated(note = "Use render instead")] pub fn render_to_view(&self, device: &DeviceState, view: &wgpu::TextureView) { - self.graphics.borrow_mut().render(device, view) + self.render(device, view) } - pub fn render(&self, device: &DeviceState, other: &GraphicsRef) { - let view = &other.graphics.borrow_mut().input_texture_view; + pub fn render(&self, device: &DeviceState, view: &wgpu::TextureView) { self.graphics.borrow_mut().render(device, view) } - pub fn render_2tex(&self, device_state: &DeviceState, other: &GraphicsRef) { - let binding = other.graphics.borrow_mut(); - let view = binding.input_texture_view_other.as_ref().unwrap(); - self.graphics.borrow_mut().render(device_state, view) + #[deprecated(note = "Use render_to_view instead")] + pub fn render_2tex(&self, device: &DeviceState, view: &wgpu::TextureView) { + self.render(device, view) } pub fn update_uniforms(&self, c: &GraphicsWindowConf, more_info: [f32; 4]) { @@ -649,7 +555,7 @@ impl GraphicsRef { &self, label: &'static str, control_graphic_fn: Arc Box + 'static>, - ) -> GraphicsRefWithControlFn { + ) -> GraphicsRefWithControlFn { GraphicsRefWithControlFn { label, graphics: self.clone(), @@ -657,13 +563,13 @@ impl GraphicsRef { } } - pub fn graphics(&self) -> GraphicsRef { + pub fn graphics(&self) -> GraphicsRefCustom { self.clone() } pub fn control_graphics_fn( &self, - ) -> Option> { + ) -> Option> { None } @@ -671,27 +577,119 @@ impl GraphicsRef { let col = self.graphics.borrow().conf.input_vertex.view.view_proj; Mat4::from_cols_array_2d(&col) } + + pub fn update_tri(&mut self, c: &GraphicsWindowConf, tri: Triangulate) { + // capture previous buffer sizes + let (old_vert_bytes_len, old_index_bytes_len) = { + let g = self.graphics.borrow(); + ( + bytemuck::cast_slice::(&g.conf.input_vertex.tri.vertices).len(), + bytemuck::cast_slice::(&g.conf.input_vertex.tri.order).len(), + ) + }; + + { + let mut g = self.graphics.borrow_mut(); + g.conf.input_vertex.tri.vertices = tri.vertices.clone(); + g.conf.input_vertex.tri.order = tri.order.clone(); + let queue = c.device.queue(); + + // vertex buffer: either recreate or overwrite + let new_vert_bytes = + bytemuck::cast_slice::(&g.conf.input_vertex.tri.vertices); + if new_vert_bytes.len() > old_vert_bytes_len { + // recreate vertex buffer with new size + let vb = c + .device + .device() + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("vertex buffer"), + contents: new_vert_bytes, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + g.vertex_buffers.vertex = vb; + } else { + queue.write_buffer(&g.vertex_buffers.vertex, 0, new_vert_bytes); + } + + // index buffer with 4-byte alignment: recreate if growing + const ALIGN: usize = 4; + let raw_index = bytemuck::cast_slice::(&g.conf.input_vertex.tri.order); + let (index_bytes, needs_recreate) = if !raw_index.len().is_multiple_of(ALIGN) { + // pad to alignment + let pad = ALIGN - (raw_index.len() % ALIGN); + let mut data = Vec::with_capacity(raw_index.len() + pad); + data.extend_from_slice(raw_index); + data.extend(std::iter::repeat_n(0, pad)); + ( + data.into_boxed_slice(), + raw_index.len() + pad > old_index_bytes_len, + ) + } else { + (raw_index.into(), raw_index.len() > old_index_bytes_len) + }; + if needs_recreate { + let ib = c + .device + .device() + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("index buffer"), + contents: &index_bytes, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + }); + g.vertex_buffers.index = ib; + } else { + queue.write_buffer(&g.vertex_buffers.index, 0, &index_bytes); + } + } + } + + pub(crate) fn texture_view(&self) -> wgpu::TextureView { + self.graphics.borrow().texture_and_desc.default_view() + } + + pub(crate) fn texture_view_other(&self) -> Option { + self.graphics + .borrow() + .other_texture_and_desc + .as_ref() + .map(|x| x.default_view()) + } } #[derive(Clone)] -pub struct GraphicsRefWithControlFn { +pub struct GraphicsRefWithControlFn { pub label: &'static str, - pub graphics: GraphicsRef, + pub graphics: GraphicsRefCustom, pub control_graphic_fn: Arc Box>, } -impl GraphicsRefWithControlFn { - pub fn control_graphics(&self, conf: &GraphicsConf) -> Vec { +pub trait AnyGraphicsRef { + fn texture_view(&self) -> wgpu::TextureView; +} +impl AnyGraphicsRef for GraphicsRefCustom +where + VertexKind: GraphicsVertex + 'static, +{ + fn texture_view(&self) -> wgpu::TextureView { + self.texture_view() + } +} + +impl GraphicsRefWithControlFn { + pub fn control_graphics(&self, conf: &GraphicsConf) -> Vec> { let ctrl_graphics = (self.control_graphic_fn)(conf); ControlGraphicsRef::new(self.label, ctrl_graphics, Some(self.graphics.clone())) } - pub fn graphics(&self) -> GraphicsRef { + pub fn graphics(&self) -> GraphicsRefCustom { self.graphics.clone() } - pub fn control_graphics_fn(&self) -> Option> { + pub fn control_graphics_fn( + &self, + ) -> Option> { // Some(self.clone()) let c = GraphicsRefWithControlFn { label: self.label, @@ -708,6 +706,11 @@ pub struct TextureAndDesc { pub texture: Arc, pub desc: wgpu::TextureDescriptor<'static>, } +impl TextureAndDesc { + pub(crate) fn default_view(&self) -> wgpu::TextureView { + self.texture.create_view(&Default::default()) + } +} pub struct TextureFor3d { shadow_pipeline: wgpu::RenderPipeline, @@ -719,9 +722,9 @@ pub struct TextureFor3d { } // represents things needed to create a single texture... it's a bit of a mess -pub struct Graphics { +pub struct Graphics { name: String, - conf: GraphicsCreator, + conf: GraphicsCreator, bind_group: wgpu::BindGroup, vertex_buffers: VertexBuffers, render_pipeline: wgpu::RenderPipeline, @@ -737,12 +740,11 @@ pub struct Graphics { textures_for_3d: Option, } -impl Graphics { +impl Graphics { pub fn update_uniforms(&mut self, c: &GraphicsWindowConf, more_info: [f32; 4]) { let queue = &c.device.queue(); self.uniforms.more_info = more_info; - // println!("{:?}", self.uniform.more_info); queue.write_buffer(&self.uniforms_buffer, 0, self.uniforms.as_bytes()); } @@ -756,7 +758,6 @@ impl Graphics { self.uniforms.more_info = more_info; self.uniforms.more_info_other = more_info_other; - // println!("{:?}", self.uniform.more_info); queue.write_buffer(&self.uniforms_buffer, 0, self.uniforms.as_bytes()); } @@ -793,7 +794,8 @@ impl Graphics { usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::COPY_SRC, + | wgpu::TextureUsages::COPY_SRC + | wgpu::TextureUsages::STORAGE_BINDING, // needed for compute mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -817,7 +819,7 @@ impl Graphics { let mut bind_group_layout_entries = Vec::new(); bind_group_layout_entries.push(wgpu::BindGroupLayoutEntry { - binding: 0 as u32, // needs to line up with @group(0) @binding(1) + binding: 0_u32, // needs to line up with @group(0) @binding(1) visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, @@ -830,7 +832,7 @@ impl Graphics { if has_second_texture { bind_group_offset += 1; bind_group_layout_entries.push(wgpu::BindGroupLayoutEntry { - binding: 1 as u32, // needs to line up with @group(0) @binding(0) + binding: 1_u32, // needs to line up with @group(0) @binding(0) visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, @@ -903,9 +905,8 @@ impl Graphics { fn _sampler(device: &wgpu::Device, details: ShaderOptions) -> wgpu::Sampler { let sampler_desc = details.as_sampler_desc(); - let sampler = device.create_sampler(&sampler_desc); - println!("sampler: {:?}, {:?}", sampler, sampler_desc); - sampler + + device.create_sampler(&sampler_desc) } fn _bind_group( @@ -924,12 +925,12 @@ impl Graphics { entries.push(wgpu::BindGroupEntry { binding: 0, - resource: wgpu::BindingResource::TextureView(&input_texture_view), + resource: wgpu::BindingResource::TextureView(input_texture_view), }); if let Some(texture_view_other) = input_texture_view_other { entries.push(wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(&texture_view_other), + resource: wgpu::BindingResource::TextureView(texture_view_other), }); binding_offset += 1; } @@ -937,7 +938,7 @@ impl Graphics { // next is the sampler entries.push(wgpu::BindGroupEntry { binding: binding_offset + 1, - resource: wgpu::BindingResource::Sampler(&sampler), + resource: wgpu::BindingResource::Sampler(sampler), }); entries.push(wgpu::BindGroupEntry { @@ -977,16 +978,17 @@ impl Graphics { } fn _render_pipeline( - vertex_conf: &InputVertexConf, + conf: &GraphicsCreator, device: &wgpu::Device, bind_group_layout: &wgpu::BindGroupLayout, fs_mod: &wgpu::ShaderModule, dst_format: wgpu::TextureFormat, ) -> wgpu::RenderPipeline { - let pipeline_layout = Graphics::_pipeline_layout(device, bind_group_layout); + let vertex_conf = &conf.input_vertex; + let pipeline_layout = Graphics::::_pipeline_layout(device, bind_group_layout); let vertex_buffer_layouts = wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, + array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x2], }; @@ -1000,7 +1002,7 @@ impl Graphics { let color_state = vec![Some(wgpu::ColorTargetState { format: dst_format, - blend: Some(wgpu::BlendState::REPLACE), //None, + blend: Some(conf.blend_state()), write_mask: wgpu::ColorWrites::ALL, })]; @@ -1030,19 +1032,16 @@ impl Graphics { depth_stencil, multisample: wgpu::MultisampleState::default(), fragment: Some(wgpu::FragmentState { - module: &fs_mod, + module: fs_mod, entry_point: "main", targets: &color_state, #[cfg(not(feature = "nannou"))] compilation_options: wgpu::PipelineCompilationOptions::default(), }), multiview: None, - // cache: None, }; - let main_pipeline = device.create_render_pipeline(&rp_desc); - - main_pipeline + device.create_render_pipeline(&rp_desc) } fn _pipeline_layout( @@ -1063,7 +1062,7 @@ impl Graphics { fs_shader_data: &str, initial_uniform: BasicUniform, texture_src_path: GraphicsAssets, - conf: GraphicsCreator, + conf: GraphicsCreator, ) -> Rc> { // todo, i used to have code here to check the conf's destination texture was okay @@ -1085,11 +1084,11 @@ impl Graphics { fs_shader_data: &str, initial_uniform: BasicUniform, texture_src_path: GraphicsAssets, - conf: GraphicsCreator, + conf: GraphicsCreator, ) -> Self { let conf_c = conf.clone(); let has_second_texture = conf.second_texture.is_some(); - let details = conf.details; + let details = conf.clone().details; let first_format = conf.first_texture.format; let second_format = conf.second_texture.map(|x| x.format); let dst_format = conf.dst_texture.format; @@ -1103,7 +1102,8 @@ impl Graphics { // make a bind group layout let first_texture_format = texture_src_path.to_format(first_format); - let texture_and_desc = Graphics::texture(c.dims, device, first_texture_format); + let texture_and_desc = + Graphics::::texture(c.dims, device, first_texture_format); let input_texture = &texture_and_desc.texture; // maybe load the image source if we have one @@ -1111,10 +1111,9 @@ impl Graphics { let input_texture_view = input_texture.create_view(&Default::default()); - println!("input {:?}", input_texture_view); let (input_texture_view_other, other_texture_and_desc) = if has_second_texture { - let other_texture = Graphics::texture(c.dims(), device, second_format.unwrap()); - println!("other texture view {:?}", &other_texture.texture); + let other_texture = + Graphics::::texture(c.dims(), device, second_format.unwrap()); ( Some(other_texture.texture.create_view(&Default::default())), Some(other_texture), @@ -1122,10 +1121,9 @@ impl Graphics { } else { (None, None) }; - println!("other input {:?}", input_texture_view_other); - let sampler = Graphics::_sampler(device, details); - let bind_group_layout = Graphics::_bind_group_layout( + let sampler = Graphics::::_sampler(device, details); + let bind_group_layout = Graphics::::_bind_group_layout( device, has_second_texture, false, @@ -1134,13 +1132,8 @@ impl Graphics { let initial_uniform_buffer = initial_uniform.to_buffer(device); - let render_pipeline = Graphics::_render_pipeline( - &conf.input_vertex, - device, - &bind_group_layout, - &fs_mod, - dst_format, - ); + let render_pipeline = + Graphics::_render_pipeline(&conf, device, &bind_group_layout, &fs_mod, dst_format); let vertex_buffers = VertexBuffers::from_conf(device, &conf.input_vertex); @@ -1219,7 +1212,7 @@ impl Graphics { // needs to be same let vertex_buffer_layouts = wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, + array_stride: std::mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x2], }; @@ -1269,7 +1262,7 @@ impl Graphics { None }; - let bind_group = Graphics::_bind_group( + let bind_group = Graphics::::_bind_group( device, &bind_group_layout, &input_texture_view, @@ -1311,7 +1304,7 @@ impl Graphics { ) -> wgpu::BindGroup { println!("making custom {:?} {:?}", texture_view, self.sampler); - Graphics::_bind_group( + Graphics::::_bind_group( device, &self.bind_group_layout, texture_view, @@ -1327,7 +1320,7 @@ impl Graphics { ) } - pub fn depth_stencil_attachment(&self) -> Option { + pub fn depth_stencil_attachment(&self) -> Option> { if let Some(TextureFor3d { depth_view, .. }) = &self.textures_for_3d { Some(wgpu::RenderPassDepthStencilAttachment { view: depth_view, @@ -1368,7 +1361,7 @@ impl Graphics { label: Some("Shadow Pass"), color_attachments: &[], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &shadow_view, + view: shadow_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(1.0), #[cfg(not(feature = "nannou"))] @@ -1384,7 +1377,7 @@ impl Graphics { timestamp_writes: Default::default(), }); shadow_pass.set_pipeline(shadow_pipeline); - shadow_pass.set_bind_group(0, &shadow_bind_group, &[]); + shadow_pass.set_bind_group(0, shadow_bind_group, &[]); shadow_pass.set_vertex_buffer(0, self.vertex_buffers.vertex.slice(..)); shadow_pass.set_index_buffer( self.vertex_buffers.index.slice(..), @@ -1420,7 +1413,7 @@ impl Graphics { rpass.set_vertex_buffer(0, self.vertex_buffers.vertex.slice(..)); rpass.set_index_buffer( self.vertex_buffers.index.slice(..), - wgpu::IndexFormat::Uint16, + wgpu::IndexFormat::Uint32, ); rpass.draw_indexed(0..self.conf.input_vertex.indices(), 0, 0..1); drop(rpass); @@ -1439,7 +1432,7 @@ impl Graphics { } pub fn quick_texture(dims: [u32; 2], device: &wgpu::Device) -> TextureAndDesc { - Graphics::texture(dims, device, DEFAULT_TEXTURE_FORMAT) + Graphics::::texture(dims, device, DEFAULT_TEXTURE_FORMAT) } pub struct VertexBuffers { @@ -1450,16 +1443,19 @@ pub struct VertexBuffers { impl VertexBuffers { // inits them all - fn from_conf(device: &wgpu::Device, conf: &InputVertexConf) -> Self { + fn from_conf( + device: &wgpu::Device, + conf: &InputVertexConf, + ) -> Self { let vertex = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, - contents: bytemuck::cast_slice(&conf.vertices[..]), - usage: wgpu::BufferUsages::VERTEX, + contents: bytemuck::cast_slice(&conf.tri.vertices[..]), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, }); let order = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: None, - contents: bytemuck::cast_slice(&conf.order[..]), - usage: wgpu::BufferUsages::INDEX, + contents: bytemuck::cast_slice(&conf.tri.order[..]), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, }); let uniform = conf.view.to_buffer(device); diff --git a/murrelet_gpu/src/lib.rs b/murrelet_gpu/src/lib.rs index cbe23f9..01a4b20 100644 --- a/murrelet_gpu/src/lib.rs +++ b/murrelet_gpu/src/lib.rs @@ -1,5 +1,9 @@ +pub mod compute; pub mod device_state; +pub mod editable_shaders; pub mod gpu_livecode; pub mod gpu_macros; pub mod graphics_ref; pub mod shader_str; +pub mod uniforms; +pub mod window; diff --git a/murrelet_gpu/src/shader_str.rs b/murrelet_gpu/src/shader_str.rs index 509cdd1..d9a9477 100644 --- a/murrelet_gpu/src/shader_str.rs +++ b/murrelet_gpu/src/shader_str.rs @@ -3,6 +3,37 @@ pub const SUFFIX: &str = r#" } "#; +pub const COMPUTE_TEX: &str = r#" +struct BasicUniform { + dims: vec4, + more_info: vec4, + more_info_other: vec4, +}; + +// --- bindings --- +@group(0) @binding(0) var input_data : array; +@group(0) @binding(1) var cell_offsets : array; +@group(0) @binding(2) var cell_indices : array; +@group(0) @binding(3) var uniforms : BasicUniform; + +@group(0) @binding(4) var out_img : texture_storage_2d; + +fn img_wh() -> vec2 { + return vec2(u32(uniforms.dims.x), u32(uniforms.dims.y)); +} + +fn to_uv(gid_xy: vec2) -> vec2 { + let wh = vec2(uniforms.dims.xy); + return (vec2(gid_xy) + vec2(0.5)) / wh; +} + +fn cell_id(uv: vec2, Nx: u32, Ny: u32) -> u32 { + let xy = clamp(vec2(floor(uv * vec2(f32(Nx), f32(Ny)))), + vec2(0u), vec2(Nx - 1u, Ny - 1u)); + return xy.y * Nx + xy.x; +} +"#; + pub const BINDING_2TEX: &str = r#" struct FragmentOutput { @location(0) f_color: vec4, @@ -130,12 +161,36 @@ fn rand2(n: vec2) -> f32 { return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); } + // more things + fn hash21(p: vec2) -> f32 { + // Dave-Hoskins style + let p3 = fract(vec3(p.x, p.y, p.x) * 0.1031); + let p3x = p3.x + dot(p3, p3.yzx + 33.33); + return fract((p3x + p3.y) * p3.z); +} + +fn hash22(p: vec2) -> vec2 { + var p3 = fract(vec3(p.x, p.y, p.x) * 0.1031); + p3 = p3 + dot(p3, p3.yzx + 33.33); + return fract((p3.xx + p3.yz) * p3.zy); +} + // i don't know where this went fn smoothStep(edge0: vec2, edge1: vec2, x: vec2) -> vec2 { let t: vec2 = clamp((x - edge0) / (edge1 - edge0), vec2(0.0, 0.0), vec2(1.0, 1.0)); return t * t * (vec2(3.0, 3.0) - 2.0 * t); } +fn smoothStepf32(edge0: f32, edge1: f32, x: f32) -> f32 { + let t: f32 = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} + +// just basically mix but this is what i use when livecoding +fn s(pct: f32, a: f32, b: f32) -> f32 { + return mix(a, b, pct); +} + fn noise2(n: vec2) -> f32 { let d = vec2(0., 1.); @@ -185,6 +240,10 @@ fn is_almost_zero(v: f32) -> f32 { return 1.0 - is_almost_nonzero(v); } + fn soft_disk(d: f32, r: f32, w: f32) -> f32 { + return 1.0 - smoothstep(r - w, r, d); +} + fn fbm(i: vec2) -> f32 { var p = i; @@ -223,6 +282,56 @@ fn clamp4(p: vec4, min: f32, max: f32) -> vec4 { ); } + + +fn colormap( + hue_start: f32, hue_length: f32, hue_target_offset: f32, + center_loc: f32, + sat_center: f32, sat_edges: f32, + value_center: f32, value_edges: f32, + tex_coords: vec2, +) -> vec3 { + // what is the main hue? + // let hue_start = uniforms.more_info.x; + // // how far to go across the x-axis + // let hue_length = uniforms.more_info.y; + // // how far to go along the y-axis, this one will have some effects that we'll add next + // let hue_target_offset = uniforms.more_info.z; + + // let center_loc = uniforms.more_info.a; + + // // now add in the transition variables + // let sat_center = uniforms.more_info_other.x; + // let sat_edges = uniforms.more_info_other.y; + + // let value_center = uniforms.more_info_other.z; + // let value_edges = uniforms.more_info_other.a; + + let target_hue_top = hue_start + tex_coords.x * hue_length; + let target_hue = target_hue_top + tex_coords.y * hue_target_offset; + + // now compute some things to figure out what we should show + let abs_shape = 1.0 - 2.0 * abs(tex_coords.y - 0.5); + let l_shape = 1.0 - tex_coords.y; + let v_shape = mix(abs_shape, l_shape, center_loc); + + let smoothed_dist_from_center = v_shape * v_shape * (3.0 - 2.0 * v_shape); + + let target_sat = mix(sat_edges, sat_center, smoothed_dist_from_center); + let target_value = mix(value_edges, value_center, smoothed_dist_from_center); + + // okay and convert to rgb + let rgb1 = hsv2rgb(vec3(target_hue, target_sat, target_value)); + + // if it's at the edges, set to black i guess? + let is_too_high_x = step(1.0, tex_coords.y + uniforms.dims.a); + let rgb = mix(rgb1, vec3(0.0, 0.0, 0.0), is_too_high_x); + + return rgb; + + +} + fn color_if_for_neg_color(p: vec4) -> vec4 { return vec4(step(0.0, p.r), step(0.0, p.g), step(0.0, p.b), 1.0); } @@ -297,8 +406,8 @@ fn main(@location(0) pos: vec3, @location(1) normal: vec3, @location(2 return VertexOutput( tex_coords, vec4(0.0), // shad_info - vec3(0.0), //normal - vec4(0.0), //light space pos + normal, + vec4(face_loc, 0.0, 0.0), //face loc vec3(0.0), //world_pos, out_pos); }"; @@ -337,3 +446,34 @@ pub const PREFIX: &str = r#" @fragment fn main(@location(0) tex_coords: vec2, @location(1) shad_info: vec4, @location(2) normal: vec3, @location(3) light_space_pos: vec4, @location(4) world_pos: vec3) -> FragmentOutput { "#; + +pub const COMPUTE_FORMAT_STR: &str = r#" +@compute @workgroup_size(8, 8) +fn main(@builtin(global_invocation_id) gid: vec3) { + let w = u32(uniforms.dims.x); + let h = u32(uniforms.dims.y); + let uv = to_uv(gid.xy); + + let Nx = max(u32(uniforms.more_info.x), 1u); + let Ny = max(u32(uniforms.more_info.y), 1u); + let cid = cell_id(uv, Nx, Ny); + + let start_idx = cell_offsets[cid]; + let end_idx = cell_offsets[cid + 1u]; + + #PREFIX_CODEHERE# + + for (var i = start_idx; i < end_idx; i = i + 1u) { + let sid = cell_indices[i]; + let data = input_data[sid]; + // dmin = min(dmin, seg_dist(uv, seg.a, seg.b)); + + #FORLOOP_CODEHERE# + + } + + #SUFFIX_CODEHERE# + + textureStore(out_img, vec2(gid.xy), result); +} +"#; diff --git a/murrelet_gpu/src/uniforms.rs b/murrelet_gpu/src/uniforms.rs new file mode 100644 index 0000000..bebf658 --- /dev/null +++ b/murrelet_gpu/src/uniforms.rs @@ -0,0 +1,95 @@ +use bytemuck::{Pod, Zeroable}; +#[cfg(feature = "nannou")] +use wgpu_for_nannou as wgpu; + +#[cfg(not(feature = "nannou"))] +use wgpu_for_latest as wgpu; + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct BasicUniform { + dims: [f32; 4], + pub more_info: [f32; 4], + pub more_info_other: [f32; 4], +} + +unsafe impl Zeroable for BasicUniform {} +unsafe impl Pod for BasicUniform {} + +impl BasicUniform { + fn empty_4() -> [f32; 4] { + [0.0, 0.0, 0.0, 0.0] + } + + pub fn from_empty() -> BasicUniform { + BasicUniform { + dims: BasicUniform::empty_4(), + more_info: BasicUniform::empty_4(), + more_info_other: BasicUniform::empty_4(), + } + } + + fn _dims_to_more_info(w: f32, h: f32) -> [f32; 4] { + [w, h, 1.0 / w, 1.0 / h] + } + + pub fn from_dims([w, h]: [u32; 2]) -> BasicUniform { + let w_f32 = w as f32; + let h_f32 = h as f32; + let dims = BasicUniform::_dims_to_more_info(w_f32, h_f32); + BasicUniform { + dims, + more_info: BasicUniform::empty_4(), + more_info_other: BasicUniform::empty_4(), + } + } + + pub fn from_dims_and_more([w, h]: [u32; 2], more_info: [f32; 4]) -> BasicUniform { + let w_f32 = w as f32; + let h_f32 = h as f32; + let dims = BasicUniform::_dims_to_more_info(w_f32, h_f32); + BasicUniform { + dims, + more_info, + more_info_other: BasicUniform::empty_4(), + } + } + + pub fn update_more_info(&mut self, more_info: [f32; 4]) { + self.more_info = more_info + } + + pub fn update_more_info_other(&mut self, more_info: [f32; 4]) { + self.more_info_other = more_info + } + + pub fn as_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } + + fn uniforms_size(&self) -> u64 { + std::mem::size_of::() as wgpu::BufferAddress + } + + pub fn to_buffer(&self, device: &wgpu::Device) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: self.uniforms_size(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }) + } +} + +pub struct UniformsPair { + pub more_info: [f32; 4], + pub more_info_other: [f32; 4], +} +impl UniformsPair { + pub fn new(more_info: [f32; 4], more_info_other: [f32; 4]) -> UniformsPair { + UniformsPair { + more_info, + more_info_other, + } + } +} diff --git a/murrelet_gpu/src/window.rs b/murrelet_gpu/src/window.rs new file mode 100644 index 0000000..8dda2a0 --- /dev/null +++ b/murrelet_gpu/src/window.rs @@ -0,0 +1,59 @@ +// stores info about the window.. + +use crate::device_state::{DeviceState, GraphicsAssets}; +#[cfg(feature = "nannou")] +use wgpu_for_nannou as wgpu; + +#[cfg(not(feature = "nannou"))] +use wgpu_for_latest as wgpu; + +#[derive(Clone, Debug)] +pub struct GraphicsWindowConf<'a> { + pub device: &'a DeviceState<'a>, + pub dims: [u32; 2], + pub assets_path: GraphicsAssets, +} +impl<'a> GraphicsWindowConf<'a> { + pub fn new( + device: &'a DeviceState, + dims: [u32; 2], + assets_path: GraphicsAssets, + ) -> GraphicsWindowConf<'a> { + GraphicsWindowConf { + device, + dims, + assets_path, + } + } + + pub fn multi(&self, multiplier: f32) -> GraphicsWindowConf<'_> { + let [x, y] = self.dims; + GraphicsWindowConf { + device: self.device, + dims: [ + (x as f32 * multiplier) as u32, + (y as f32 * multiplier) as u32, + ], + assets_path: GraphicsAssets::Nothing, + } + } + + pub fn dims(&self) -> [u32; 2] { + self.dims + } + + pub fn device(&self) -> &wgpu::Device { + self.device.device() + } + + pub fn with_dims(&self, dims: [u32; 2]) -> Self { + Self { + dims, + ..self.clone() + } + } + + pub fn queue(&self) -> &wgpu::Queue { + self.device.queue() + } +} diff --git a/murrelet_gui/Cargo.toml b/murrelet_gui/Cargo.toml new file mode 100644 index 0000000..f51a48a --- /dev/null +++ b/murrelet_gui/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "murrelet_gui" +version = "0.1.2" +edition = "2021" + +[features] +default = [] +glam = ["dep:glam"] +murrelet = ["dep:murrelet_common"] + +[dependencies] +murrelet_gui_derive = { workspace = true } +murrelet_schema = { workspace = true } +murrelet_schema_derive = { workspace = true } +murrelet_common = { workspace = true, optional = true } # used to derive types + +thiserror = "2.0.11" +serde = { version = "1.0.104", features = ["derive"] } +serde_json = "1.0.48" +itertools = "0.10.5" + +glam = { version = "0.23", optional = true } + +[[example]] +name = "tests" +path = "examples/tests.rs" + +[[example]] +name = "tests_schema" +path = "examples/tests_schema.rs" diff --git a/murrelet_gui/examples/tests.rs b/murrelet_gui/examples/tests.rs new file mode 100644 index 0000000..8870ad5 --- /dev/null +++ b/murrelet_gui/examples/tests.rs @@ -0,0 +1,108 @@ +mod tests_schema; + +use std::collections::HashMap; + +use murrelet_gui::{CanMakeGUI, MurreletEnumValGUI, MurreletGUI, MurreletGUISchema, ValueGUI}; + +#[derive(MurreletGUI)] +pub struct BasicTypes { + a_number: f32, + b_number: usize, + c_number: u64, + d_number: i32, + bool: bool, + something: Vec, + s: String, + #[murrelet_gui(reference = "test")] + referenced_string: String, +} + +fn custom_func() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Num) +} + +#[derive(MurreletGUI)] +pub struct OverridesAndRecursive { + a_number: f32, + something: Vec, + #[murrelet_gui(func = "custom_func")] + label: String, + #[murrelet_gui(kind = "skip")] + b: HashMap, +} + +#[derive(MurreletGUI)] +enum EnumTest { + A, + B(OverridesAndRecursive), +} + +#[derive(MurreletGUI)] +struct SimpleNewtype(f32); + +// fn lerp_partial(&self, pct: T) -> Self { +// SimpleNewtype(pct.lerp_pct() as f32) +// } +// } + +// #[derive(Debug, Clone, MurreletUX)] + +fn main() { + // let b = BasicTypes{ + // a_number: 1.0, + // b_number: -10.0, + // }; + let test_val = BasicTypes::make_gui(); + + let basic_types_schema = MurreletGUISchema::Struct( + "BasicTypes".to_string(), + vec![ + ("a_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("b_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("c_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("d_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("bool".to_owned(), MurreletGUISchema::Val(ValueGUI::Bool)), + ( + "something".to_owned(), + MurreletGUISchema::list(MurreletGUISchema::Val(ValueGUI::Num)), + ), + ("s".to_owned(), MurreletGUISchema::Skip), + ( + "referenced_string".to_owned(), + MurreletGUISchema::Val(ValueGUI::Name("test".to_owned(), false)), + ), + ], + ); + + assert_eq!(test_val, basic_types_schema); + + let test_val = OverridesAndRecursive::make_gui(); + + let overrides_and_recursive_schema = MurreletGUISchema::Struct( + "OverridesAndRecursive".to_string(), + vec![ + ("a_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ( + "something".to_owned(), + MurreletGUISchema::list(basic_types_schema), + ), + ("label".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), // make sure it calls the override + ("b".to_owned(), MurreletGUISchema::Skip), + ], + ); + assert_eq!(test_val, overrides_and_recursive_schema); + + let test_val = EnumTest::make_gui(); + + assert_eq!( + test_val, + MurreletGUISchema::Enum( + "EnumTest".to_string(), + vec![ + (MurreletEnumValGUI::Unit("A".to_owned())), + (MurreletEnumValGUI::Unnamed("B".to_owned(), overrides_and_recursive_schema)), + ], + false + ) + ); +} diff --git a/murrelet_gui/examples/tests_schema.rs b/murrelet_gui/examples/tests_schema.rs new file mode 100644 index 0000000..d526742 --- /dev/null +++ b/murrelet_gui/examples/tests_schema.rs @@ -0,0 +1,107 @@ +use std::collections::HashMap; + +use murrelet_gui::{CanChangeToGUI, CanMakeGUI, MurreletEnumValGUI, MurreletGUISchema, ValueGUI}; +use murrelet_schema::*; +use murrelet_schema_derive::MurreletSchema; + +#[derive(MurreletSchema)] +pub struct BasicTypes { + a_number: f32, + b_number: usize, + c_number: u64, + d_number: i32, + bool: bool, + something: Vec, + s: String, + referenced_string: String, +} + +fn custom_func() -> MurreletSchema { + MurreletSchema::Val(murrelet_schema::MurreletPrimitive::Num) +} + +#[derive(MurreletSchema)] +pub struct OverridesAndRecursive { + a_number: f32, + something: Vec, + #[murrelet_schema(func = "custom_func")] + label: String, + #[murrelet_schema(kind = "skip")] + b: HashMap, +} + +#[derive(MurreletSchema)] +enum EnumTest { + A, + B(OverridesAndRecursive), +} + +#[derive(MurreletSchema)] +struct SimpleNewtype(f32); + +// fn lerp_partial(&self, pct: T) -> Self { +// SimpleNewtype(pct.lerp_pct() as f32) +// } +// } + +// #[derive(Debug, Clone, MurreletUX)] + +fn main() { + // let b = BasicTypes{ + // a_number: 1.0, + // b_number: -10.0, + // }; + let test_val = BasicTypes::make_schema().change_to_gui(); + + let basic_types_schema = MurreletGUISchema::Struct( + "BasicTypes".to_string(), + vec![ + ("a_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("b_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("c_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("d_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ("bool".to_owned(), MurreletGUISchema::Val(ValueGUI::Bool)), + ( + "something".to_owned(), + MurreletGUISchema::list(MurreletGUISchema::Val(ValueGUI::Num)), + ), + ("s".to_owned(), MurreletGUISchema::Val(ValueGUI::String)), + ( + "referenced_string".to_owned(), + MurreletGUISchema::Val(ValueGUI::String), + ), + ], + ); + + assert_eq!(test_val, basic_types_schema); + + let test_val = OverridesAndRecursive::make_schema().change_to_gui(); + + let overrides_and_recursive_schema = MurreletGUISchema::Struct( + "OverridesAndRecursive".to_string(), + vec![ + ("a_number".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), + ( + "something".to_owned(), + MurreletGUISchema::list(basic_types_schema), + ), + ("label".to_owned(), MurreletGUISchema::Val(ValueGUI::Num)), // make sure it calls the override + ("b".to_owned(), MurreletGUISchema::Skip), + ], + ); + assert_eq!(test_val, overrides_and_recursive_schema); + + let test_val = EnumTest::make_schema().change_to_gui(); + + assert_eq!( + test_val, + MurreletGUISchema::Enum( + "EnumTest".to_string(), + vec![ + (MurreletEnumValGUI::Unit("A".to_owned())), + (MurreletEnumValGUI::Unnamed("B".to_owned(), overrides_and_recursive_schema)), + ], + false + ) + ); +} diff --git a/murrelet_gui/src/lib.rs b/murrelet_gui/src/lib.rs new file mode 100644 index 0000000..d1b2df3 --- /dev/null +++ b/murrelet_gui/src/lib.rs @@ -0,0 +1,209 @@ +use itertools::Itertools; +#[cfg(feature = "murrelet")] +use murrelet_common::MurreletColor; +pub use murrelet_gui_derive::MurreletGUI; +use murrelet_schema::{MurreletEnumVal, MurreletPrimitive, MurreletSchema}; +use serde::Serialize; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum ValueGUI { + Bool, // should be a ControlBool + Num, // should be a ControlF32 + Name(String, bool), // clue for the front-end to sync the strings, bool is if it's def + Color, // expected to give h s v a + Defs, // make a ctx node + Vec2, // arbitrary vec2, also see Coords + Vec3, // arbitrary vec3 + Style, // murrelet style + Angle, // angle pi + Coords, // global coords, so the user can click things + String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum MurreletEnumValGUI { + Unnamed(String, MurreletGUISchema), + Unit(String), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum MurreletGUISchema { + NewType(String, Box), + Struct(String, Vec<(String, MurreletGUISchema)>), // field val + Enum(String, Vec, bool), // type, val, is untagged + List(Box), + Val(ValueGUI), + Skip, +} +impl MurreletGUISchema { + pub fn new_type(name: String, m: MurreletGUISchema) -> Self { + Self::NewType(name, Box::new(m)) + } + + pub fn list(m: MurreletGUISchema) -> Self { + Self::List(Box::new(m)) + } + + pub fn as_enum(&self) -> Option<&Vec> { + if let Self::Enum(_, v, _) = self { + Some(v) + } else { + None + } + } + + pub fn as_new_type(&self) -> Option<&Box> { + if let Self::NewType(_, v) = self { + Some(v) + } else { + None + } + } + + pub fn unwrap_to_struct_fields(self) -> Vec<(String, MurreletGUISchema)> { + match self { + MurreletGUISchema::Struct(_, items) => items, + _ => unreachable!("tried to flatten a struct that wasn't a struct"), + } + } +} + +// this should be on the Control version +pub trait CanMakeGUI: Sized { + fn make_gui() -> MurreletGUISchema; +} + +macro_rules! impl_can_make_gui_for_num { + ($ty:ty) => { + impl CanMakeGUI for $ty { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Num) + } + } + }; +} + +impl_can_make_gui_for_num!(f32); +impl_can_make_gui_for_num!(f64); +impl_can_make_gui_for_num!(u32); +impl_can_make_gui_for_num!(u64); +impl_can_make_gui_for_num!(i32); +impl_can_make_gui_for_num!(i64); +impl_can_make_gui_for_num!(usize); + +impl CanMakeGUI for Vec { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::List(Box::new(T::make_gui())) + } +} + +impl CanMakeGUI for String { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::Skip + } +} + +impl CanMakeGUI for bool { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Bool) + } +} + +#[cfg(feature = "glam")] +impl CanMakeGUI for glam::Vec2 { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Vec2) + } +} + +#[cfg(feature = "glam")] +impl CanMakeGUI for glam::Vec3 { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Vec3) + } +} + +#[cfg(feature = "murrelet")] +impl CanMakeGUI for MurreletColor { + fn make_gui() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Color) + } +} + +#[cfg(feature = "murrelet")] +pub fn make_gui_angle() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Angle) +} + +#[cfg(feature = "murrelet")] +pub fn make_gui_vec2() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Vec2) +} + +#[cfg(feature = "murrelet")] +pub fn make_gui_vec2_coords() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Coords) +} + +#[cfg(feature = "murrelet")] +pub fn make_gui_vec3() -> MurreletGUISchema { + MurreletGUISchema::Val(ValueGUI::Vec3) +} + +// if you already have a schema, you can transform it +pub trait CanChangeToGUI: Sized { + fn change_to_gui(&self) -> MurreletGUISchema; +} + +impl CanChangeToGUI for MurreletSchema { + fn change_to_gui(&self) -> MurreletGUISchema { + match self { + MurreletSchema::NewType(name, murrelet_schema) => { + MurreletGUISchema::NewType(name.clone(), Box::new(murrelet_schema.change_to_gui())) + } + MurreletSchema::Struct(name, items) => MurreletGUISchema::Struct( + name.clone(), + items + .iter() + .map(|(k, v)| (k.clone(), v.change_to_gui())) + .collect::>(), + ), + MurreletSchema::Enum(name, items, b) => MurreletGUISchema::Enum( + name.clone(), + items.values().map(change_enum_to_gui).collect_vec(), + *b, + ), + MurreletSchema::List(murrelet_schema) => { + MurreletGUISchema::List(Box::new(murrelet_schema.change_to_gui())) + } + MurreletSchema::Val(murrelet_primitive) => { + MurreletGUISchema::Val(change_primitive_to_gui(murrelet_primitive)) + } + MurreletSchema::Skip => MurreletGUISchema::Skip, + } + } +} + +fn change_enum_to_gui(a: &MurreletEnumVal) -> MurreletEnumValGUI { + match a { + MurreletEnumVal::Unnamed(a, murrelet_schema) => { + MurreletEnumValGUI::Unnamed(a.clone(), murrelet_schema.change_to_gui()) + } + MurreletEnumVal::Unit(a) => MurreletEnumValGUI::Unit(a.clone()), + } +} + +fn change_primitive_to_gui(a: &MurreletPrimitive) -> ValueGUI { + match a { + MurreletPrimitive::Bool => ValueGUI::Bool, + MurreletPrimitive::Num => ValueGUI::Num, + MurreletPrimitive::Color => ValueGUI::Color, + MurreletPrimitive::Defs => ValueGUI::Defs, + MurreletPrimitive::Vec2 => ValueGUI::Vec2, + MurreletPrimitive::Vec3 => ValueGUI::Vec3, + MurreletPrimitive::Style => ValueGUI::Style, + MurreletPrimitive::Angle => ValueGUI::Angle, + MurreletPrimitive::Coords => ValueGUI::Coords, + MurreletPrimitive::String => ValueGUI::String, + } +} diff --git a/murrelet_gui_derive/Cargo.toml b/murrelet_gui_derive/Cargo.toml new file mode 100644 index 0000000..dca223a --- /dev/null +++ b/murrelet_gui_derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "murrelet_gui_derive" +version = "0.1.2" +edition = "2021" + +[lib] +proc-macro = true + +[features] +debug_logging = ["log"] + +[dependencies] +syn = "2.0.15" +quote = "1.0.18" +proc-macro2 = "1.0.37" +darling = "0.20.3" + +log = { version = "0.4.25", optional = true } + diff --git a/murrelet_gui_derive/src/derive_gui.rs b/murrelet_gui_derive/src/derive_gui.rs new file mode 100644 index 0000000..491ffe2 --- /dev/null +++ b/murrelet_gui_derive/src/derive_gui.rs @@ -0,0 +1,235 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::parser::*; + +pub(crate) struct FieldTokensGUI { + pub(crate) for_make_gui: TokenStream2, + // pub(crate) for_gui_to_livecode: TokenStream2, +} +impl GenFinal for FieldTokensGUI { + // Something(f32) + fn make_newtype_struct_final( + idents: ParsedFieldIdent, + variants: Vec, + ) -> TokenStream2 { + let name = idents.name; + let name_str = name.to_string(); + + let for_make_gui = variants.iter().map(|x| x.for_make_gui.clone()); + // let for_gui_to_livecode = variants.iter().map(|x| x.for_gui_to_livecode.clone()); + + quote! { + impl murrelet_gui::CanMakeGUI for #name { + fn make_gui() -> murrelet_gui::MurreletGUISchema { + murrelet_gui::MurreletGUISchema::new_type(#name_str.to_owned(), #(#for_make_gui,)*) + } + + // fn gui_to_livecode(&self, gui_val: murrelet_gui::MurreletGUISchema) -> murrelet_gui::MurreletGUISchemaResult { + // if let Some(s) = gui_val.as_new_type() { + // Ok(#name(#(#for_gui_to_livecode,)*)) + // } else { + // Err(murrelet_gui::MurreletGUISchemaErr::GUIToLivecode("newtype not in newtype")) + // } + + // } + } + } + } + + fn make_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2 { + let name = idents.name; + let name_str = name.to_string(); + + let for_make_gui = variants.iter().map(|x| x.for_make_gui.clone()); + + // let for_assign_vars = variants.iter().map(|x| x.for_assign_vars.clone()); + // let for_gui_to_livecode = variants.iter().map(|x| x.for_gui_to_livecode.clone()); + + quote! { + impl murrelet_gui::CanMakeGUI for #name { + fn make_gui() -> murrelet_gui::MurreletGUISchema { + + let mut v = vec![]; + #(#for_make_gui;)* + + murrelet_gui::MurreletGUISchema::Struct(#name_str.to_owned(), v) + } + + // fn gui_to_livecode(&self, ux_val: murrelet_gui::MurreletGUISchema) -> murrelet_gui::MurreletGUISchemaResult { + + // #(#for_assign_vars,)* + + // Ok(#name(#(#for_gui_to_livecode,)*)) + // } + } + } + } + + fn make_enum_final( + idents: ParsedFieldIdent, + variants: Vec, + is_untagged: bool, + ) -> TokenStream2 { + let name = idents.name; + let name_str = name.to_string(); + + let for_make_gui = variants.iter().map(|x| x.for_make_gui.clone()); + // let for_gui_to_livecode = variants.iter().map(|x| x.for_gui_to_livecode.clone()); + + quote! { + impl murrelet_gui::CanMakeGUI for #name { + fn make_gui() -> murrelet_gui::MurreletGUISchema { + murrelet_gui::MurreletGUISchema::Enum(#name_str.to_owned(), vec![#(#for_make_gui,)*], #is_untagged) + } + + // fn gui_to_livecode(&self, gui_val: murrelet_gui::MurreletGUISchema) -> murrelet_gui::MurreletGUISchemaResult { + // if let Some(enum_name_and_val) = gui_val.as_enum() { + // let (enum_name, enum_val) = enum_name_and_val; + // match gui_val { + // #(#for_gui_to_livecode,)* + // } + // } + // } + } + } + } + + fn from_override_enum(func: &str) -> FieldTokensGUI { + let method: syn::Path = syn::parse_str(func).expect("Custom method is invalid path!"); + + let for_make_gui = quote! { + #method() + }; + + FieldTokensGUI { for_make_gui } + } + + fn from_newtype_struct(idents: StructIdents, _parent_ident: syn::Ident) -> FieldTokensGUI { + let ty = convert_vec_type(&idents.data.ty); + + let for_make_gui = quote! { + #ty::make_gui() + }; + + FieldTokensGUI { for_make_gui } + } + + // e.g. TileAxisLocs::V(TileAxisVs) + fn from_unnamed_enum(idents: EnumIdents) -> FieldTokensGUI { + let variant_ident = idents.data.ident; + let ty = convert_vec_type(&idents.data.fields.fields.first().unwrap().ty); + let variant_ident_str = variant_ident.to_string(); + + let for_make_gui = quote! { (murrelet_gui::MurreletEnumValGUI::Unnamed(#variant_ident_str.to_string(), #ty::make_gui())) }; + // let for_gui_to_livecode = quote! { murrelet_gui::MurreletEnumValGUI::Unnamed(#variant_ident_str, enum_val) => #name::#variant_ident(enum_val.gui_to_livecode()) }; + + FieldTokensGUI { + for_make_gui, + // for_gui_to_livecode, + // for_assign_vars: quote!(), + } + } + + // e.g. TileAxis::Diag + fn from_unit_enum(idents: EnumIdents) -> FieldTokensGUI { + let variant_ident = idents.data.ident; + let variant_ident_str = variant_ident.to_string(); + + let for_make_gui = + quote! { murrelet_gui::MurreletEnumValGUI::Unit(#variant_ident_str.to_owned()) }; + // let for_gui_to_livecode = + // quote! { murrelet_gui::Unit(#variant_ident_str) => #name::#variant_ident }; + + FieldTokensGUI { + for_make_gui, + // for_gui_to_livecode, + // for_assign_vars: quote!(), + } + } + + // s: String with reference + fn from_name(idents: StructIdents) -> FieldTokensGUI { + let field_name = idents.data.ident.unwrap().to_string(); + + let name_reference = idents + .data + .reference + .expect("from name called without a reference!"); + + let is_main = idents.data.is_ref_def.unwrap_or(false); + + let for_make_gui = quote! { v.push((#field_name.to_owned(), murrelet_gui::MurreletGUISchema::Val(murrelet_gui::ValueGUI::Name(#name_reference.to_owned(), #is_main)))) }; + + // let for_assign_vars + // let for_gui_to_livecode = quote! { murrelet_gui::ValueGUIResponse::Name(name) => name }; + + FieldTokensGUI { + for_make_gui, + // for_gui_to_livecode, + // for_assign_vars: , + } + } + + // skip + fn from_noop_struct(idents: StructIdents) -> FieldTokensGUI { + let field_name = idents.data.ident.unwrap().to_string(); + + let for_make_gui = + quote! { v.push((#field_name.to_owned(), murrelet_gui::MurreletGUISchema::Skip)) }; + // let for_gui_to_livecode = + // quote! { murrelet_gui::Unit(#variant_ident_str) => #name::#variant_ident }; + + FieldTokensGUI { + for_make_gui, + // for_gui_to_livecode, + } + } + + // f32, Vec2, etc + fn from_type_struct(idents: StructIdents) -> FieldTokensGUI { + let field_name = idents.data.ident.unwrap(); + let field_name_str = field_name.to_string(); + // to call a static function, we need to + let kind = convert_vec_type(&idents.data.ty); + + let is_flat = idents.data.flatten.unwrap_or(false); + + let for_make_gui = if is_flat { + quote! { v.extend(#kind::make_gui().unwrap_to_struct_fields().into_iter()) } + } else { + quote! { v.push((#field_name_str.to_owned(), #kind::make_gui())) } + }; + + FieldTokensGUI { for_make_gui } + } + + fn from_override_struct(idents: StructIdents, func: &str) -> FieldTokensGUI { + let field_name = idents.data.ident.unwrap(); + let field_name_str = field_name.to_string(); + + let method: syn::Path = syn::parse_str(func).expect("Custom method is invalid path!"); + + let for_make_gui = quote! { v.push((#field_name_str.to_owned(), #method())) }; + + FieldTokensGUI { for_make_gui } + } +} + +// we need to use turbofish to call an associated function +fn convert_vec_type(ty: &syn::Type) -> TokenStream2 { + if let syn::Type::Path(type_path) = ty { + if let Some(last_segment) = type_path.path.segments.last() { + if last_segment.ident == "Vec" { + if let syn::PathArguments::AngleBracketed(angle_bracketed) = &last_segment.arguments + { + if let Some(inner_arg) = angle_bracketed.args.first() { + return quote! { Vec:: < #inner_arg > }; + } + } + } + } + } + + quote! { #ty } +} diff --git a/murrelet_gui_derive/src/lib.rs b/murrelet_gui_derive/src/lib.rs new file mode 100644 index 0000000..37b4a86 --- /dev/null +++ b/murrelet_gui_derive/src/lib.rs @@ -0,0 +1,16 @@ +extern crate proc_macro; + +use darling::FromDeriveInput; +use derive_gui::FieldTokensGUI; +use parser::{GenFinal, LivecodeReceiver}; +use proc_macro::TokenStream; + +mod derive_gui; +mod parser; + +#[proc_macro_derive(MurreletGUI, attributes(murrelet_gui))] +pub fn murrelet_livecode_derive_murrelet_gui(input: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(input as syn::DeriveInput); + let ast_receiver = LivecodeReceiver::from_derive_input(&ast).unwrap(); + FieldTokensGUI::from_ast(ast_receiver).into() +} diff --git a/murrelet_gui_derive/src/parser.rs b/murrelet_gui_derive/src/parser.rs new file mode 100644 index 0000000..34dec9c --- /dev/null +++ b/murrelet_gui_derive/src/parser.rs @@ -0,0 +1,233 @@ +use darling::{ast, FromDeriveInput, FromField, FromVariant}; +use proc_macro2::TokenStream as TokenStream2; + +#[derive(Debug)] +pub(crate) struct ParsedFieldIdent { + pub(crate) name: syn::Ident, +} + +// trait and helpers needed to parse a variety of objects +pub(crate) trait GenFinal +where + Self: Sized, +{ + fn from_newtype_struct(_idents: StructIdents, parent_ident: syn::Ident) -> Self; + fn from_unnamed_enum(idents: EnumIdents) -> Self; + fn from_unit_enum(idents: EnumIdents) -> Self; + fn from_noop_struct(idents: StructIdents) -> Self; + fn from_name(idents: StructIdents) -> Self; + fn from_type_struct(idents: StructIdents) -> Self; + // fn from_recurse_struct_vec(idents: StructIdents) -> Self; + + fn from_ast(ast_receiver: LivecodeReceiver) -> TokenStream2 { + match ast_receiver.data { + ast::Data::Enum(_) => Self::make_enum(&ast_receiver), + ast::Data::Struct(ast::Fields { + style: ast::Style::Tuple, + .. + }) => Self::make_newtype(&ast_receiver), + ast::Data::Struct(_) => Self::make_struct(&ast_receiver), + } + } + fn from_override_struct(idents: StructIdents, func: &str) -> Self; + fn from_override_enum(func: &str) -> Self; + + fn make_enum_final( + idents: ParsedFieldIdent, + variants: Vec, + is_untagged: bool, + ) -> TokenStream2; + fn make_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2; + fn make_newtype_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2; + + fn make_struct(s: &LivecodeReceiver) -> TokenStream2 { + let name = s.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_struct {}", Self::classname(), name.to_string()); + + // shouldn't be calling this with something that's not a struct.. + let fields = s.data.clone().take_struct().unwrap(); + + let livecodable_fields = fields + .iter() + .map(|field| { + let idents = StructIdents { + data: field.clone(), + }; + + match field.how_to_control_this() { + HowToControlThis::Skip => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_noop_struct"); + Self::from_noop_struct(idents) + } + HowToControlThis::Name => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_name"); + Self::from_name(idents) + } + HowToControlThis::GUIType => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_type_struct"); + Self::from_type_struct(idents) + } + HowToControlThis::Override(func) => Self::from_override_struct(idents, &func), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_struct_final(idents, livecodable_fields) + } + + fn make_enum(e: &LivecodeReceiver) -> TokenStream2 { + let name = e.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_enum {}", Self::classname(), name.to_string()); + + let variants = e.data.clone().take_enum().unwrap(); + + // just go through and find ones that wrap around a type, and make sure those types are + let variants = variants + .iter() + .map(|variant| { + let ident = EnumIdents { + // enum_name: name.clone(), + data: variant.clone(), + }; + + match variant.fields.style { + ast::Style::Tuple => Self::from_unnamed_enum(ident), + ast::Style::Struct => panic!("enum named fields not supported yet"), + ast::Style::Unit => Self::from_unit_enum(ident), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + let is_untagged = if let Some(enum_tag) = &e.enum_tag { + enum_tag.as_str() == "external" + } else { + false + }; + + Self::make_enum_final(idents, variants, is_untagged) + } + + fn make_newtype(s: &LivecodeReceiver) -> TokenStream2 { + let name = s.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_newtype {}", Self::classname(), name.to_string()); + + // shouldn't be calling this with something that's not a struct.. + let fields = s.data.clone().take_struct().unwrap(); + + let livecodable_fields = fields + .iter() + .map(|field| { + let idents = StructIdents { + data: field.clone(), + }; + + match field.how_to_control_this() { + HowToControlThis::GUIType => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_newtype_struct"); + Self::from_newtype_struct(idents, name.clone()) + } + HowToControlThis::Skip => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_newtype_recurse_struct_vec"); + Self::from_noop_struct(idents) + } + HowToControlThis::Name => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_name"); + Self::from_name(idents) + } + HowToControlThis::Override(func) => Self::from_override_enum(&func), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_newtype_struct_final(idents, livecodable_fields) + } +} + +#[derive(Debug, FromField, Clone)] +#[darling(attributes(murrelet_gui))] +pub(crate) struct LivecodeFieldReceiver { + pub(crate) ident: Option, + pub(crate) ty: syn::Type, + pub(crate) kind: Option, + pub(crate) reference: Option, + pub(crate) is_ref_def: Option, + pub(crate) func: Option, + pub(crate) flatten: Option, +} +impl LivecodeFieldReceiver { + fn how_to_control_this(&self) -> HowToControlThis { + if let Some(kind_val) = &self.kind { + if kind_val == "skip" { + HowToControlThis::Skip + } else if kind_val == "reference" { + HowToControlThis::Name + } else { + panic!("unexpected kind") + } + } else if self.reference.is_some() { + HowToControlThis::Name + } else if let Some(func) = &self.func { + HowToControlThis::Override(func.to_owned()) + } else { + HowToControlThis::GUIType + } + } +} + +// for enums +#[derive(Debug, FromVariant, Clone)] +#[darling(attributes(murrelet_gui))] +pub(crate) struct LivecodeVariantReceiver { + pub(crate) ident: syn::Ident, + pub(crate) fields: ast::Fields, +} + +#[derive(Debug, Clone, FromDeriveInput)] +#[darling(attributes(murrelet_gui), supports(any))] +pub(crate) struct LivecodeReceiver { + ident: syn::Ident, + data: ast::Data, + enum_tag: Option, +} +impl LivecodeReceiver {} + +// represents an enum +pub(crate) struct EnumIdents { + // pub(crate) enum_name: syn::Ident, + pub(crate) data: LivecodeVariantReceiver, +} + +impl EnumIdents {} + +#[derive(Clone, Debug)] +pub struct StructIdents { + pub(crate) data: LivecodeFieldReceiver, +} +impl StructIdents {} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum HowToControlThis { + Skip, // just do the default values + GUIType, // build a gui for this type + // GUIVec, // GUI for a list + Name, // a referenced thing, + Override(String), +} diff --git a/murrelet_livecode/Cargo.toml b/murrelet_livecode/Cargo.toml index dc3af12..a9ce45d 100644 --- a/murrelet_livecode/Cargo.toml +++ b/murrelet_livecode/Cargo.toml @@ -11,19 +11,24 @@ license = "AGPL-3.0-or-later" schemars = ["dep:schemars"] [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } +murrelet_common = { workspace = true } +murrelet_gui = { workspace = true, features = ["glam"] } -lerpable = "0.0.2" +lerpable = "0.0.3" itertools = "0.10.5" regex = "1.7.3" uuid = "1.8.0" rand = "0.8" -glam = "0.28.0" +glam = { version = "0.28.0", features = ["serde"] } anyhow = "1.0.86" noise = "0.9.0" evalexpr = { version = "11.1.0", features = ["serde_support"] } serde = { version = "1.0.104", features = ["derive"] } serde_yaml = "0.9.17" +thiserror = "2.0.11" schemars = { version = "0.8.21", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true } diff --git a/murrelet_livecode/src/app_src.rs b/murrelet_livecode/src/app_src.rs index c46ac05..6c8fabe 100644 --- a/murrelet_livecode/src/app_src.rs +++ b/murrelet_livecode/src/app_src.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use glam::{vec2, Vec2}; -use murrelet_common::{IsLivecodeSrc, LivecodeSrcUpdateInput, LivecodeValue}; +use murrelet_common::{CustomVars, IsLivecodeSrc, LivecodeSrcUpdateInput, LivecodeValue}; // hacky, and maybe should include more keys or maybe it has too many, but this is quick to type (kDt) #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -81,6 +81,7 @@ pub struct AppInputValues { mouse_loc: Vec2, // doesn't need a click // can refactor. for now, this is a quick way to exclude, say, keyboard things from livecode web include_keyboard: bool, + custom_vars: CustomVars, } impl AppInputValues { // there's a nicer way to write this for keys... @@ -122,6 +123,7 @@ impl AppInputValues { ("w".to_owned(), LivecodeValue::Float(w as f64)), ("h".to_owned(), LivecodeValue::Float(h as f64)), ]); + r.extend(self.custom_vars.to_exec_funcs()); r } } @@ -143,6 +145,7 @@ impl IsLivecodeSrc for AppInputValues { let keys_cycle = self.keys_cycle; let keys_fired = self.keys_fire; + // i don't remember why i wrote it this way... self.result(has_click, cx, cy, mx, my, keys_cycle, keys_fired, w, h) } @@ -164,6 +167,7 @@ impl IsLivecodeSrc for AppInputValues { } self.mouse_loc = app.mouse_position; + // only update clicks if they are clicking! self.click_fire = false; self.click_changed = false; if app.mouse_left_is_down { @@ -173,6 +177,8 @@ impl IsLivecodeSrc for AppInputValues { self.click_changed = true; } + self.custom_vars.update(&src_input.app().custom_vars); + self.window_dims = app.window_dims; } } @@ -217,7 +223,7 @@ impl AppInputValues { pub fn key_cycle_bool(&self, key: MurreletKey) -> bool { // just need to check if this one's pressed right now if let Some(k) = self.lookup.get(&key) { - self.keys_cycle[*k] % 2 == 0 + self.keys_cycle[*k].is_multiple_of(2) } else { false } @@ -263,6 +269,7 @@ impl AppInputValues { mouse_loc: Vec2::ZERO, click_loc: Vec2::ZERO, include_keyboard, + custom_vars: CustomVars::default(), } } diff --git a/murrelet_livecode/src/cachedcompute.rs b/murrelet_livecode/src/cachedcompute.rs new file mode 100644 index 0000000..41fb6a4 --- /dev/null +++ b/murrelet_livecode/src/cachedcompute.rs @@ -0,0 +1,27 @@ +use std::cell::OnceCell; + +#[derive(Clone, Debug)] +pub struct CachedCompute(OnceCell); + +impl Default for CachedCompute { + fn default() -> Self { + Self::new() + } +} + +impl CachedCompute { + pub fn new() -> Self { + Self(OnceCell::new()) + } + + pub fn has_been_set(&self) -> bool { + self.0.get().is_some() + } + + pub fn get_or_init(&self, f: F) -> &T + where + F: FnOnce() -> T, + { + self.0.get_or_init(f) + } +} diff --git a/murrelet_livecode/src/expr.rs b/murrelet_livecode/src/expr.rs index fc470ce..323e877 100644 --- a/murrelet_livecode/src/expr.rs +++ b/murrelet_livecode/src/expr.rs @@ -1,5 +1,5 @@ #![allow(dead_code)] -use std::{f64::consts::PI, fmt::Debug}; +use std::{collections::HashMap, f64::consts::PI, fmt::Debug, sync::Arc}; use evalexpr::*; use glam::{vec2, Vec2}; @@ -9,6 +9,7 @@ use noise::{NoiseFn, Perlin}; use rand::{rngs::StdRng, Rng, SeedableRng}; use crate::types::{AdditionalContextNode, LivecodeError, LivecodeResult}; +use regex::Regex; pub fn init_evalexpr_func_ctx() -> LivecodeResult { context_map!{ @@ -27,6 +28,16 @@ pub fn init_evalexpr_func_ctx() -> LivecodeResult { } Ok(Value::Empty) }), + "p" => Function::new(move |argument| { + if let Ok(a) = argument.as_float() { + println!("{:?} (float)", a); + Ok(Value::Float(a)) + } else { + let a = argument.as_int()?; + println!("{:?} (int)", a); + Ok(Value::Int(a)) + } + }), "manymod" => Function::new(move |argument| { let a = argument.as_tuple()?; @@ -42,6 +53,12 @@ pub fn init_evalexpr_func_ctx() -> LivecodeResult { } Ok(Value::Int(result)) }), + "trigger" => Function::new(move |argument| { + let tuple = argument.as_fixed_len_tuple(3)?; + let (val, last_val, rate) = (tuple[0].as_number()?, tuple[1].as_number()?, tuple[2].as_number()?); + let f = (val / rate).floor() > (last_val / rate).floor(); + Ok(Value::Boolean(f)) + }), "clamp" => Function::new(move |argument| { let tuple = argument.as_fixed_len_tuple(3)?; @@ -92,6 +109,17 @@ pub fn init_evalexpr_func_ctx() -> LivecodeResult { let f = 1.0 - (src * 2.0 - 1.0).abs(); Ok(Value::Float(f)) }), + // l2 version of this, use power instead! + "tri2" => Function::new(|argument| { + let src = argument.as_number()?; + let f = 1.0 - (src * 2.0 - 1.0).powi(2); + Ok(Value::Float(f)) + }), + "smooth" => Function::new(|argument| { + let t = argument.as_number()?; + let f = smoothstep(t, 0.0, 1.0); + Ok(Value::Float(f)) + }), // bounce(t, 0.25) "bounce" => Function::new(|argument| { let (src, mult, offset) = match argument.as_fixed_len_tuple(3) { @@ -128,6 +156,7 @@ pub fn init_evalexpr_func_ctx() -> LivecodeResult { let f = smoothstep(t, edge0, edge1); Ok(Value::Float(f)) }), + "step" => Function::new(move |argument| { let tuple = argument.as_fixed_len_tuple(2)?; let (src, val) = (tuple[0].as_number()?, tuple[1].as_number()?); @@ -196,6 +225,29 @@ pub fn init_evalexpr_func_ctx() -> LivecodeResult { let f = (PI * 2.0 * (w * t + phase)).sin(); Ok(Value::Float(f)) }), + "sinpos" => Function::new(move |argument| { + let (t, w, phase) = match argument.as_fixed_len_tuple(3) { + Ok(tuple) => (tuple[0].as_number()?, tuple[1].as_number()?, tuple[2].as_number()?), + Err(_) => { + match argument.as_fixed_len_tuple(2) { + Ok(tuple) => (tuple[0].as_number()?, tuple[1].as_number()?, 0.0), + Err(_) => { + (argument.as_float()?, 1.0, 0.0) + }, + } + } + }; + let f = 0.5 + 0.5 * (PI * 2.0 * (w * t + phase)).sin(); + Ok(Value::Float(f)) + }), + "quantize" => Function::new(move |argument| { + let tuple = argument.as_fixed_len_tuple(2)?; + let (x, y) = (tuple[0].as_number()?, tuple[1].as_number()?); + + let p = (x * y).floor() / y; + Ok(Value::Float(p)) + }), + "cos" => Function::new(move |argument| { let (t, w, phase) = match argument.as_fixed_len_tuple(3) { Ok(tuple) => (tuple[0].as_number()?, tuple[1].as_number()?, tuple[2].as_number()?), @@ -221,11 +273,28 @@ pub fn init_evalexpr_func_ctx() -> LivecodeResult { ); let f = aa * (m * PI * x / a).cos() * (n * PI * y / a).cos() - bb * (n * PI * x / b).cos() * (m * PI * y / b).cos(); Ok(Value::Float(f)) + }), + "len" => Function::new(move |argument| { + let tuple = argument.as_fixed_len_tuple(2)?; + let (x1, y1) = ( + tuple[0].as_number()?, tuple[1].as_number()?, + ); + let f = vec2(x1 as f32, y1 as f32).length(); + Ok(Value::Float(f as f64)) + }), + "dist" => Function::new(move |argument| { + let tuple = argument.as_fixed_len_tuple(4)?; + let (x1, y1, x2, y2) = ( + tuple[0].as_number()?, tuple[1].as_number()?, + tuple[2].as_number()?, tuple[3].as_number()?, + ); + let f = vec2(x1 as f32, y1 as f32).distance(vec2(x2 as f32, y2 as f32)); + Ok(Value::Float(f as f64)) }) }.map_err(|err| {LivecodeError::EvalExpr("error in init_evalexpr_func_ctx!".to_string(), err)}) } -fn lc_val_to_expr(v: &LivecodeValue) -> Value { +pub fn lc_val_to_expr(v: &LivecodeValue) -> Value { match v { LivecodeValue::Float(f) => Value::Float(*f), LivecodeValue::Bool(f) => Value::Boolean(*f), @@ -235,10 +304,14 @@ fn lc_val_to_expr(v: &LivecodeValue) -> Value { // simple mapping of values #[derive(Debug, Clone)] -pub struct ExprWorldContextValues(Vec<(String, LivecodeValue)>); +pub struct ExprWorldContextValues(HashMap); impl ExprWorldContextValues { pub fn new(v: Vec<(String, LivecodeValue)>) -> Self { - Self(v) + Self(v.into_iter().collect()) + } + + pub fn empty() -> Self { + Self::new(vec![]) } pub fn update_ctx(&self, ctx: &mut HashMapContext) -> LivecodeResult<()> { @@ -253,7 +326,20 @@ impl ExprWorldContextValues { } pub fn set_val(&mut self, name: &str, val: LivecodeValue) { - self.0.push((name.to_owned(), val)) + if self.0.contains_key(name) { + let re = Regex::new(r"^(.*?)(\d+)$").unwrap(); + let new_name = if let Some(caps) = re.captures(name) { + let base = caps.get(1).map_or("", |m| m.as_str()); + let num = caps.get(2).map_or("0", |m| m.as_str()); + let num: u32 = num.parse().unwrap_or(0); + format!("{}{}", base, num + 1) + } else { + format!("{}_0", name) + }; + self.0.insert(new_name, val); + } else { + self.0.insert(name.to_owned(), val); + } } pub fn new_from_idx(idx: IdxInRange) -> Self { @@ -261,6 +347,7 @@ impl ExprWorldContextValues { ("i".to_string(), LivecodeValue::Int(idx.i() as i64)), ("if".to_string(), LivecodeValue::Float(idx.i() as f64)), ("pct".to_string(), LivecodeValue::Float(idx.pct() as f64)), + ("x".to_string(), LivecodeValue::Float(idx.pct() as f64)), // just in case i use the wrong one ("total".to_string(), LivecodeValue::Int(idx.total() as i64)), ( "totalf".to_string(), @@ -285,9 +372,27 @@ impl ExprWorldContextValues { Self::new(new_vals) } - fn combine(&mut self, vals: ExprWorldContextValues) -> Self { + pub fn combine(&mut self, vals: ExprWorldContextValues) -> Self { // have the new ones added later, so they'll overwrite if there are duplicates... - Self::new([self.0.clone(), vals.0].concat()) + + let mut new = self.clone(); + + for (name, val) in vals.0 { + new.set_val(&name, val); + } + + new + } + + pub(crate) fn get_variable(&self, identifier: &str) -> Option<&LivecodeValue> { + self.0.get(identifier) + } + + pub(crate) fn to_vals(&self) -> Vec<(String, Value)> { + self.0 + .iter() + .map(|(k, v)| (k.clone(), lc_val_to_expr(v))) + .collect_vec() } } @@ -300,7 +405,7 @@ impl IntoExprWorldContext for Vec<(String, f32)> { let v = self .iter() .map(|(s, x)| (s.to_owned(), LivecodeValue::Float(*x as f64))) - .collect_vec(); + .collect(); ExprWorldContextValues(v) } } @@ -357,6 +462,64 @@ impl GuideType { } } +pub trait ToMixedDefs { + fn to_mixed_def(&self) -> MixedEvalDefsRef; +} + +impl ToMixedDefs for ExprWorldContextValues { + fn to_mixed_def(&self) -> MixedEvalDefsRef { + MixedEvalDefs::new_from_expr(self.clone()).to_mixed_def() + } +} + +impl ToMixedDefs for Arc { + fn to_mixed_def(&self) -> MixedEvalDefsRef { + MixedEvalDefsRef(self.clone()) + } +} + +impl ToMixedDefs for MixedEvalDefs { + fn to_mixed_def(&self) -> MixedEvalDefsRef { + MixedEvalDefsRef::new(self.clone()) + } +} + +impl ToMixedDefs for (&str, LivecodeValue) { + fn to_mixed_def(&self) -> MixedEvalDefsRef { + MixedEvalDefs::new_simple(self.0, self.1).to_mixed_def() + } +} + +#[derive(Debug, Clone)] +pub struct MixedEvalDefsRef(Arc); +impl MixedEvalDefsRef { + pub fn new(m: MixedEvalDefs) -> Self { + MixedEvalDefsRef(Arc::new(m)) + } + + pub fn from_ctx_node(x: AdditionalContextNode) -> Self { + let mut m = MixedEvalDefs::new(); + m.add_node(x); + Self::new(m) + } + + pub fn update_ctx(&self, ctx: &mut HashMapContext) -> LivecodeResult<()> { + self.0.update_ctx(ctx) + } + + pub(crate) fn new_from_expr(more_vals: ExprWorldContextValues) -> MixedEvalDefsRef { + Self::new(MixedEvalDefs::new_from_expr(more_vals)) + } + + pub(crate) fn expr_vals(&self) -> &ExprWorldContextValues { + self.0.expr_vals() + } + + pub fn as_defs(&self) -> &MixedEvalDefs { + self.0.as_ref() + } +} + #[derive(Debug, Clone)] pub struct MixedEvalDefs { vals: ExprWorldContextValues, @@ -421,4 +584,26 @@ impl MixedEvalDefs { c.set_val(name, val); c } + + pub fn from_idx(idx: IdxInRange) -> Self { + Self::new_from_expr(ExprWorldContextValues::new_from_idx(idx)) + } + + pub(crate) fn expr_vals(&self) -> &ExprWorldContextValues { + &self.vals + } + + pub fn add_idx(&mut self, i: IdxInRange, prefix: &str) { + self.set_vals(ExprWorldContextValues::new_from_idx(i).with_prefix(prefix)); + } + + pub fn with_idx(mut self, i: IdxInRange, prefix: &str) -> MixedEvalDefs { + self.set_vals(ExprWorldContextValues::new_from_idx(i).with_prefix(prefix)); + self + } + + pub fn with_val(mut self, prefix: &str, val: LivecodeValue) -> MixedEvalDefs { + self.set_val(prefix, val); + self + } } diff --git a/murrelet_livecode/src/lazy.rs b/murrelet_livecode/src/lazy.rs index 2442ec0..7cb018b 100644 --- a/murrelet_livecode/src/lazy.rs +++ b/murrelet_livecode/src/lazy.rs @@ -1,15 +1,22 @@ -use evalexpr::{HashMapContext, IterateVariablesContext, Node}; -use itertools::Itertools; -use lerpable::{step, Lerpable}; -use murrelet_common::{IdxInRange, MurreletColor}; -use serde::Deserialize; +use std::sync::Arc; use crate::{ - expr::{ExprWorldContextValues, MixedEvalDefs}, - livecode::{GetLivecodeIdentifiers, LivecodeFromWorld, LivecodeFunction, LivecodeVariable}, - state::LivecodeWorldState, + expr::{ExprWorldContextValues, MixedEvalDefs, ToMixedDefs}, + livecode::{ + GetLivecodeIdentifiers, LivecodeFromWorld, LivecodeFunction, LivecodeToControl, + LivecodeVariable, + }, + nestedit::{NestEditable, NestedMod}, + state::{LivecodeWorldState, WorldWithLocalVariables}, types::{LivecodeError, LivecodeResult}, }; +use evalexpr::Node; + +use itertools::Itertools; +use lerpable::IsLerpingMethod; +use lerpable::{step, Lerpable}; +use murrelet_common::{IdxInRange, LivecodeValue, MurreletColor}; +use serde::Deserialize; #[derive(Debug, Deserialize, Clone)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] @@ -23,10 +30,16 @@ pub enum ControlLazyNodeF32 { } impl ControlLazyNodeF32 { + pub const ZERO: Self = ControlLazyNodeF32::Float(0.0); + pub fn new(n: Node) -> Self { Self::Expr(n) } + pub fn new_f32(n: f32) -> Self { + Self::Float(n) + } + fn result(&self) -> Result { match self { ControlLazyNodeF32::Int(d) => Ok(*d as f32), @@ -72,46 +85,46 @@ impl GetLivecodeIdentifiers for ControlLazyNodeF32 { // todo, figure out how to only build this context once per unitcell/etc #[derive(Debug, Clone)] pub struct LazyNodeF32Inner { - n: Node, // what will be evaluated! - world: LivecodeWorldState, // (could be a reference...) - more_defs: MixedEvalDefs, + n: Arc, // what will be evaluated! + world: WorldWithLocalVariables, // this is a reference :D } impl LazyNodeF32Inner { pub fn new(n: Node, world: LivecodeWorldState) -> Self { Self { - n, - world, - more_defs: MixedEvalDefs::new(), + n: Arc::new(n), + world: world.to_local(), } } // options to add more details... - pub fn add_more_defs(&self, more_defs: &MixedEvalDefs) -> Self { - let mut c = self.clone(); - c.more_defs = c.more_defs.combine(more_defs); - c + pub fn add_more_defs(&self, more_defs: &M) -> Self { + let c = self.clone(); + c.add_expr_values(more_defs.to_mixed_def().expr_vals()) } - pub fn add_expr_values(&self, more_vals: ExprWorldContextValues) -> Self { + pub fn add_expr_values(&self, more_vals: &ExprWorldContextValues) -> Self { let mut c = self.clone(); - c.more_defs.set_vals(more_vals); + c.world.update_with_simple_defs(more_vals); c } // internal function to build the ctx - fn build_ctx(&self) -> LivecodeResult { - let mut ctx = self.world.ctx().clone(); - self.more_defs.update_ctx(&mut ctx)?; - Ok(ctx) + fn build_ctx(&self) -> &WorldWithLocalVariables { + &self.world } // what you'll use pub fn eval(&self) -> LivecodeResult { - let ctx = self.build_ctx()?; + let ctx = self.build_ctx(); self.n - .eval_float_with_context(&ctx) - .or_else(|_| self.n.eval_int_with_context(&ctx).map(|x| x as f64)) + .eval_float_with_context(ctx) + .or_else(|_| self.n.eval_int_with_context(ctx).map(|x| x as f64)) + .or_else(|_| { + self.n + .eval_boolean_with_context(ctx) + .map(|x| if x { 1.0 } else { -1.0 }) + }) .map(|x| x as f32) .map_err(|err| LivecodeError::EvalExpr("error evaluating lazy".to_string(), err)) } @@ -129,13 +142,18 @@ pub enum LazyNodeF32 { impl LazyNodeF32 { pub fn new(def: ControlLazyNodeF32, world: &LivecodeWorldState) -> Self { match def { - ControlLazyNodeF32::Expr(n) => { - Self::Node(LazyNodeF32Inner::new(n, world.clone_to_lazy())) - } + ControlLazyNodeF32::Expr(n) => Self::Node(LazyNodeF32Inner::new(n, world.clone())), _ => Self::NoCtxNode(def), } } + pub fn simple_number(val: f32) -> Self { + Self::new( + ControlLazyNodeF32::Float(val), + &LivecodeWorldState::new_dummy(), + ) + } + pub fn n(&self) -> Option<&Node> { match self { LazyNodeF32::Uninitialized => None, @@ -144,7 +162,7 @@ impl LazyNodeF32 { } } - pub fn eval_with_ctx(&self, more_defs: &MixedEvalDefs) -> LivecodeResult { + pub fn eval_with_ctx(&self, more_defs: &M) -> LivecodeResult { // update ctx let with_more_ctx = self.add_more_defs(more_defs)?; @@ -157,7 +175,7 @@ impl LazyNodeF32 { } } - pub fn add_more_defs(&self, more_defs: &MixedEvalDefs) -> LivecodeResult { + pub fn add_more_defs(&self, more_defs: &M) -> LivecodeResult { match self { LazyNodeF32::Uninitialized => { Err(LivecodeError::Raw("uninitialized lazy node".to_owned())) @@ -176,7 +194,7 @@ impl LazyNodeF32 { LazyNodeF32::Node(v) => { let vals = ExprWorldContextValues::new_from_idx(idx).with_prefix(&format!("{}_", prefix)); - v.add_expr_values(vals).eval() + v.add_expr_values(&vals).eval() } LazyNodeF32::NoCtxNode(v) => v.result(), } @@ -196,10 +214,19 @@ impl LazyNodeF32 { pub fn variable_names(&self) -> LivecodeResult> { match self { LazyNodeF32::Uninitialized => Err(LivecodeError::Raw("not initialized".to_owned())), - LazyNodeF32::Node(c) => Ok(c.build_ctx()?.iter_variable_names().collect_vec()), + LazyNodeF32::Node(c) => Ok(c.build_ctx().variable_names()), LazyNodeF32::NoCtxNode(_) => Err(LivecodeError::Raw("no ctx".to_owned())), } } + + pub fn eval_with_xy(&self, xy: glam::Vec2) -> LivecodeResult { + let expr = ExprWorldContextValues::new(vec![ + ("x".to_string(), LivecodeValue::float(xy.x)), + ("y".to_string(), LivecodeValue::float(xy.y)), + ]); + + self.eval_with_ctx(&expr) + } } impl Lerpable for LazyNodeF32 { @@ -210,17 +237,25 @@ impl Lerpable for LazyNodeF32 { pub trait IsLazy where - Self: Sized, + Self: Sized + Clone, { type Target; fn eval_lazy(&self, expr: &MixedEvalDefs) -> LivecodeResult; - fn eval_idx(&self, idx: IdxInRange, prefix: &str) -> LivecodeResult { - let vals = ExprWorldContextValues::new_from_idx(idx).with_prefix(&format!("{}_", prefix)); + fn with_more_defs(&self, more_defs: &MixedEvalDefs) -> LivecodeResult; + + // without a _, like unitcell.. + fn eval_idx_(&self, idx: IdxInRange, prefix: &str) -> LivecodeResult { + let vals = ExprWorldContextValues::new_from_idx(idx).with_prefix(prefix); self.eval_lazy(&MixedEvalDefs::new_from_expr(vals)) } + + // backwards compatible + fn eval_idx(&self, idx: IdxInRange, prefix: &str) -> LivecodeResult { + self.eval_idx_(idx, &format!("{}_", prefix)) + } } impl IsLazy for LazyNodeF32 { @@ -228,6 +263,26 @@ impl IsLazy for LazyNodeF32 { fn eval_lazy(&self, expr: &MixedEvalDefs) -> LivecodeResult { self.eval_with_ctx(expr) } + + fn with_more_defs(&self, more_defs: &MixedEvalDefs) -> LivecodeResult { + self.add_more_defs(more_defs) + } +} + +impl IsLazy for Vec +where + Source: IsLazy, +{ + type Target = Vec; + fn eval_lazy(&self, expr: &MixedEvalDefs) -> LivecodeResult> { + self.iter().map(|x| x.eval_lazy(expr)).collect() + } + + fn with_more_defs(&self, more_defs: &MixedEvalDefs) -> LivecodeResult { + self.iter() + .map(|item| item.with_more_defs(more_defs)) + .collect::>>() + } } impl crate::unitcells::UnitCellCreator for T @@ -240,25 +295,239 @@ where } } -pub fn eval_lazy_color(v: &[LazyNodeF32], ctx: &MixedEvalDefs) -> LivecodeResult { - Ok(murrelet_common::MurreletColor::hsva( - v[0].eval_lazy(ctx)?, - v[1].eval_lazy(ctx)?, - v[2].eval_lazy(ctx)?, - v[3].eval_lazy(ctx)?, - )) +#[derive(Clone, Debug, Default)] +pub struct LazyVec2 { + x: LazyNodeF32, + y: LazyNodeF32, +} + +impl LazyVec2 { + pub fn new(x: LazyNodeF32, y: LazyNodeF32) -> Self { + Self { x, y } + } +} + +impl NestEditable for LazyVec2 { + fn nest_update(&self, _mods: NestedMod) -> Self { + self.clone() // noop + } + + fn nest_get(&self, _getter: &[&str]) -> LivecodeResult { + Err(LivecodeError::NestGetExtra("LazyNodeF32".to_owned())) // maybe in the future! + } +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct ControlLazyVec2(Vec); + +impl ControlLazyVec2 { + pub fn new(x: ControlLazyNodeF32, y: ControlLazyNodeF32) -> Self { + Self(vec![x, y]) + } +} +impl LivecodeFromWorld for ControlLazyVec2 { + fn o(&self, w: &LivecodeWorldState) -> LivecodeResult { + Ok(LazyVec2::new(self.0[0].o(w)?, self.0[1].o(w)?)) + } +} + +impl GetLivecodeIdentifiers for ControlLazyVec2 { + fn variable_identifiers(&self) -> Vec { + self.0 + .iter() + .flat_map(|f| f.variable_identifiers()) + .collect_vec() + } + + fn function_identifiers(&self) -> Vec { + self.0 + .iter() + .flat_map(|f| f.function_identifiers()) + .collect_vec() + } +} + +impl LivecodeToControl for LazyVec2 { + fn to_control(&self) -> ControlLazyVec2 { + ControlLazyVec2(vec![self.x.to_control(), self.y.to_control()]) + } +} + +impl IsLazy for LazyVec2 { + type Target = glam::Vec2; + + fn eval_lazy(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(glam::vec2(self.x.eval_lazy(ctx)?, self.y.eval_lazy(ctx)?)) + } + + fn with_more_defs(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(LazyVec2::new( + self.x.with_more_defs(ctx)?, + self.y.with_more_defs(ctx)?, + )) + } +} + +#[derive(Clone, Debug, Default)] +pub struct LazyVec3 { + x: LazyNodeF32, + y: LazyNodeF32, + z: LazyNodeF32, +} + +impl LazyVec3 { + pub fn new(x: LazyNodeF32, y: LazyNodeF32, z: LazyNodeF32) -> Self { + Self { x, y, z } + } +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct ControlLazyVec3(Vec); +impl LivecodeFromWorld for ControlLazyVec3 { + fn o(&self, w: &LivecodeWorldState) -> LivecodeResult { + Ok(LazyVec3::new( + self.0[0].o(w)?, + self.0[1].o(w)?, + self.0[2].o(w)?, + )) + } +} + +impl GetLivecodeIdentifiers for ControlLazyVec3 { + fn variable_identifiers(&self) -> Vec { + self.0 + .iter() + .flat_map(|f| f.variable_identifiers()) + .collect_vec() + } + + fn function_identifiers(&self) -> Vec { + self.0 + .iter() + .flat_map(|f| f.function_identifiers()) + .collect_vec() + } +} + +impl LivecodeToControl for LazyVec3 { + fn to_control(&self) -> ControlLazyVec3 { + ControlLazyVec3(vec![ + self.x.to_control(), + self.y.to_control(), + self.z.to_control(), + ]) + } +} + +impl IsLazy for LazyVec3 { + type Target = glam::Vec3; + + fn eval_lazy(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(glam::vec3( + self.x.eval_lazy(ctx)?, + self.y.eval_lazy(ctx)?, + self.z.eval_lazy(ctx)?, + )) + } + + fn with_more_defs(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(LazyVec3::new( + self.x.with_more_defs(ctx)?, + self.y.with_more_defs(ctx)?, + self.z.with_more_defs(ctx)?, + )) + } +} + +#[derive(Clone, Debug, Default)] +pub struct LazyMurreletColor { + h: LazyNodeF32, + s: LazyNodeF32, + v: LazyNodeF32, + a: LazyNodeF32, +} + +impl LazyMurreletColor { + pub fn new(h: LazyNodeF32, s: LazyNodeF32, v: LazyNodeF32, a: LazyNodeF32) -> Self { + Self { h, s, v, a } + } +} + +#[derive(Clone, Debug, Default, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct ControlLazyMurreletColor(Vec); + +impl ControlLazyMurreletColor { + pub fn new_default(h: f32, s: f32, v: f32, a: f32) -> Self { + ControlLazyMurreletColor(vec![ + ControlLazyNodeF32::new_f32(h), + ControlLazyNodeF32::new_f32(s), + ControlLazyNodeF32::new_f32(v), + ControlLazyNodeF32::new_f32(a), + ]) + } +} + +impl LivecodeFromWorld for ControlLazyMurreletColor { + fn o(&self, w: &LivecodeWorldState) -> LivecodeResult { + Ok(LazyMurreletColor::new( + self.0[0].o(w)?, + self.0[1].o(w)?, + self.0[2].o(w)?, + self.0[3].o(w)?, + )) + } } -pub fn eval_lazy_vec3(v: &[LazyNodeF32], ctx: &MixedEvalDefs) -> LivecodeResult { - Ok(glam::vec3( - v[0].eval_lazy(ctx)?, - v[1].eval_lazy(ctx)?, - v[2].eval_lazy(ctx)?, - )) +impl GetLivecodeIdentifiers for ControlLazyMurreletColor { + fn variable_identifiers(&self) -> Vec { + self.0 + .iter() + .flat_map(|f| f.variable_identifiers()) + .collect_vec() + } + + fn function_identifiers(&self) -> Vec { + self.0 + .iter() + .flat_map(|f| f.function_identifiers()) + .collect_vec() + } } -pub fn eval_lazy_vec2(v: &[LazyNodeF32], ctx: &MixedEvalDefs) -> LivecodeResult { - Ok(glam::vec2(v[0].eval_lazy(ctx)?, v[1].eval_lazy(ctx)?)) +impl LivecodeToControl for LazyMurreletColor { + fn to_control(&self) -> ControlLazyMurreletColor { + ControlLazyMurreletColor(vec![ + self.h.to_control(), + self.s.to_control(), + self.v.to_control(), + self.a.to_control(), + ]) + } +} + +impl IsLazy for LazyMurreletColor { + type Target = MurreletColor; + + fn eval_lazy(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(MurreletColor::hsva( + self.h.eval_lazy(ctx)?, + self.s.eval_lazy(ctx)?, + self.v.eval_lazy(ctx)?, + self.a.eval_lazy(ctx)?, + )) + } + + fn with_more_defs(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(LazyMurreletColor::new( + self.h.with_more_defs(ctx)?, + self.s.with_more_defs(ctx)?, + self.v.with_more_defs(ctx)?, + self.a.with_more_defs(ctx)?, + )) + } } pub fn eval_lazy_f32( @@ -275,3 +544,105 @@ pub fn eval_lazy_f32( }; Ok(result) } + +// can lerp between lazy items, by gathering the pairs + pct, and then evaluating them + +#[derive(Clone, Debug)] +pub struct LazyLerp { + left: WrappedLazyType, + right: WrappedLazyType, + pct: f32, // hm, just convert to the pct... +} + +impl LazyLerp { + fn new(left: WrappedLazyType, right: WrappedLazyType, pct: f32) -> Self { + Self { left, right, pct } + } +} + +// newtype to avoid orphan +#[derive(Clone, Debug)] +pub enum WrappedLazyType { + Single(T), + Lerp(Box>), +} +impl WrappedLazyType +where + T: IsLazy + std::fmt::Debug + Clone, +{ + pub(crate) fn new(x: T) -> Self { + Self::Single(x) + } + + pub(crate) fn new_lerp(left: WrappedLazyType, right: WrappedLazyType, pct: f32) -> Self { + WrappedLazyType::Lerp(Box::new(LazyLerp::new(left, right, pct))) + } +} + +impl IsLazy for LazyLerp +where + T: IsLazy, + T::Target: Lerpable, +{ + type Target = T::Target; + fn eval_lazy(&self, expr: &MixedEvalDefs) -> LivecodeResult { + let left = self.left.eval_lazy(expr)?; + let right = self.right.eval_lazy(expr)?; + let r = left.lerpify(&right, &self.pct); + + Ok(r) + } + + fn with_more_defs(&self, more_defs: &MixedEvalDefs) -> LivecodeResult { + Ok(Self { + left: self.left.with_more_defs(more_defs)?, + right: self.right.with_more_defs(more_defs)?, + pct: self.pct, + }) + } +} + +impl IsLazy for WrappedLazyType +where + T: IsLazy, + T::Target: Lerpable, +{ + type Target = T::Target; + fn eval_lazy(&self, expr: &MixedEvalDefs) -> LivecodeResult { + match self { + WrappedLazyType::Single(s) => s.eval_lazy(expr), + WrappedLazyType::Lerp(s) => s.eval_lazy(expr), + } + } + + fn with_more_defs(&self, more_defs: &MixedEvalDefs) -> LivecodeResult { + Ok(match self { + WrappedLazyType::Single(s) => WrappedLazyType::Single(s.with_more_defs(more_defs)?), + WrappedLazyType::Lerp(s) => { + WrappedLazyType::Lerp(Box::new(s.with_more_defs(more_defs)?)) + } + }) + } +} + +impl Lerpable for WrappedLazyType +where + T: IsLazy + Clone + std::fmt::Debug, +{ + fn lerpify(&self, other: &Self, pct: &M) -> Self { + WrappedLazyType::new_lerp(self.clone(), other.clone(), pct.lerp_pct() as f32) + } +} + +impl LivecodeToControl for WrappedLazyType +where + T: LivecodeToControl + IsLazy, +{ + fn to_control(&self) -> ControlT { + match self { + WrappedLazyType::Single(inner) => inner.to_control(), + // hax because it's just to control... + WrappedLazyType::Lerp(lerp) => lerp.left.to_control(), + } + } +} diff --git a/murrelet_livecode/src/lib.rs b/murrelet_livecode/src/lib.rs index 326e80b..7c89077 100644 --- a/murrelet_livecode/src/lib.rs +++ b/murrelet_livecode/src/lib.rs @@ -1,5 +1,6 @@ pub mod app_src; pub mod boop; +pub mod cachedcompute; pub mod expr; pub mod lazy; pub mod livecode; diff --git a/murrelet_livecode/src/livecode.rs b/murrelet_livecode/src/livecode.rs index 406f254..98d3aae 100644 --- a/murrelet_livecode/src/livecode.rs +++ b/murrelet_livecode/src/livecode.rs @@ -10,6 +10,7 @@ use glam::Vec2; use glam::Vec3; use itertools::Itertools; use murrelet_common::clamp; +use murrelet_common::AnglePi; use murrelet_common::MurreletColor; use serde::Deserialize; @@ -29,6 +30,10 @@ pub fn empty_vec() -> Vec { pub trait LivecodeFromWorld { fn o(&self, w: &LivecodeWorldState) -> LivecodeResult; + + fn o_dummy(&self) -> LivecodeResult { + self.o(&LivecodeWorldState::new_dummy()) + } } impl LivecodeFromWorld for ControlF32 { @@ -61,6 +66,15 @@ impl LivecodeFromWorld for [ControlF32; 4] { } } +impl LivecodeFromWorld> for Vec +where + Source: LivecodeFromWorld, +{ + fn o(&self, w: &LivecodeWorldState) -> LivecodeResult> { + self.iter().map(|x| x.o(w)).collect::, _>>() + } +} + pub trait LivecodeToControl { fn to_control(&self) -> ControlT; } @@ -95,6 +109,12 @@ impl LivecodeToControl for bool { } } +impl LivecodeToControl for AnglePi { + fn to_control(&self) -> ControlF32 { + ControlF32::Raw(self._angle_pi()) + } +} + impl LivecodeToControl<[ControlF32; 2]> for Vec2 { fn to_control(&self) -> [ControlF32; 2] { [self.x.to_control(), self.y.to_control()] @@ -135,12 +155,31 @@ impl LivecodeToControl for u64 { } } +impl LivecodeToControl> for Vec +where + Source: LivecodeToControl, +{ + fn to_control(&self) -> Vec { + self.iter().map(|x| x.to_control()).collect_vec() + } +} + impl LivecodeToControl for LazyNodeF32 { fn to_control(&self) -> ControlLazyNodeF32 { ControlLazyNodeF32::new(self.n().cloned().unwrap()) } } +impl LivecodeToControl> for Option +where + T: LivecodeToControl + Clone, + ControlType: LivecodeFromWorld, +{ + fn to_control(&self) -> Option { + self.as_ref().map(|s| s.to_control()) + } +} + // wrappers around identifiers evalexpr gives us, right now // just to control midi controller #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -284,6 +323,23 @@ impl GetLivecodeIdentifiers for [ControlF32; 4] { } } +impl GetLivecodeIdentifiers for Vec +where + T: GetLivecodeIdentifiers, +{ + fn variable_identifiers(&self) -> Vec { + self.iter() + .flat_map(|x| x.variable_identifiers()) + .collect_vec() + } + + fn function_identifiers(&self) -> Vec { + self.iter() + .flat_map(|x| x.function_identifiers()) + .collect_vec() + } +} + impl GetLivecodeIdentifiers for String { fn variable_identifiers(&self) -> Vec { vec![] @@ -294,9 +350,10 @@ impl GetLivecodeIdentifiers for String { } } +// impl GetLivecodeIdentifiers for AdditionalContextNode { fn variable_identifiers(&self) -> Vec { - vec![] + self.vars() } fn function_identifiers(&self) -> Vec { @@ -314,6 +371,20 @@ impl GetLivecodeIdentifiers for HashMap { } } +impl GetLivecodeIdentifiers for Option { + fn variable_identifiers(&self) -> Vec { + self.as_ref() + .map(|x| x.variable_identifiers()) + .unwrap_or(vec![]) + } + + fn function_identifiers(&self) -> Vec { + self.as_ref() + .map(|x| x.function_identifiers()) + .unwrap_or(vec![]) + } +} + impl GetLivecodeIdentifiers for ControlBool { fn variable_identifiers(&self) -> Vec { match self { @@ -440,6 +511,8 @@ pub enum ControlF32 { } impl ControlF32 { + pub const ZERO: ControlF32 = ControlF32::Float(0.0); + // for backwards compatibility #[allow(non_snake_case)] pub fn Raw(v: f32) -> ControlF32 { @@ -457,6 +530,8 @@ impl ControlF32 { } pub fn _o(&self, w: &LivecodeWorldState) -> LivecodeResult { + let a = w.ctx()?; + let ctx = a.as_ref(); match self { ControlF32::Bool(b) => { if *b { @@ -467,15 +542,15 @@ impl ControlF32 { } ControlF32::Int(i) => Ok(*i as f32), ControlF32::Float(x) => Ok(*x), - ControlF32::Expr(e) => match e.eval_float_with_context(w.ctx()).map(|x| x as f32) { - Ok(r) => Ok(r), - Err(_) => { - let b = e - .eval_boolean_with_context(w.ctx()) - .map_err(|err| LivecodeError::EvalExpr("evalexpr err".to_string(), err)); - Ok(if b? { 1.0 } else { -1.0 }) - } - }, + ControlF32::Expr(e) => e + .eval_float_with_context(ctx) + .map(|x| x as f32) + .or_else(|_| e.eval_int_with_context(ctx).map(|b| b as f32)) + .or_else(|_| { + e.eval_boolean_with_context(ctx) + .map(|b| if b { 1.0 } else { -1.0 }) + .map_err(|err| LivecodeError::EvalExpr("evalexpr err".to_string(), err)) + }), } } } @@ -497,15 +572,6 @@ pub enum ControlBool { Expr(Node), } impl ControlBool { - // pub fn to_unitcell_control(&self) -> UnitCellControlExprBool { - // match self { - // ControlBool::Raw(x) => UnitCellControlExprBool::Bool(*x), - // ControlBool::Int(x) => UnitCellControlExprBool::Int(*x), - // ControlBool::Float(x) => UnitCellControlExprBool::Float(*x), - // ControlBool::Expr(x) => UnitCellControlExprBool::Expr(x.clone()), - // } - // } - pub fn force_from_str(s: &str) -> ControlBool { match build_operator_tree(s) { Ok(e) => Self::Expr(e), @@ -517,21 +583,22 @@ impl ControlBool { } pub fn o(&self, w: &LivecodeWorldState) -> LivecodeResult { - // self.to_unitcell_control().eval(w) + let a = w.ctx()?; + let ctx = a.as_ref(); match self { ControlBool::Raw(b) => Ok(*b), ControlBool::Int(i) => Ok(*i > 0), ControlBool::Float(x) => Ok(*x > 0.0), - ControlBool::Expr(e) => match e.eval_boolean_with_context(w.ctx()) { - Ok(r) => Ok(r), - Err(_) => { - let b = e.eval_float_with_context(w.ctx()).map_err(|err| { - LivecodeError::EvalExpr("error evaluing bool".to_string(), err) - }); - b.map(|x| x > 0.0) - } - }, + + ControlBool::Expr(e) => e + .eval_boolean_with_context(ctx) + .or_else(|_| e.eval_float_with_context(ctx).map(|b| b > 0.0)) + .or_else(|_| { + e.eval_int_with_context(ctx) + .map(|b| b > 0) + .map_err(|err| LivecodeError::EvalExpr("evalexpr err".to_string(), err)) + }), } } diff --git a/murrelet_livecode/src/nestedit.rs b/murrelet_livecode/src/nestedit.rs index 6d78188..92a362f 100644 --- a/murrelet_livecode/src/nestedit.rs +++ b/murrelet_livecode/src/nestedit.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use evalexpr::Node; use glam::{vec2, vec3, Vec2, Vec3}; -use murrelet_common::MurreletColor; +use murrelet_common::{AnglePi, MurreletColor}; use crate::lazy::LazyNodeF32; use crate::types::{AdditionalContextNode, LivecodeError, LivecodeResult}; @@ -135,6 +135,20 @@ impl NestEditable for i32 { } } +impl NestEditable for AnglePi { + fn nest_update(&self, mods: NestedMod) -> Self { + AnglePi::new( + mods.get_curr_as_f32() + .map(|x| x) + .unwrap_or(self._angle_pi()), + ) + } + + fn nest_get(&self, getter: &[&str]) -> LivecodeResult { + nest_default(getter, format!("{}", self._angle_pi())) + } +} + impl NestEditable for Vec2 { fn nest_update(&self, mods: NestedMod) -> Self { let x = mods.get_subfield_as_f32("x").unwrap_or(self.x); @@ -288,6 +302,16 @@ impl NestEditable for Vec { } } +impl NestEditable for Option { + fn nest_update(&self, _mods: NestedMod) -> Self { + self.clone() // noop + } + + fn nest_get(&self, _getter: &[&str]) -> LivecodeResult { + Err(LivecodeError::NestGetExtra("Option".to_owned())) // maybe in the future! + } +} + impl NestEditable for HashMap { fn nest_update(&self, _mods: NestedMod) -> Self { self.clone() // noop diff --git a/murrelet_livecode/src/state.rs b/murrelet_livecode/src/state.rs index 824343f..da70071 100644 --- a/murrelet_livecode/src/state.rs +++ b/murrelet_livecode/src/state.rs @@ -1,8 +1,16 @@ -use evalexpr::HashMapContext; +use std::{ + collections::HashSet, + sync::{Arc, RwLock}, +}; + +use evalexpr::{Context, EvalexprResult, HashMapContext, IterateVariablesContext, Value}; use murrelet_common::*; use crate::{ - expr::{ExprWorldContextValues, IntoExprWorldContext, MixedEvalDefs}, + expr::{ + init_evalexpr_func_ctx, ExprWorldContextValues, IntoExprWorldContext, MixedEvalDefs, + MixedEvalDefsRef, + }, types::{AdditionalContextNode, LivecodeResult}, unitcells::UnitCellContext, }; @@ -11,23 +19,257 @@ use crate::{ enum LivecodeWorldStateStage { Timeless, World(LiveCodeTimeInstantInfo), - Unit(LiveCodeTimeInstantInfo), - Lazy(LiveCodeTimeInstantInfo), + // Unit(LiveCodeTimeInstantInfo), + // Lazy(LiveCodeTimeInstantInfo), +} +// impl LivecodeWorldStateStage { +// fn add_step(&self, stage: LivecodeWorldStateStage) -> LivecodeWorldStateStage { +// // todo, i could start to represent the tree of steps.. but right now, just do the latest one +// stage +// } +// } + +#[derive(Clone, Debug)] +pub enum CacheFlag { + NotCached, + Cached, +} + +#[derive(Clone, Debug)] +pub struct CachedHM { + flag: CacheFlag, + data: Arc, // we keep this around so we don't need to drop } -impl LivecodeWorldStateStage { - fn add_step(&self, stage: LivecodeWorldStateStage) -> LivecodeWorldStateStage { - // todo, i could start to represent the tree of steps.. but right now, just do the latest one - stage +impl CachedHM { + fn new_rw() -> Arc> { + let hm = HashMapContext::new(); + Arc::new(RwLock::new(CachedHM { + data: Arc::new(hm), + flag: CacheFlag::NotCached, + })) + } + + fn clear(&mut self) { + self.flag = CacheFlag::NotCached; + } + + fn cached(&self) -> CacheResult { + match self.flag { + CacheFlag::NotCached => CacheResult::NotCached, + CacheFlag::Cached => CacheResult::Cached(self.data.clone()), + } + } + + fn update_arc(&mut self, hm: Arc) { + self.data = hm; + self.flag = CacheFlag::Cached; } } -#[derive(Debug, Clone)] +enum CacheResult { + Cached(Arc), + NotCached, +} + +#[derive(Clone, Debug)] pub struct LivecodeWorldState { + cached: Arc>, + state: Arc, + refs: Vec, +} +impl LivecodeWorldState { + pub fn new_legacy(state: LivecodeWorldStateInner) -> LivecodeResult { + Ok(Self { + cached: CachedHM::new_rw(), + state: Arc::new(state), + refs: vec![], + }) + } + + pub fn new( + evalexpr_func_ctx: &HashMapContext, + livecode_src: &LivecodeSrc, + time: LiveCodeTimeInstantInfo, + node: AdditionalContextNode, + assets: AssetsRef, + ) -> LivecodeResult { + let state = + LivecodeWorldStateInner::new(evalexpr_func_ctx, livecode_src, time, node, assets)?; + + Self::new_legacy(state) + } + + pub fn new_timeless( + evalexpr_func_ctx: &HashMapContext, + livecode_src: &LivecodeSrc, + ) -> LivecodeResult { + let state = LivecodeWorldStateInner::new_timeless(evalexpr_func_ctx, livecode_src)?; + Self::new_legacy(state) + } + + pub fn clone_with_vals(&self, expr: ExprWorldContextValues, prefix: &str) -> Self { + let e = expr.with_prefix(prefix); + let new_info = MixedEvalDefs::new_from_expr(e); + + let mut refs = self.refs.clone(); + refs.push(MixedEvalDefsRef::new(new_info)); + + Self { + cached: CachedHM::new_rw(), + state: self.state.clone(), + refs, + } + } + + pub fn clone_to_unitcell( + &self, + unit_cell_ctx: &UnitCellContext, + prefix: &str, + maybe_node: Option<&MixedEvalDefsRef>, + ) -> LivecodeResult { + let new_info = unit_cell_ctx + .as_expr_world_context_values() + .with_prefix(prefix); + + let mut refs = self.refs.clone(); + refs.push(MixedEvalDefsRef::new(MixedEvalDefs::new_from_expr( + new_info, + ))); + if let Some(node) = maybe_node { + refs.push(node.clone()); + } + + Ok(Self { + cached: CachedHM::new_rw(), + state: self.state.clone(), + refs, + }) + } + + pub(crate) fn new_dummy() -> Self { + Self::new_legacy(LivecodeWorldStateInner::new_dummy_with_funcs()).unwrap() + } + + pub(crate) fn ctx(&self) -> LivecodeResult> { + if let CacheResult::Cached(c) = self.cached.read().unwrap().cached() { + return Ok(c); + } + let mut cache = self.cached.write().unwrap(); + let mut ctx = self.state.ctx().clone(); + for mixed in &self.refs { + mixed.update_ctx(&mut ctx)?; + } + let arc = Arc::new(ctx); + cache.update_arc(arc.clone()); + + Ok(arc) + } + + pub fn actual_frame_u64(&self) -> u64 { + self.state.actual_frame_u64() + } + + pub fn vars(&self) -> HashSet { + self.state.vars() + } + + pub fn update_with_defs(&mut self, md: MixedEvalDefsRef) { + self.refs.push(md); + self.cached.write().unwrap().clear(); + } + + pub fn time(&self) -> LiveCodeTimeInstantInfo { + self.state.time() + } + + pub fn actual_frame(&self) -> f32 { + self.state.actual_frame() + } + + pub fn asset_layer(&self, key: &str, layer_idx: usize) -> Option> { + self.state.asset_layer(key, layer_idx) + } + + pub fn asset_layers_in_key(&self, key: &str) -> &[String] { + self.state.asset_layers_in_key(key) + } + + pub fn update_with_simple_defs( + &self, + more_vals: ExprWorldContextValues, + ) -> WorldWithLocalVariables { + let locals = more_vals.to_vals(); + + WorldWithLocalVariables { + base: self.ctx().unwrap(), + locals, + builtins_disabled: false, + } + } + + pub fn to_local(&self) -> WorldWithLocalVariables { + WorldWithLocalVariables { + base: self.ctx().unwrap(), + locals: vec![], + builtins_disabled: false, + } + } +} + +// some chatgpt help +#[derive(Debug, Clone)] +pub struct WorldWithLocalVariables { + base: Arc, + locals: Vec<(String, Value)>, + builtins_disabled: bool, +} +impl WorldWithLocalVariables { + pub fn update_with_simple_defs(&mut self, more_vals: &ExprWorldContextValues) { + let mut locals = more_vals.to_vals(); + locals.extend(self.locals.iter().cloned()); + self.locals = locals; + } + + pub(crate) fn variable_names(&self) -> Vec { + let mut names: Vec = self.locals.iter().map(|(k, _)| k.clone()).collect(); + names.extend(self.base.iter_variable_names()); + names + } +} + +impl Context for WorldWithLocalVariables { + fn get_value(&self, identifier: &str) -> Option<&Value> { + if let Some((_, v)) = self.locals.iter().find(|(k, _v)| k == identifier) { + return Some(v); + } + self.base.get_value(identifier) + } + + fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { + self.base.call_function(identifier, argument) + } + + fn are_builtin_functions_disabled(&self) -> bool { + self.builtins_disabled + } + + fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> { + self.builtins_disabled = disabled; + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct LivecodeWorldStateInner { context: HashMapContext, stage: LivecodeWorldStateStage, assets: AssetsRef, } -impl LivecodeWorldState { +impl LivecodeWorldStateInner { + pub fn vars(&self) -> HashSet { + self.context.iter_variable_names().collect() + } + fn clone_ctx_and_add_world( evalexpr_func_ctx: &HashMapContext, livecode_src: &LivecodeSrc, @@ -52,17 +294,17 @@ impl LivecodeWorldState { Ok(ctx) } - pub fn new<'a>( + pub fn new( evalexpr_func_ctx: &HashMapContext, livecode_src: &LivecodeSrc, time: LiveCodeTimeInstantInfo, node: AdditionalContextNode, assets: AssetsRef, - ) -> LivecodeResult { + ) -> LivecodeResult { let context = Self::clone_ctx_and_add_world(evalexpr_func_ctx, livecode_src, Some(time), Some(node))?; - Ok(LivecodeWorldState { + Ok(Self { context, stage: LivecodeWorldStateStage::World(time), assets: assets.clone(), @@ -72,10 +314,10 @@ impl LivecodeWorldState { pub fn new_timeless( evalexpr_func_ctx: &HashMapContext, livecode_src: &LivecodeSrc, - ) -> LivecodeResult { + ) -> LivecodeResult { let context = Self::clone_ctx_and_add_world(evalexpr_func_ctx, livecode_src, None, None)?; - Ok(LivecodeWorldState { + Ok(Self { context, stage: LivecodeWorldStateStage::Timeless, assets: Assets::empty_ref(), @@ -93,8 +335,6 @@ impl LivecodeWorldState { match self.stage { LivecodeWorldStateStage::Timeless => panic!("checking time in a timeless world"), LivecodeWorldStateStage::World(t) => t, - LivecodeWorldStateStage::Unit(t) => t, - LivecodeWorldStateStage::Lazy(t) => t, } } @@ -122,51 +362,6 @@ impl LivecodeWorldState { more_defs.update_ctx(self.ctx_mut()) } - pub fn clone_with_vals( - &self, - expr: ExprWorldContextValues, - prefix: &str, - ) -> LivecodeResult { - let mut lazy = self.clone_to_lazy(); // eh just need to clone - - expr.with_prefix(prefix).update_ctx(lazy.ctx_mut())?; - - Ok(lazy) - } - - pub fn clone_to_unitcell( - &self, - unit_cell_ctx: &UnitCellContext, - prefix: &str, - ) -> LivecodeResult { - let mut context = self.context.clone(); - unit_cell_ctx - .as_expr_world_context_values() - .with_prefix(prefix) - .update_ctx(&mut context)?; - - let r = LivecodeWorldState { - context, - stage: self - .stage - .add_step(LivecodeWorldStateStage::Unit(self.time())), - assets: self.assets.clone(), - }; - - Ok(r) - } - - pub fn clone_to_lazy(&self) -> Self { - let context = self.context.clone(); - LivecodeWorldState { - context, - stage: self - .stage - .add_step(LivecodeWorldStateStage::Lazy(self.time())), - assets: self.assets.clone(), - } - } - pub fn asset_layer(&self, key: &str, layer_idx: usize) -> Option> { self.assets.asset_layer(key, layer_idx).cloned() } @@ -174,6 +369,29 @@ impl LivecodeWorldState { pub fn asset_layers_in_key(&self, key: &str) -> &[String] { self.assets.layer_for_key(key) } + + pub fn new_dummy_with_funcs() -> Self { + Self::new( + &init_evalexpr_func_ctx().unwrap(), + &LivecodeSrc::new(vec![]), + LiveCodeTimeInstantInfo::new_dummy(), + AdditionalContextNode::new_dummy(), + Arc::new(Assets::empty()), + ) + .unwrap() + } + + pub fn new_dummy() -> Self { + let empty_ctx = HashMapContext::new(); + Self::new( + &empty_ctx, + &LivecodeSrc::new(vec![]), + LiveCodeTimeInstantInfo::new_dummy(), + AdditionalContextNode::new_dummy(), + Arc::new(Assets::empty()), + ) + .unwrap() + } } #[derive(Copy, Clone, Debug)] @@ -313,6 +531,18 @@ impl LiveCodeTimeInstantInfo { self.seconds_since_updated_frame() } } + + fn new_dummy() -> LiveCodeTimeInstantInfo { + LiveCodeTimeInstantInfo { + timing_config: LivecodeTimingConfig { + bpm: 120.0, + fps: 60.0, + realtime: false, + beats_per_bar: 4.0, + }, + system_timing: LiveCodeTiming::default(), + } + } } impl IsLivecodeSrc for LiveCodeTimeInstantInfo { @@ -328,15 +558,11 @@ impl IsLivecodeSrc for LiveCodeTimeInstantInfo { ("t".to_owned(), LivecodeValue::Float(time as f64)), ( "tease".to_owned(), - LivecodeValue::Float(ease( - time.into(), - (1.0 / self.timing_config.beats_per_bar).into(), - 0.0, - )), + LivecodeValue::Float(ease(time.into(), 1.0 / 4.0, 0.0)), ), ( "stease".to_owned(), - LivecodeValue::Float(ease(time.into(), 0.01, 0.0)), + LivecodeValue::Float(ease(time.into(), 0.0125, 0.0)), ), ("ti".to_owned(), LivecodeValue::Int(time as i64)), ("f".to_owned(), LivecodeValue::Float(frame as f64)), diff --git a/murrelet_livecode/src/types.rs b/murrelet_livecode/src/types.rs index d278f0e..55ba821 100644 --- a/murrelet_livecode/src/types.rs +++ b/murrelet_livecode/src/types.rs @@ -1,53 +1,91 @@ -use std::collections::HashSet; +use std::{collections::HashSet, fmt::Debug}; use evalexpr::{build_operator_tree, EvalexprError, HashMapContext, Node}; use itertools::Itertools; use lerpable::{step, Lerpable}; -use murrelet_common::IdxInRange2d; +use murrelet_common::{print_expect, IdxInRange, IdxInRange2d, LivecodeValue}; +use murrelet_gui::CanMakeGUI; use serde::{Deserialize, Deserializer}; -use serde_yaml::Location; +use thiserror::Error; use crate::{ - expr::IntoExprWorldContext, - livecode::{GetLivecodeIdentifiers, LivecodeFromWorld}, + expr::{IntoExprWorldContext, MixedEvalDefs, ToMixedDefs}, + lazy::{ControlLazyNodeF32, IsLazy, LazyNodeF32, WrappedLazyType}, + livecode::{ + ControlF32, GetLivecodeIdentifiers, LivecodeFromWorld, LivecodeToControl, LivecodeVariable, + }, state::LivecodeWorldState, - unitcells::UnitCellExprWorldContext, + unitcells::UnitCellIdx, }; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum LivecodeError { + #[error("{0}")] Raw(String), // my custom errors + #[error("{0}: {1}")] EvalExpr(String, EvalexprError), + #[error("{0}: {1}")] Io(String, std::io::Error), + #[error("nest get requested for odd thing...: {0}")] NestGetExtra(String), + #[error("nest get requested for odd thing...: {0}")] NestGetInvalid(String), - SerdeLoc(Location, String), + #[error("parse_error :: loc: {0}, err: {1}")] + SerdeLoc(String, String), + #[error("shader parse error: {0}")] WGPU(String), + #[error("parse: {0}")] + JsonParse(String), } -impl LivecodeError {} -impl std::fmt::Display for LivecodeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LivecodeError::Raw(msg) => write!(f, "{}", msg), - LivecodeError::EvalExpr(msg, err) => write!(f, "{}: {}", msg, err), - LivecodeError::Io(msg, err) => write!(f, "{}: {}", msg, err), - LivecodeError::NestGetExtra(err) => { - write!(f, "nest get has unusable tokens...: {}", err) - } - LivecodeError::NestGetInvalid(err) => { - write!(f, "nest get requested for odd thing...: {}", err) - } - LivecodeError::SerdeLoc(location, err) => { - // if it's err, hrm, remove the controlvec ones - let loc = format!("{},{}", location.line(), location.column()); - write!(f, "parse_error :: loc: {}, err: {}", loc, err) - } - LivecodeError::WGPU(err) => write!(f, "shader parse error: {}", err), - } +impl LivecodeError { + pub fn raw(s: &str) -> Self { + Self::Raw(s.to_string()) + } + + pub fn rawr(s: &str) -> LivecodeResult { + LivecodeResult::Err(Self::raw(s)) } } -impl std::error::Error for LivecodeError {} +pub trait IterUnwrapOrPrint { + fn iter_unwrap(&self, err: &str, f: F) -> Vec + where + F: Fn(&T) -> LivecodeResult; +} + +impl IterUnwrapOrPrint for Vec { + fn iter_unwrap(&self, err: &str, f: F) -> Vec + where + F: Fn(&T) -> LivecodeResult, + { + let res: LivecodeResult> = self.iter().map(f).collect(); + print_expect(res, err).unwrap_or(vec![]) + } +} + +// impl std::fmt::Display for LivecodeError { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// match self { +// LivecodeError::Raw(msg) => write!(f, "{}", msg), +// LivecodeError::EvalExpr(msg, err) => write!(f, "{}: {}", msg, err), +// LivecodeError::Io(msg, err) => write!(f, "{}: {}", msg, err), +// LivecodeError::NestGetExtra(err) => { +// write!(f, "nest get has unusable tokens...: {}", err) +// } +// LivecodeError::NestGetInvalid(err) => { +// write!(f, "nest get requested for odd thing...: {}", err) +// } +// LivecodeError::SerdeLoc(location, err) => { +// // if it's err, hrm, remove the controlvec ones +// let loc = format!("{},{}", location.line(), location.column()); +// write!(f, "parse_error :: loc: {},{}, err: {}", loc, err) +// } +// LivecodeError::WGPU(err) => write!(f, "shader parse error: {}", err), +// } +// } +// } + +// impl std::error::Error for LivecodeError {} pub type LivecodeResult = Result; @@ -56,6 +94,14 @@ pub type LivecodeResult = Result; #[serde(transparent)] pub struct AdditionalContextNode(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Node); +fn _default_ctx() -> AdditionalContextNode { + AdditionalContextNode::new_dummy() +} + +fn _default_ctx_lazy() -> AdditionalContextNode { + AdditionalContextNode::new_dummy() +} + impl Default for AdditionalContextNode { fn default() -> Self { Self(build_operator_tree("").unwrap()) @@ -63,11 +109,30 @@ impl Default for AdditionalContextNode { } impl AdditionalContextNode { + pub fn vars(&self) -> Vec { + self.0 + .iter_variable_identifiers() + .sorted() + .dedup() + .map(LivecodeVariable::from_str) + .collect_vec() + } + pub fn eval_raw(&self, ctx: &mut HashMapContext) -> LivecodeResult<()> { self.0 .eval_empty_with_context_mut(ctx) .map_err(|err| LivecodeError::EvalExpr("error evaluating ctx".to_owned(), err)) } + + pub fn new_dummy() -> AdditionalContextNode { + AdditionalContextNode(build_operator_tree("").unwrap()) + } +} + +impl CanMakeGUI for AdditionalContextNode { + fn make_gui() -> murrelet_gui::MurreletGUISchema { + murrelet_gui::MurreletGUISchema::Val(murrelet_gui::ValueGUI::Defs) + } } impl Lerpable for AdditionalContextNode { @@ -80,33 +145,402 @@ impl Lerpable for AdditionalContextNode { #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(untagged)] pub enum ControlVecElementRepeatMethod { - Single(usize), - Rect([usize; 2]), + Single(ControlF32), + Rect([ControlF32; 2]), } impl ControlVecElementRepeatMethod { - fn len(&self) -> usize { + fn len(&self, w: &LivecodeWorldState) -> LivecodeResult { + let v = match self { + ControlVecElementRepeatMethod::Single(s) => s.o(w)?, + ControlVecElementRepeatMethod::Rect(r) => { + let rr = r.o(w)?; + rr.x * rr.y + } + }; + Ok(v as usize) + } + fn iter(&self, w: &LivecodeWorldState) -> LivecodeResult> { + let v = match self { + ControlVecElementRepeatMethod::Single(s) => { + IdxInRange::enumerate_count(s.o(w)? as usize) + .iter() + .map(|x| x.to_2d()) + .collect_vec() + } + ControlVecElementRepeatMethod::Rect(s) => { + let rr = s.o(w)?; + IdxInRange2d::enumerate_counts(rr.x as usize, rr.y as usize) + } + }; + Ok(v) + } +} + +#[derive(Debug, Clone, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct ControlLazyBlendRepeatMethod { + count: ControlLazyNodeF32, + blend: ControlLazyNodeF32, +} + +#[derive(Debug, Clone, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum DeserLazyControlVecElementRepeatMethod { + Blend(ControlLazyBlendRepeatMethod), + Single(ControlLazyNodeF32), + Rect([ControlLazyNodeF32; 2]), +} +impl DeserLazyControlVecElementRepeatMethod { + fn o(&self, w: &LivecodeWorldState) -> LivecodeResult { match self { - ControlVecElementRepeatMethod::Single(s) => *s, - ControlVecElementRepeatMethod::Rect(r) => r[0] * r[1], + DeserLazyControlVecElementRepeatMethod::Single(lazy) => { + let v = lazy.o(w)?; + Ok(LazyVecElementRepeatMethod::Single(v)) + } + DeserLazyControlVecElementRepeatMethod::Rect(lazy) => { + let x = lazy[0].o(w)?; + let y = lazy[1].o(w)?; + Ok(LazyVecElementRepeatMethod::Rect([x, y])) + } + DeserLazyControlVecElementRepeatMethod::Blend(b) => { + let count = b.count.o(w)?; + let blend = b.blend.o(w)?; + + Ok(LazyVecElementRepeatMethod::Blend(LazyBlendRepeatMethod { + count, + blend, + })) + } } } - fn iter(&self) -> Vec { +} + +#[derive(Debug, Clone, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct DeserLazyControlVecElementRepeat { + repeat: DeserLazyControlVecElementRepeatMethod, + #[serde(default = "_default_ctx")] + ctx: AdditionalContextNode, + prefix: String, + what: Vec>, +} +impl DeserLazyControlVecElementRepeat { + fn o( + &self, + w: &LivecodeWorldState, + ) -> LivecodeResult>> + where + DeserSource: LivecodeFromWorld, + LazySource: IsLazy + Debug + Clone, + Target: Lerpable, + { + let what = self + .what + .iter() + .map(|x| x.o::(w)) + .collect::, _>>()?; + + Ok(LazyVecElementRepeat { + repeat: self.repeat.o(w)?, + ctx: self.ctx.clone(), + prefix: self.prefix.clone(), + what, + }) + } +} + +#[derive(Debug, Clone)] +pub enum DeserLazyControlVecElement +where + Source: Clone + Debug, +{ + Single(Source), + Repeat(DeserLazyControlVecElementRepeat), +} + +impl DeserLazyControlVecElement +where + Source: Clone + Debug, +{ + pub fn raw(c: Source) -> Self { + Self::Single(c) + } +} +impl DeserLazyControlVecElement { + pub fn o( + &self, + w: &LivecodeWorldState, + ) -> LivecodeResult>> + where + DeserSource: LivecodeFromWorld, + LazySource: Debug + Clone + IsLazy, + Target: Lerpable, + { + let a = match self { + DeserLazyControlVecElement::Single(a) => { + LazyControlVecElement::Single(WrappedLazyType::new(a.o(w)?)) + } + DeserLazyControlVecElement::Repeat(r) => { + LazyControlVecElement::Repeat(r.o::(w)?) + } + }; + Ok(a) + } +} + +// chatgpt +impl<'de, Source> Deserialize<'de> for DeserLazyControlVecElement +where + Source: Deserialize<'de> + Clone + Debug, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_yaml::Value::deserialize(deserializer)?; + + let mut errors = Vec::new(); + + // try the simple one + match Source::deserialize(value.clone()) { + Ok(single) => return Ok(DeserLazyControlVecElement::Single(single)), + Err(e) => errors.push(format!("{}", e)), + } + + match DeserLazyControlVecElementRepeat::deserialize(value.clone()) { + Ok(repeat) => return Ok(DeserLazyControlVecElement::Repeat(repeat)), + Err(e) => { + // it's gonna fail, so just check what + errors.push(format!("(repeat {})", e)) + } + } + + // Both variants failed, return an error with detailed messages + Err(serde::de::Error::custom(format!( + "ControlVecElement {}", + errors.join(" ") + ))) + } +} + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for DeserLazyControlVecElement +where + Source: schemars::JsonSchema + Clone + Debug, +{ + fn schema_name() -> String { + format!("LazyControlVecElement_{}", Source::schema_name()) + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + use schemars::schema::{Schema, SchemaObject, SubschemaValidation}; + // Variant 1: plain Source (your Single case without a wrapper key) + let single_schema = Source::json_schema(gen); + // Variant 2: the repeat object + let repeat_schema = >::json_schema(gen); + + Schema::Object(SchemaObject { + subschemas: Some(Box::new(SubschemaValidation { + one_of: Some(vec![single_schema, repeat_schema]), + ..Default::default() + })), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some( + "Either a single element (inline) OR a repeat object { repeat, prefix?, what }" + .to_string(), + ), + ..Default::default() + })), + ..Default::default() + }) + } +} + +#[derive(Debug, Clone)] +pub struct LazyBlendRepeatMethod { + count: LazyNodeF32, + blend: LazyNodeF32, +} + +// just an intermediate type... +#[derive(Debug, Clone)] +pub enum LazyVecElementRepeatMethod { + Blend(LazyBlendRepeatMethod), + Single(LazyNodeF32), + Rect([LazyNodeF32; 2]), +} +impl LazyVecElementRepeatMethod { + fn len(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + let v = match self { + LazyVecElementRepeatMethod::Single(s) => s.eval_lazy(ctx)?, + LazyVecElementRepeatMethod::Rect(r) => { + let rx = r[0].eval_lazy(ctx)?; + let ry = r[1].eval_lazy(ctx)?; + rx * ry + } + LazyVecElementRepeatMethod::Blend(b) => b.count.eval_lazy(ctx)?, + }; + Ok(v as usize) + } + fn iter(&self, ctx: &MixedEvalDefs) -> LivecodeResult> { + let v = match self { + LazyVecElementRepeatMethod::Single(s) => { + IdxInRange::enumerate_count(s.eval_lazy(ctx)? as usize) + .iter() + .map(|x| x.to_2d()) + .collect_vec() + } + LazyVecElementRepeatMethod::Rect(s) => { + let rx = s[0].eval_lazy(ctx)?; + let ry = s[1].eval_lazy(ctx)?; + IdxInRange2d::enumerate_counts(rx as usize, ry as usize) + } + LazyVecElementRepeatMethod::Blend(b) => IdxInRange::enumerate_count( + b.count.eval_lazy(ctx)? as usize + b.blend.eval_lazy(ctx)? as usize, + ) + .iter() + .map(|x| x.to_2d()) + .collect_vec(), + }; + Ok(v) + } + + fn next_blend(&self, ctx: &MixedEvalDefs) -> Option { match self { - ControlVecElementRepeatMethod::Single(s) => { - let mut v = vec![]; - for i in 0..*s { - v.push(IdxInRange2d::new(i, 1, *s)); + LazyVecElementRepeatMethod::Blend(b) => { + let blend = b.blend.eval_with_ctx(ctx).unwrap_or_default() as usize; + if blend > 0 { + Some(BlendWith::new(blend)) + } else { + None } - v } - ControlVecElementRepeatMethod::Rect(s) => { - let mut v = vec![]; - for i in 0..s[0] { - for j in 0..s[1] { - v.push(IdxInRange2d::new_rect(i, j, s[0], s[1])); + LazyVecElementRepeatMethod::Single(_) => None, + LazyVecElementRepeatMethod::Rect(_) => None, + } + } +} + +// just internal method, if we realize we're looking at a lazy, +#[derive(Debug, Clone)] +pub struct LazyVecElementRepeat { + repeat: LazyVecElementRepeatMethod, + ctx: AdditionalContextNode, + prefix: String, + what: Vec>, +} + +impl LazyVecElementRepeat> +where + Inner: Clone + Debug + IsLazy, + Inner::Target: Lerpable, +{ + // expands repeats + applies defs, while preserving WrappedLazyType nodes for blending + pub fn lazy_expand_vec_repeat_element( + &self, + ctx: &MixedEvalDefs, + ) -> LivecodeResult>> + where + Inner::Target: Lerpable, + { + let mut result: Vec> = + Vec::with_capacity(self.repeat.len(ctx)? * self.what.len()); + + let prefix = if self.prefix.is_empty() { + "i_".to_string() + } else { + format!("{}_", self.prefix) + }; + + for idx in self.repeat.iter(ctx)? { + let mut scoped_ctx = ctx.clone(); + scoped_ctx.add_node(self.ctx.clone()); + let expr = UnitCellIdx::from_idx2d(idx, 1.0).as_expr_world_context_values(); + scoped_ctx.set_vals(expr.with_prefix(&prefix)); + + let mut is_blending: Option = None; + + for src in &self.what { + match src { + LazyControlVecElement::Single(c) => { + let item = c.with_more_defs(&scoped_ctx)?; + blend_with_list(&mut result, item, &mut is_blending); + } + LazyControlVecElement::Repeat(c) => { + let nested = c.lazy_expand_vec_repeat_element(&scoped_ctx)?; + for item in nested { + blend_with_list(&mut result, item, &mut is_blending); + } + + if let Some(new_blend) = c.repeat.next_blend(&scoped_ctx) { + is_blending = Some(new_blend); + } } } - v + } + } + + Ok(result) + } + + pub fn with_more_defs(&self, ctx: &MixedEvalDefs) -> LivecodeResult + where + Inner::Target: Lerpable, + { + Ok(Self { + repeat: self.repeat.clone(), + ctx: self.ctx.clone(), + prefix: self.prefix.clone(), + what: self + .what + .iter() + .map(|elem| elem.with_more_defs(ctx)) + .collect::>>()?, + }) + } +} + +impl LivecodeToControl> + for LazyControlVecElement +where + Source: Debug + Clone + LivecodeToControl + IsLazy, + ControlSource: Debug + Clone, +{ + fn to_control(&self) -> DeserLazyControlVecElement { + match self { + LazyControlVecElement::Single(src) => { + DeserLazyControlVecElement::Single(src.to_control()) + } + LazyControlVecElement::Repeat(rep) => { + let repeat = match &rep.repeat { + LazyVecElementRepeatMethod::Single(l) => { + DeserLazyControlVecElementRepeatMethod::Single(l.to_control()) + } + LazyVecElementRepeatMethod::Rect([x, y]) => { + DeserLazyControlVecElementRepeatMethod::Rect([ + x.to_control(), + y.to_control(), + ]) + } + LazyVecElementRepeatMethod::Blend(b) => { + DeserLazyControlVecElementRepeatMethod::Blend( + ControlLazyBlendRepeatMethod { + count: b.count.to_control(), + blend: b.blend.to_control(), + }, + ) + } + }; + + let what = rep.what.iter().map(|e| e.to_control()).collect::>(); + + DeserLazyControlVecElement::Repeat(DeserLazyControlVecElementRepeat { + repeat, + prefix: rep.prefix.clone(), + what, + ctx: rep.ctx.clone(), + }) } } } @@ -114,14 +548,21 @@ impl ControlVecElementRepeatMethod { #[derive(Debug, Clone, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct ControlVecElementRepeat { +pub struct ControlVecElementRepeat { repeat: ControlVecElementRepeatMethod, // #[serde(default)] prefix: String, - what: Vec, + what: Vec>, + #[serde(default)] + blend_with_next: usize, } -impl GetLivecodeIdentifiers for ControlVecElement { +// impl GetLivecodeIdentifiers for ControlVecElement +impl GetLivecodeIdentifiers for ControlVecElement +where + Source: Clone + Debug + GetLivecodeIdentifiers, + // Sequencer: UnitCellCreator + GetLivecodeIdentifiers, +{ fn variable_identifiers(&self) -> Vec { match self { ControlVecElement::Single(c) => c.variable_identifiers(), @@ -149,12 +590,121 @@ impl GetLivecodeIdentifiers for ControlVecElement } } -impl ControlVecElementRepeat { - pub fn eval_and_expand_vec(&self, w: &LivecodeWorldState) -> LivecodeResult> +// chatgpt wanted to use this instead of IdxInRange, but I can probably switch back to idxinrange... +#[derive(Debug, Clone, Copy)] +struct BlendWith { + offset: usize, // 0 = last item + count: usize, // number of items to blend +} + +impl BlendWith { + fn new(count: usize) -> Self { + assert!(count > 0, "blend_with_next requires count > 0"); + Self { + offset: count - 1, + count, + } + } + + fn pct(&self) -> f32 { + (self.offset + 1) as f32 / (self.count + 1) as f32 + } + + fn prev(self) -> Option { + if self.offset == 0 { + None + } else { + Some(Self { + offset: self.offset - 1, + count: self.count, + }) + } + } +} + +fn blend_with_list( + result: &mut Vec, + new: Target, + is_blending: &mut Option, +) { + if let Some(curr_offset) = is_blending { + // otherwise, too far back, skip + if curr_offset.offset < result.len() { + let i = result.len() - 1 - curr_offset.offset; + + result[i] = new.lerpify(&result[i], &curr_offset.pct()); + + // if we can subtract 1, that's the next one to check. + // otherwise, set to none. + *is_blending = curr_offset.prev(); + } + } else { + result.push(new); + } +} + +pub fn eval_and_expand_vec_list( + items: &[ControlVecElement], + w: &LivecodeWorldState, +) -> LivecodeResult> +where + Source: LivecodeFromWorld + Clone + Debug, + Target: Lerpable, +{ + let mut result: Vec = Vec::new(); + let mut is_blending: Option = None; + + for item in items { + let expanded = item.eval_and_expand_vec(w)?; + for elem in expanded { + blend_with_list(&mut result, elem, &mut is_blending); + } + + let blend_count = item.blend_with_next(); + if blend_count > 0 { + is_blending = Some(BlendWith::new(blend_count)); + } + } + + Ok(result) +} + +pub fn lazy_expand_vec_list( + items: &[LazyControlVecElement>], + ctx: &MixedEvalDefs, +) -> LivecodeResult>> +where + Inner: Clone + Debug + IsLazy, + Inner::Target: Lerpable, +{ + let mut result: Vec> = Vec::new(); + let mut is_blending: Option = None; + + for item in items { + let expanded = item.lazy_expand_vec(ctx)?; + for elem in expanded { + blend_with_list(&mut result, elem, &mut is_blending); + } + + if let Some(blend_count) = item.blend_with_next(ctx) { + is_blending = Some(blend_count); + } + } + + Ok(result) +} + +impl ControlVecElementRepeat { + pub fn _eval_and_expand_vec( + &self, + w: &LivecodeWorldState, + offset: usize, + ) -> LivecodeResult<(usize, Vec)> where Source: LivecodeFromWorld, + Target: Lerpable, { - let mut result = Vec::with_capacity(self.repeat.len() * self.what.len()); + let mut result = Vec::with_capacity(self.repeat.len(w)? * self.what.len()); let prefix = if self.prefix.is_empty() { "i_".to_string() @@ -162,28 +712,186 @@ impl ControlVecElementRepeat { format!("{}_", self.prefix) }; - for idx in self.repeat.iter() { - let expr = - UnitCellExprWorldContext::from_idx2d(idx, 1.0).as_expr_world_context_values(); - let new_w = w.clone_with_vals(expr, &prefix)?; + let mut offset = offset; + + for idx in self.repeat.iter(w)? { + let expr = UnitCellIdx::from_idx2d(idx, 1.0).as_expr_world_context_values(); + let mut new_w = w.clone_with_vals(expr, &prefix); + + let mut is_blending: Option = None; for src in &self.what { - let o = src.o(&new_w)?; - result.push(o); + match src { + ControlVecElement::Single(c) => { + // just update it and overwrite it... + new_w.update_with_defs( + ("vseed", LivecodeValue::float(offset as f32)).to_mixed_def(), + ); + let o = c.o(&new_w)?; + + blend_with_list(&mut result, o, &mut is_blending); + + offset += 1; + } + ControlVecElement::Repeat(c) => { + let (new_offset, o) = c._eval_and_expand_vec(&new_w, offset)?; + + for item in o.into_iter() { + blend_with_list(&mut result, item, &mut is_blending); + } + + if c.blend_with_next > 0 { + is_blending = Some(BlendWith::new(c.blend_with_next)); + } + + offset = new_offset; + } + } } } - Ok(result) + Ok((offset, result)) + } + + pub fn eval_and_expand_vec(&self, w: &LivecodeWorldState) -> LivecodeResult> + where + Source: LivecodeFromWorld, + Target: Lerpable, + { + let (_, a) = self._eval_and_expand_vec(w, 0)?; + Ok(a) } } +// #[derive(Debug, Clone)] +// #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +// #[cfg_attr( +// feature = "schemars", +// schemars(bound = "Source: schemars::JsonSchema, ControlSequencer: schemars::JsonSchema") +// )] +// pub struct VecUnitCell +// where +// Source: Clone + Debug + Default, +// Sequencer: UnitCellCreator, +// ControlSequencer: LivecodeFromWorld, + +// { +// sequencer: ControlSequencer, +// ctx: AdditionalContextNode, +// prefix: String, +// node: Source, +// #[serde(skip)] +// #[cfg_attr(feature = "schemars", schemars(skip))] +// _marker: PhantomData, +// } +// impl VecUnitCell +// where +// Source: Clone + Debug + Default, +// Sequencer: UnitCellCreator + Clone, +// ControlSequencer: LivecodeFromWorld, +// { +// fn eval_and_expand_vec( +// &self, +// w: &LivecodeWorldState, +// ) -> Result, LivecodeError> +// where +// Source: Clone + Debug + Default + LivecodeFromWorld, +// Sequencer: UnitCellCreator, +// Target: Default + Clone + Debug + +// { +// let seq = self.sequencer.o(w)?; +// let n: Box> = Box::new(self.node.clone()); +// let t = TmpUnitCells::new( +// seq, +// n, +// Some(self.ctx.clone()), +// &self.prefix, +// ).o(w)?; +// Ok(t.items.into_iter().map(|x| *x.node).collect_vec()) +// } +// } + #[derive(Debug, Clone)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub enum ControlVecElement { +// #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +// pub enum ControlVecElement +pub enum ControlVecElement +where + Source: Clone + Debug, +{ Single(Source), Repeat(ControlVecElementRepeat), + // UnitCell(VecUnitCell), +} + +#[derive(Debug, Clone)] +pub enum LazyControlVecElement +where + Source: Clone + Debug + crate::lazy::IsLazy, +{ + Single(Source), + Repeat(LazyVecElementRepeat), +} + +impl IsLazy for LazyControlVecElement> +where + Inner: Clone + Debug + IsLazy, + Inner::Target: Lerpable, +{ + type Target = Vec; + + fn eval_lazy(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + let expanded: Vec> = self.lazy_expand_vec(ctx)?; + expanded.into_iter().map(|s| s.eval_lazy(ctx)).collect() + } + + fn with_more_defs(&self, ctx: &MixedEvalDefs) -> LivecodeResult { + Ok(match self { + LazyControlVecElement::Single(s) => { + LazyControlVecElement::Single(s.with_more_defs(ctx)?) + } + LazyControlVecElement::Repeat(rep) => { + LazyControlVecElement::Repeat(rep.with_more_defs(ctx)?) + } + }) + } +} + +impl LazyControlVecElement> +where + Inner: Clone + Debug + IsLazy, + Inner::Target: Lerpable, +{ + pub fn lazy_expand_vec( + &self, + ctx: &MixedEvalDefs, + ) -> LivecodeResult>> { + match self { + LazyControlVecElement::Single(c) => Ok(vec![c.clone()]), + LazyControlVecElement::Repeat(c) => c.lazy_expand_vec_repeat_element(ctx), + } + } + + fn blend_with_next(&self, ctx: &MixedEvalDefs) -> Option { + match self { + LazyControlVecElement::Single(_) => None, + LazyControlVecElement::Repeat(r) => r.repeat.next_blend(ctx), + } + } } -impl ControlVecElement { +impl ControlVecElement +where + Source: Clone + Debug, + // Sequencer: UnitCellCreator, + // ControlSequencer: LivecodeFromWorld, +{ + fn blend_with_next(&self) -> usize { + match self { + ControlVecElement::Single(_) => 0, + ControlVecElement::Repeat(r) => r.blend_with_next, + } + } + pub fn raw(c: Source) -> Self { Self::Single(c) } @@ -191,18 +899,88 @@ impl ControlVecElement { pub fn eval_and_expand_vec(&self, w: &LivecodeWorldState) -> LivecodeResult> where Source: LivecodeFromWorld, + Target: Lerpable, { match self { ControlVecElement::Single(c) => Ok(vec![c.o(w)?]), ControlVecElement::Repeat(r) => r.eval_and_expand_vec(w), + // ControlVecElement::UnitCell(c) => c.eval_and_expand_vec(w), } } } -// chatgpt +// chatgpt can implement my deserializers... + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for ControlVecElement +where + Source: schemars::JsonSchema + Clone + Debug, +{ + fn schema_name() -> String { + format!("ControlVecElement_{}", Source::schema_name()) + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + use schemars::schema::{Schema, SchemaObject, SubschemaValidation}; + // Variant 1: plain Source (your Single case without a wrapper key) + let single_schema = Source::json_schema(gen); + // Variant 2: the repeat object + let repeat_schema = >::json_schema(gen); + + Schema::Object(SchemaObject { + subschemas: Some(Box::new(SubschemaValidation { + one_of: Some(vec![single_schema, repeat_schema]), + ..Default::default() + })), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some( + "Either a single element (inline) OR a repeat object { repeat, prefix?, what }" + .to_string(), + ), + ..Default::default() + })), + ..Default::default() + }) + } +} + +impl GetLivecodeIdentifiers for DeserLazyControlVecElement +where + Source: Clone + Debug + GetLivecodeIdentifiers, +{ + fn variable_identifiers(&self) -> Vec { + match self { + DeserLazyControlVecElement::Single(c) => c.variable_identifiers(), + DeserLazyControlVecElement::Repeat(r) => r + .what + .iter() + .flat_map(|x| x.variable_identifiers()) + .collect::>() + .into_iter() + .collect_vec(), + } + } + + fn function_identifiers(&self) -> Vec { + match self { + DeserLazyControlVecElement::Single(c) => c.function_identifiers(), + DeserLazyControlVecElement::Repeat(r) => r + .what + .iter() + .flat_map(|x| x.function_identifiers()) + .collect::>() + .into_iter() + .collect_vec(), + } + } +} + +// chatgpt can implement my deserializers... impl<'de, Source> Deserialize<'de> for ControlVecElement where - Source: Deserialize<'de>, + Source: Deserialize<'de> + Clone + Debug, + // Sequencer: UnitCellCreator, + // ControlSequencer: LivecodeFromWorld, { fn deserialize(deserializer: D) -> Result where @@ -218,7 +996,6 @@ where Err(e) => errors.push(format!("{}", e)), } - // match ControlVecElementRepeat::deserialize(value.clone()) { Ok(repeat) => return Ok(ControlVecElement::Repeat(repeat)), Err(e) => { @@ -234,3 +1011,18 @@ where ))) } } + +impl crate::nestedit::NestEditable for LazyControlVecElement +where + LazyTarget: IsLazy, +{ + fn nest_update(&self, _mods: crate::nestedit::NestedMod) -> Self { + self.clone() + } + + fn nest_get(&self, _getter: &[&str]) -> LivecodeResult { + Err(LivecodeError::NestGetExtra( + "LazyControlVecElement".to_owned(), + )) + } +} diff --git a/murrelet_livecode/src/unitcells.rs b/murrelet_livecode/src/unitcells.rs index 30a48ac..b00edbd 100644 --- a/murrelet_livecode/src/unitcells.rs +++ b/murrelet_livecode/src/unitcells.rs @@ -8,7 +8,7 @@ use rand::{Rng, SeedableRng}; use std::fmt::Debug; use std::{any::Any, collections::HashMap, fmt}; -use crate::expr::{ExprWorldContextValues, IntoExprWorldContext}; +use crate::expr::{ExprWorldContextValues, IntoExprWorldContext, MixedEvalDefsRef}; use crate::livecode::LivecodeFromWorld; use crate::state::LivecodeWorldState; use crate::types::AdditionalContextNode; @@ -18,7 +18,7 @@ use crate::types::LivecodeResult; pub struct TmpUnitCells { sequencer: CtxSource, node: Box>, - ctx: Option, + ctx: Option, prefix: String, } @@ -32,33 +32,12 @@ impl TmpUnitCells( - world_ctx: &'a LivecodeWorldState, - prefix: &'a str, - unit_cell_ctx: &'a UnitCellContext, - maybe_node: Option<&'a AdditionalContextNode>, -) -> LivecodeResult { - // world_ctx is currently just the World, so first attach the unit cell world state - - let mut world_state = world_ctx.clone_to_unitcell(unit_cell_ctx, prefix)?; - - let unit_cell_world_ctx = world_state.ctx_mut(); - - // now update the unit_cell context to have the node - if let Some(node) = maybe_node { - node.eval_raw(unit_cell_world_ctx)?; - } - - // great, now we have it built. return it! - Ok(world_state) -} - impl TmpUnitCells where CtxSource: UnitCellCreator, @@ -67,7 +46,7 @@ where pub fn eval_with_ctx( &self, world_ctx: &LivecodeWorldState, - unit_cell_ctx: &Option, + unit_cell_ctx: Option<&MixedEvalDefsRef>, ) -> Vec> { // right now this one doesn't usually return an error because we do stuff // to avoid returning every time, should tidy up @@ -84,7 +63,7 @@ where // it doesn't have sequencer ctx yet, we'll add that next let unit_cell_world_ctx_result = - create_unit_cell(world_ctx, &self.prefix, ctx, unit_cell_ctx.as_ref()); + world_ctx.clone_to_unitcell(ctx, &self.prefix, unit_cell_ctx); // and evaluate with this! // todo can i use the result to clean this up @@ -114,7 +93,7 @@ where } pub fn o(&self, ctx: &LivecodeWorldState) -> LivecodeResult> { - Ok(UnitCells::new(self.eval_with_ctx(ctx, &self.ctx))) + Ok(UnitCells::new(self.eval_with_ctx(ctx, self.ctx.as_ref()))) } } @@ -171,7 +150,7 @@ impl UnitCell { } pub fn idx(&self) -> IdxInRange2d { - self.detail.ctx.to_idx2d() + self.detail.idx.to_idx2d() } pub fn is_alternate(&self) -> bool { @@ -183,7 +162,7 @@ impl Default for UnitCell { Self { node: Default::default(), detail: UnitCellContext::new( - UnitCellExprWorldContext::from_idx1d(IdxInRange::new(0, 1)), + UnitCellIdx::from_idx1d(IdxInRange::new(0, 1)), SimpleTransform2d::ident(), ), } @@ -210,14 +189,14 @@ impl UnitCells { Self { items } } - pub fn iter(&self) -> std::slice::Iter> { + pub fn iter(&self) -> std::slice::Iter<'_, UnitCell> { self.items.iter() } pub fn x_y_z_max(&self) -> (u64, u64, u64) { // each should have the same, so grab the first if let Some(first) = self.items.first() { - first.detail.ctx.max() + first.detail.idx.max() } else { (0, 0, 0) } @@ -232,9 +211,9 @@ impl UnitCells { for item in &self.items { hm.insert( ( - item.detail.ctx.x_i, - item.detail.ctx.y_i, - item.detail.ctx.z_i, + item.detail.idx.x_i, + item.detail.idx.y_i, + item.detail.idx.z_i, ), item.clone(), ); @@ -262,6 +241,11 @@ impl UnitCells { None } + + // conveninence method + pub fn copy_nodes(&self) -> Vec { + self.items.iter().map(|x| *x.node.clone()).collect_vec() + } } impl FromIterator> @@ -290,6 +274,15 @@ impl UnitCellLookup { Self { data, maxes } } + pub fn x_y_z_max(&self) -> (u64, u64, u64) { + self.maxes + } + + pub fn dims(&self) -> Dim2d { + let (x, y, _) = self.maxes; + Dim2d::from_x_y(x, y) + } + pub fn to_vec2d(&self) -> Vec>>> { let mut vs = vec![]; @@ -336,20 +329,20 @@ impl UnitCellLookup { match neighbor { CellNeighbor::Hex(HexCellNeighbor::Up) => self.get_ij(i, j + 1), CellNeighbor::Hex(HexCellNeighbor::UpLeft) => { - let jj = if i % 2 == 0 { j + 1 } else { j }; + let jj = if i.is_multiple_of(2) { j + 1 } else { j }; self.get_ij(i - 1, jj) } CellNeighbor::Hex(HexCellNeighbor::DownLeft) => { - let jj = if i % 2 == 0 { j } else { j - 1 }; + let jj = if i.is_multiple_of(2) { j } else { j - 1 }; self.get_ij(i - 1, jj) } CellNeighbor::Hex(HexCellNeighbor::Down) => self.get_ij(i, j - 1), CellNeighbor::Hex(HexCellNeighbor::DownRight) => { - let jj = if i % 2 == 0 { j } else { j - 1 }; + let jj = if i.is_multiple_of(2) { j } else { j - 1 }; self.get_ij(i + 1, jj) } CellNeighbor::Hex(HexCellNeighbor::UpRight) => { - let jj = if i % 2 == 0 { j + 1 } else { j }; + let jj = if i.is_multiple_of(2) { j + 1 } else { j }; self.get_ij(i + 1, jj) } @@ -370,6 +363,50 @@ impl UnitCellLookup { .get(&(i as u64, j as u64, 0)) .map(|x| *(x.node).clone()) } + + pub fn force_get_dim(&self, dim: &Dim2d) -> &UnitCell { + self.force_get_ij(dim.i(), dim.j()) + } + + // this probably only works for rectangles... + pub fn loc_to_tile_idx_and_offset( + &self, + v: Vec2, + ) -> Option { + // blah + for uc in self.data.values() { + if uc.bounds().contains(v) { + let local_offset = v - uc.center(); + let scaled_offset = local_offset / uc.bounds().wh(); + return Some(IdxAndOffset { + ij: uc.idx(), + offset_i: scaled_offset.x, + offset_j: scaled_offset.y, + }); + } + } + None + } +} + +pub struct IdxAndOffset { + ij: IdxInRange2d, + // how far in i and j they are + offset_i: f32, + offset_j: f32, +} +impl IdxAndOffset { + pub fn lerp_idxes(&self) -> [(usize, usize); 4] { + self.ij.lerp_idx() + } + + pub fn offset_i(&self) -> f32 { + self.offset_i + } + + pub fn offset_j(&self) -> f32 { + self.offset_j + } } #[derive(PartialEq, Eq, Copy, Clone)] @@ -461,25 +498,52 @@ impl Clone for Box { // eh, should make this easier... #[derive(Debug, Clone)] pub struct UnitCellContext { - ctx: UnitCellExprWorldContext, + idx: UnitCellIdx, + ctx: Option, pub detail: UnitCellDetails, pub tile_info: Option>, } impl UnitCellContext { - pub fn new(ctx: UnitCellExprWorldContext, transform: SimpleTransform2d) -> UnitCellContext { + pub fn new(idx: UnitCellIdx, transform: SimpleTransform2d) -> UnitCellContext { UnitCellContext { - ctx, + idx, + ctx: None, detail: UnitCellDetails::new(transform), tile_info: None, } } - pub fn new_with_base( - ctx: UnitCellExprWorldContext, - detail: UnitCellDetails, + pub fn new_expr( + idx: UnitCellIdx, + ctx: ExprWorldContextValues, + transform: SimpleTransform2d, ) -> UnitCellContext { UnitCellContext { - ctx, + idx, + ctx: Some(ctx), + detail: UnitCellDetails::new(transform), + tile_info: None, + } + } + + pub fn new_full( + idx: UnitCellIdx, + ctx: ExprWorldContextValues, + transform: SimpleTransform2d, + adjust_transform: SimpleTransform2d, + ) -> UnitCellContext { + UnitCellContext { + idx, + ctx: Some(ctx), + detail: UnitCellDetails::new_fancy(transform, adjust_transform, true), + tile_info: None, + } + } + + pub fn new_with_base(ctx: UnitCellIdx, detail: UnitCellDetails) -> UnitCellContext { + UnitCellContext { + idx: ctx, + ctx: None, detail, tile_info: None, } @@ -490,24 +554,26 @@ impl UnitCellContext { } pub fn new_with_info( - ctx: UnitCellExprWorldContext, + ctx: UnitCellIdx, detail: UnitCellDetails, tile_info: Box, ) -> UnitCellContext { UnitCellContext { - ctx, + idx: ctx, + ctx: None, detail, tile_info: Some(tile_info), } } pub fn new_with_option_info( - ctx: UnitCellExprWorldContext, + ctx: UnitCellIdx, detail: UnitCellDetails, tile_info: Option>, ) -> UnitCellContext { UnitCellContext { - ctx, + idx: ctx, + ctx: None, detail, tile_info, } @@ -523,23 +589,39 @@ impl UnitCellContext { // just updates details... // applies the other transform _after_ current pub fn combine(&self, other: &UnitCellContext) -> UnitCellContext { + let ctx = match (&self.ctx, &other.ctx) { + (None, None) => self.ctx.clone(), + (None, Some(that)) => Some(that.clone()), + (Some(this), None) => Some(this.clone()), + (Some(this), Some(that)) => Some(this.clone().combine(that.clone())), + }; + UnitCellContext { - ctx: self.ctx, + idx: self.idx, + ctx, detail: other.detail.as_wallpaper().unwrap().combine(&self.detail), tile_info: None, } } pub fn combine_keep_other_ctx(&self, other: &UnitCellContext) -> UnitCellContext { + let ctx = match (&self.ctx, &other.ctx) { + (None, None) => self.ctx.clone(), + (None, Some(that)) => Some(that.clone()), + (Some(this), None) => Some(this.clone()), + (Some(this), Some(that)) => Some(that.clone().combine(this.clone())), + }; + UnitCellContext { - ctx: other.ctx, + idx: self.idx, + ctx, detail: other.detail.as_wallpaper().unwrap().combine(&self.detail), tile_info: None, } } - pub fn ctx(&self) -> UnitCellExprWorldContext { - self.ctx + pub fn ctx(&self) -> UnitCellIdx { + self.idx } pub fn transform(&self) -> SimpleTransform2d { @@ -547,7 +629,7 @@ impl UnitCellContext { } pub fn idx(&self) -> IdxInRange2d { - self.ctx.to_idx2d() + self.idx.to_idx2d() } pub fn rect_bound(&self) -> Vec { @@ -573,12 +655,12 @@ impl UnitCellContext { self.detail.transform_with_skew_mat4() } - pub fn transform_with_skew(&self, v: &F) -> Polyline { - self.detail.transform_with_skew(v) + pub fn transform_with_skew(&self, v: &F) -> F { + v.transform_with(&self.detail.transform_with_skew_mat4()) } pub fn transform_one_point_with_skew(&self, v: Vec2) -> Vec2 { - self.detail.transform_with_skew(&vec![v]).clone_to_vec()[0] + v.transform_with(&self.detail.transform_with_skew_mat4()) } pub fn transform_no_skew_one_point(&self, v: Vec2) -> Vec2 { @@ -586,9 +668,9 @@ impl UnitCellContext { self.detail.transform_no_skew_one_point(v) } - pub fn transform_no_skew(&self, v: &F) -> Polyline { + pub fn transform_no_skew(&self, v: &F) -> F { // also does adjust shape.. - self.detail.transform_no_skew(v) + v.transform_with(&self.detail.transform_no_skew_mat4()) } pub fn transform_no_skew_mat4(&self) -> SimpleTransform2d { @@ -602,18 +684,23 @@ impl UnitCellContext { impl IntoExprWorldContext for UnitCellContext { fn as_expr_world_context_values(&self) -> ExprWorldContextValues { - let mut ctx_vals = self.ctx.as_expr_world_context_values(); + let mut ctx_vals = self.idx.as_expr_world_context_values(); - let loc = self - .detail - .transform_with_skew(&vec![vec2(-50.0, -50.0), vec2(50.0, 50.0)]); - let locs = loc.into_iter_vec2().collect_vec(); - let width = locs[1].x - locs[0].x; - let height = locs[1].y - locs[0].y; + let locs = vec![vec2(-50.0, -50.0), vec2(50.0, -50.0), vec2(50.0, 50.0)] + .into_iter() + .map(|x| self.detail.transform_with_skew_mat4().transform_vec2(x)) + .collect_vec(); + + let width = locs[1].distance(locs[0]); + let height = locs[1].distance(locs[2]); ctx_vals.set_val("u_width", LivecodeValue::float(width)); ctx_vals.set_val("u_height", LivecodeValue::float(height)); + if let Some(expr) = &self.ctx { + ctx_vals = ctx_vals.combine(expr.clone()); + } + ctx_vals } } @@ -633,7 +720,7 @@ impl Lerpable for UnitCellContext { // world state for unit cell #[derive(Copy, Clone, Debug)] -pub struct UnitCellExprWorldContext { +pub struct UnitCellIdx { x: f32, y: f32, z: f32, @@ -646,10 +733,10 @@ pub struct UnitCellExprWorldContext { seed: f32, h_ratio: f32, // width is always 100, what is h } -impl UnitCellExprWorldContext { +impl UnitCellIdx { // this just needs to be interesting.... not correct pub fn experimental_lerp(&self, other: &Self, pct: f32) -> Self { - UnitCellExprWorldContext { + UnitCellIdx { x: self.x.lerpify(&other.x, &pct), y: self.y.lerpify(&other.y, &pct), z: self.z.lerpify(&other.z, &pct), @@ -664,12 +751,8 @@ impl UnitCellExprWorldContext { } } - pub fn from_idx2d_and_actual_xy( - xy: Vec2, - idx: IdxInRange2d, - h_ratio: f32, - ) -> UnitCellExprWorldContext { - UnitCellExprWorldContext { + pub fn from_idx2d_and_actual_xy(xy: Vec2, idx: IdxInRange2d, h_ratio: f32) -> UnitCellIdx { + UnitCellIdx { x: xy.x, y: xy.y, z: 0.0, @@ -684,8 +767,8 @@ impl UnitCellExprWorldContext { } } - pub fn from_idx1d(idx: IdxInRange) -> UnitCellExprWorldContext { - UnitCellExprWorldContext { + pub fn from_idx1d(idx: IdxInRange) -> UnitCellIdx { + UnitCellIdx { x: idx.pct(), y: 0.0, z: 0.0, @@ -700,8 +783,8 @@ impl UnitCellExprWorldContext { } } - pub fn from_idx2d(idx: IdxInRange2d, h_ratio: f32) -> UnitCellExprWorldContext { - UnitCellExprWorldContext { + pub fn from_idx2d(idx: IdxInRange2d, h_ratio: f32) -> UnitCellIdx { + UnitCellIdx { x: idx.i.pct(), y: idx.j.pct(), z: 0.0, @@ -716,16 +799,12 @@ impl UnitCellExprWorldContext { } } - pub fn from_idx3d( - x_idx: IdxInRange, - y_idx: IdxInRange, - z_idx: IdxInRange, - ) -> UnitCellExprWorldContext { + pub fn from_idx3d(x_idx: IdxInRange, y_idx: IdxInRange, z_idx: IdxInRange) -> UnitCellIdx { let seed = z_idx.i() * (y_idx.total_usize() * x_idx.total_usize()) as u64 + y_idx.i() * (x_idx.total_usize() as u64) + x_idx.i(); - UnitCellExprWorldContext { + UnitCellIdx { x: x_idx.pct(), y: y_idx.pct(), z: z_idx.pct(), @@ -760,7 +839,7 @@ impl UnitCellExprWorldContext { } } -impl IntoExprWorldContext for UnitCellExprWorldContext { +impl IntoExprWorldContext for UnitCellIdx { fn as_expr_world_context_values(&self) -> ExprWorldContextValues { // make a few rns let mut rng = StdRng::seed_from_u64((self.seed + 19247.0) as u64); @@ -871,22 +950,11 @@ impl UnitCellDetails { } } - fn transform_with_skew(&self, face: &F) -> Polyline { - let vs = face - .into_iter_vec2() - .map(|x| match self { - UnitCellDetails::Wallpaper(d) => d.transform_with_skew(x), - UnitCellDetails::Function(d) => d.transform_with_skew(x), - }) - .collect_vec(); - Polyline::new(vs) - } - pub fn transform_no_skew_one_point(&self, v: Vec2) -> Vec2 { self.transform_no_skew(&vec![v]).clone_to_vec()[0] } - pub fn transform_no_skew(&self, v: &F) -> Polyline { + pub fn transform_no_skew(&self, v: &F) -> F { match self { UnitCellDetails::Wallpaper(w) => w.transform_no_skew(v), UnitCellDetails::Function(_) => todo!(), @@ -949,7 +1017,7 @@ impl Clone for UnitCellDetailsFunction { } impl UnitCellDetailsFunction { - fn transform_with_skew(&self, x: Vec2) -> Vec2 { + pub fn transform_with_skew(&self, x: Vec2) -> Vec2 { (self.func)(x) } } @@ -970,16 +1038,12 @@ impl UnitCellDetailsWallpaper { // adjust the shape (symmetry, rotation), translate the center let offset = self.offset(); let new_center = SimpleTransform2d::translate(offset); - self.adjust_shape.add_after(&new_center) + self.adjust_shape.add_transform_after(&new_center) } - pub fn transform_no_skew(&self, v: &F) -> Polyline { + pub fn transform_no_skew(&self, v: &F) -> F { let m = self.transform_no_skew_mat(); - Polyline::new( - v.into_iter_vec2() - .map(|x| m.transform_vec2(x)) - .collect_vec(), - ) + v.transform_with(&m) } // how to move the location of something @@ -1002,12 +1066,12 @@ impl UnitCellDetailsWallpaper { .as_wallpaper() .unwrap() .transform_vertex - .add_after(&self.transform_vertex), + .add_transform_after(&self.transform_vertex), adjust_shape: detail .as_wallpaper() .unwrap() .adjust_shape - .add_after(&self.adjust_shape), + .add_transform_after(&self.adjust_shape), is_base: self.is_base && detail.as_wallpaper().unwrap().is_base, }) } diff --git a/murrelet_livecode_macros/Cargo.toml b/murrelet_livecode_macros/Cargo.toml index 65b34b0..02fbbaf 100644 --- a/murrelet_livecode_macros/Cargo.toml +++ b/murrelet_livecode_macros/Cargo.toml @@ -9,8 +9,9 @@ license = "AGPL-3.0-or-later" [features] schemars = ["murrelet_livecode/schemars", "murrelet_livecode_derive/schemars"] +# murrelet_gui = ["murrelet_livecode/murrelet_gui", "murrelet_livecode_derive/murrelet_gui"] [dependencies] -murrelet_livecode_derive = { version = "0.1.2", path = "murrelet_livecode_derive", default-features = false } -murrelet_livecode = { version = "0.1.2", path = "../murrelet_livecode", default-features = false } +murrelet_livecode_derive = { workspace = true, default-features = false } +murrelet_livecode = { workspace = true, default-features = false } diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/Cargo.toml b/murrelet_livecode_macros/murrelet_livecode_derive/Cargo.toml index 28924ba..d465dce 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/Cargo.toml +++ b/murrelet_livecode_macros/murrelet_livecode_derive/Cargo.toml @@ -12,6 +12,7 @@ proc-macro = true [features] schemars = ["murrelet_livecode/schemars"] +# murrelet_gui = ["murrelet_livecode/murrelet_gui"] [dependencies] syn = "2.0.15" @@ -19,15 +20,16 @@ quote = "1.0.18" proc-macro2 = "1.0.37" darling = "0.20.3" serde = { version = "1.0.104", features = ["derive"] } -murrelet_livecode = { version = "0.1.2", path = "../../murrelet_livecode", default-features = false} +murrelet_livecode = { workspace = true, default-features = false } # just for examples... [dev-dependencies] -murrelet_common = { version = "0.1.2", path = "../../murrelet_common"} -glam = "0.28.0" +murrelet_common = { workspace = true } +glam = { version = "0.28.0", features = ["serde"] } palette = "0.7.6" -lerpable = "0.0.2" +lerpable = { version = "0.0.3", features = ["glam"] } schemars = "0.8.21" +murrelet_gui = { workspace = true, features = ["glam"] } [[example]] name = "tests" diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/examples/tests.rs b/murrelet_livecode_macros/murrelet_livecode_derive/examples/tests.rs index 8a41711..99ce7b2 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/examples/tests.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/examples/tests.rs @@ -3,18 +3,22 @@ use std::collections::HashMap; use glam::*; use lerpable::Lerpable; use murrelet_common::*; +use murrelet_livecode::cachedcompute::CachedCompute; use murrelet_livecode::{types::AdditionalContextNode, unitcells::*}; -use murrelet_livecode_derive::Livecode; +use murrelet_livecode_derive::Cached; +use murrelet_livecode_derive::{Livecode, LivecodeOnly, NestEdit}; #[derive(Debug, Clone, Livecode, Lerpable, Default)] pub struct BasicTypes { a_number: f32, - b_color: MurreletColor, - #[lerpable(func = "lerpify_vec2")] c_vec2: Vec2, + b_angle: AnglePi, + c_vec3: Vec3, + b_color: MurreletColor, something: Vec, - #[lerpable(func = "lerpify_vec_vec2")] list_of_vec2: Vec, + option_f32: Option, + // option_vec2: Option, } fn empty_string() -> String { @@ -31,22 +35,22 @@ pub struct BasicTypesWithDefaults { a_number: f32, b_color: MurreletColor, #[livecode(serde_default = "0")] - #[lerpable(func = "lerpify_vec2")] - c_vec2: Vec2, + c_vec2_serde_default: Vec2, something: Vec, - #[lerpable(func = "lerpify_vec_vec2")] list_of_vec2: Vec, #[livecode(kind = "none", serde_default = "empty_string")] label: String, #[livecode(kind = "none")] #[lerpable(method = "skip")] b: HashMap, + list_test: Vec, } #[derive(Debug, Clone, Livecode, Lerpable, Default)] struct TestLazy { #[lerpable(method = "skip")] lazy: LazyBasicTypes, + // lazy_test: LazyBasicTypes, } #[derive(Debug, Clone, Livecode, Lerpable, Default)] @@ -55,80 +59,106 @@ enum EnumTest { A, B(TestLazy), C(#[lerpable(method = "skip")] LazyTestLazy), + // D(LazyTestLazy), } #[derive(Debug, Clone, Livecode, Lerpable, Default)] struct TestNewType(Vec); -#[derive(Debug, Clone, Livecode, Lerpable, Default)] -struct SequencerTest { - sequencer: SimpleSquareSequence, - ctx: AdditionalContextNode, - #[livecode(src = "sequencer", ctx = "ctx")] - node: UnitCells, - #[livecode(src = "sequencer", ctx = "ctx")] - #[lerpable(method = "skip")] - node_two: UnitCells, -} +// #[derive(Debug, Clone, Livecode, Lerpable, Default)] +// struct SequencerTest { +// sequencer: SimpleSquareSequence, +// ctx: AdditionalContextNode, +// #[livecode(src = "sequencer", ctx = "ctx")] +// node: UnitCells, +// #[livecode(src = "sequencer", ctx = "ctx")] +// #[lerpable(method = "skip")] +// node_two: UnitCells, +// } + +// fn make_grid( +// x: usize, +// y: usize, +// cell_size: Vec2, +// offset_alternating: bool, +// ) -> Vec { +// let x_usize = x; +// let y_usize = y; + +// (0..x_usize) +// .flat_map(|x| { +// let x_idx = IdxInRange::new(x, x_usize); +// (0..y_usize).map(move |y| { +// let y_idx = IdxInRange::new(y, y_usize); +// let idx = IdxInRange2d::new_from_idx(x_idx, y_idx); +// let ctx = UnitCellIdx::from_idx2d(idx, 1.0); + +// let mut center = if offset_alternating { +// let mut center = idx.to_alternating_i().center_of_cell(); +// center += vec2(-0.5, 0.0); +// if idx.i.i() % 2 == 1 { +// center += vec2(0.5, 0.5); +// } + +// let offset_angle = AnglePi::new(1.0 / 6.0); +// let diag_scale = offset_angle.to_norm_dir() * cell_size.x / (100.0 * 0.5); + +// center *= diag_scale; +// center +// } else { +// let mut center = idx.center_of_cell(); +// center *= vec2(cell_size.x, cell_size.y) / 100.0; +// center +// }; + +// center *= 100.0; + +// let transform = SimpleTransform2d::new(vec![ +// SimpleTransform2dStep::translate(center), +// SimpleTransform2dStep::scale_both(cell_size.x / 100.0), +// ]); + +// UnitCellContext::new(ctx, transform) +// }) +// }) +// .collect::>() +// } + +// #[derive(Clone, Debug, Default, Livecode, Lerpable)] +// pub struct SimpleSquareSequence { +// rows: usize, +// cols: usize, +// size: f32, +// } +// impl UnitCellCreator for SimpleSquareSequence { +// fn to_unit_cell_ctxs(&self) -> Vec { +// make_grid(self.cols, self.rows, vec2(self.size, self.size), false) +// } +// } + +// // new type -fn make_grid( - x: usize, - y: usize, - cell_size: Vec2, - offset_alternating: bool, -) -> Vec { - let x_usize = x; - let y_usize = y; - - (0..x_usize) - .flat_map(|x| { - let x_idx = IdxInRange::new(x, x_usize); - (0..y_usize).map(move |y| { - let y_idx = IdxInRange::new(y, y_usize); - let idx = IdxInRange2d::new_from_idx(x_idx, y_idx); - let ctx = UnitCellExprWorldContext::from_idx2d(idx, 1.0); - - let mut center = if offset_alternating { - let mut center = idx.to_alternating_i().center_of_cell(); - center += vec2(-0.5, 0.0); - if idx.i.i() % 2 == 1 { - center += vec2(0.5, 0.5); - } - - let offset_angle = AnglePi::new(1.0 / 6.0); - let diag_scale = offset_angle.to_norm_dir() * cell_size.x / (100.0 * 0.5); - - center *= diag_scale; - center - } else { - let mut center = idx.center_of_cell(); - center *= vec2(cell_size.x, cell_size.y) / 100.0; - center - }; - - center *= 100.0; - - let transform = SimpleTransform2d::new(vec![ - SimpleTransform2dStep::translate(center), - SimpleTransform2dStep::scale_both(cell_size.x / 100.0), - ]); - - UnitCellContext::new(ctx, transform) - }) - }) - .collect::>() -} +#[derive(Clone, Debug, Default, Livecode, Lerpable)] +pub struct NewTypeWithType(f32); #[derive(Clone, Debug, Default, Livecode, Lerpable)] -pub struct SimpleSquareSequence { - rows: usize, - cols: usize, - size: f32, -} -impl UnitCellCreator for SimpleSquareSequence { - fn to_unit_cell_ctxs(&self) -> Vec { - make_grid(self.cols, self.rows, vec2(self.size, self.size), false) - } -} +pub struct NewTypeWithTypeVec2(Vec2); + +#[derive(Clone, Debug, Default, Livecode, Lerpable)] +pub struct NewTypeWithVec(Vec); + +#[derive(Clone, Debug, Default, LivecodeOnly)] +pub struct NewTypeWithStruct(BasicTypes); + +#[derive(Clone, Debug, Default, LivecodeOnly)] +pub struct NewTypeWithStructLazy(LazyBasicTypes); + +// #[derive(Debug, Clone, Cached)] +// pub struct BirdOutline { + +// back: CachedCompute, +// neck_back: CachedCompute>, + +// } fn main() {} diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_boop.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_boop.rs deleted file mode 100644 index 0f32106..0000000 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_boop.rs +++ /dev/null @@ -1,752 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::parser::*; - -pub(crate) fn update_to_boop_ident(name: syn::Ident) -> syn::Ident { - prefix_ident("Boop", name) -} - -pub(crate) struct BoopFieldType(ControlType); - -impl BoopFieldType { - fn to_token(&self) -> TokenStream2 { - match self.0 { - ControlType::F32 => quote! {murrelet_livecode::boop::BoopState}, - ControlType::F32_2 => quote! {murrelet_livecode::boop::BoopState2}, - ControlType::F32_3 => quote! {murrelet_livecode::boop::BoopState3}, - ControlType::Color => quote! {murrelet_livecode::boop::BoopStateHsva}, - - // nothing fancy here yet either.. - ControlType::LazyNodeF32 => quote! {murrelet_livecode::lazy::LazyNodeF32}, - - // ControlType::LinSrgbaUnclamped => quote!{[murrelet_livecode::livecode::ControlF32; 4]}, - ControlType::Bool => quote! {bool}, // nothing fancy here yet - _ => panic!("boop doesn't have {:?} yet", self.0), - } - } - - fn for_world(&self, idents: StructIdents) -> TokenStream2 { - let name = idents.name(); - let orig_ty = idents.orig_ty(); - let yaml_name = idents.name().to_string(); - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - - match self.0 { - ControlType::F32 => { - quote! {#name: self.#name.boop(&conf.copy_with_new_current_boop(#yaml_name), t, &(target.#name as f32)) as #orig_ty} - } - //ControlType::F32_2 => quote!{#name: self.#name[0].boop(#new_conf, t, target.#name), self.#name[1].boop(conf, t, target.#name[1]))}, - // ControlType::F32_3 => quote!{#name: vec3(self.#name[0].boop(conf, t, target.#name[0]), self.#name[1].boop(conf, t, target.#name[1]), self.#name[2].boop(conf, t, target.#name[2]))}, - // ControlType::LinSrgba => quote!{#name: hsva(self.#name[0].boop(conf, t, target.#name[0]), self.#name[1].boop(conf, t, target.#name[1]), self.#name[2].boop(conf, t, target.#name[2]), self.#name[3].boop(conf, t, target.#name[3])).into_lin_srgba()}, - // ControlType::LinSrgbaUnclamped => quote!{#name: murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.#name)}, - ControlType::Bool => quote! {#name: self.#name.clone()}, // sorry, nothing fancy for bools yet - ControlType::LazyNodeF32 => quote! {#name: self.#name.clone()}, // sorry, nothing fancy for bools yet - - // _ => quote!{#name: self.#name.boop(conf, t, &target.#name) as #orig_ty} // try to convert back to usize/etc - _ => { - let f32_out = match (idents.data.f32min, idents.data.f32max) { - (None, None) => quote! {self.#name.boop(#new_conf, t, &target.#name)}, - (None, Some(max)) => { - quote! {f32::min(self.#name.boop(#new_conf, t, &target.#name), #max)} - } - (Some(min), None) => { - quote! {f32::max(#min, self.#name.boop(#new_conf, t, &target.#name))} - } - (Some(min), Some(max)) => { - quote! {f32::min(f32::max(#min, self.#name.boop(#new_conf, t, &target.#name)), #max)} - } - }; - quote! {#name: #f32_out as #orig_ty} - } - } - } - - fn for_boop_init(&self, idents: StructIdents) -> TokenStream2 { - let name = idents.name(); - // let orig_ty = idents.orig_ty; - // let new_type = idents. - let yaml_name = idents.name().to_string(); - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - - match self.0 { - ControlType::F32 => { - quote! {#name: murrelet_livecode::boop::BoopState::boop_init_at_time(#new_conf, t, &(target.#name as f32))} - } - ControlType::F32_2 => { - quote! {#name: murrelet_livecode::boop::BoopState2::boop_init_at_time(#new_conf, t, &target.#name)} - } - ControlType::F32_3 => { - quote! {#name: murrelet_livecode::boop::BoopState3::boop_init_at_time(#new_conf, t, &target.#name)} - } //vec3(self.#name[0].boop(#new_conf, t, target.#name[0]), self.#name[1].boop(#new_conf, t, target.#name[1]), self.#name[2].boop(#new_conf, t, target.#name[2]))}, - ControlType::Color => { - quote! {#name: murrelet_livecode::boop::BoopStateHsva::boop_init_at_time(#new_conf, t, &target.#name)} - } //hsva(self.#name[0].boop(#new_conf, t, target.#name[0]), self.#name[1].boop(#new_conf, t, target.#name[1]), self.#name[2].boop(#new_conf, t, target.#name[2]), self.#name[3].boop(#new_conf, t, target.#name[3])).into_lin_srgba()}, - // ControlType::LinSrgbaUnclamped => quote!{#name: murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.#name)}, - ControlType::Bool => quote! {#name: target.#name}, // sorry, nothing fancy for bools yet - ControlType::LazyNodeF32 => quote! {#name: target.#name.clone()}, // sorry, nothing fancy for bools yet - // _ => quote!{#name: self.#name.boop(conf, t, &target.#name) as #orig_ty} // try to convert back to usize/etc - _ => panic!("boop doesn't have {:?} yet", self.0), - } - // quote!{#name: self.#name.boop_init_at_time(conf, t, &target.#name)} - } - - fn for_newtype_world(&self, _idents: StructIdents) -> TokenStream2 { - quote! {target.0} - // match self.0 { - // ControlType::F32 => quote!{self.0}, - // //ControlType::F32_2 => quote!{#name: self.#name[0].boop(#new_conf, t, target.#name), self.#name[1].boop(conf, t, target.#name[1]))}, - // // ControlType::F32_3 => quote!{#name: vec3(self.#name[0].boop(conf, t, target.#name[0]), self.#name[1].boop(conf, t, target.#name[1]), self.#name[2].boop(conf, t, target.#name[2]))}, - // // ControlType::LinSrgba => quote!{#name: hsva(self.#name[0].boop(conf, t, target.#name[0]), self.#name[1].boop(conf, t, target.#name[1]), self.#name[2].boop(conf, t, target.#name[2]), self.#name[3].boop(conf, t, target.#name[3])).into_lin_srgba()}, - // // ControlType::LinSrgbaUnclamped => quote!{#name: murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.#name)}, - // ControlType::Bool => quote!{self.0}, // sorry, nothing fancy for bools yet - // // _ => quote!{#name: self.#name.boop(conf, t, &target.#name) as #orig_ty} // try to convert back to usize/etc - // _ => todo!("newtype world") - // } - } - - // this might not access the right place :) - fn for_newtype_boop_init(&self, name: syn::Ident) -> TokenStream2 { - let yaml_name = name.to_string(); - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - - match self.0 { - ControlType::F32 => { - quote! {murrelet_livecode::boop::BoopState::boop_init_at_time(#new_conf, t, &(target.0 as f32))} - } - ControlType::F32_2 => { - quote! {murrelet_livecode::boop::BoopState2::boop_init_at_time(#new_conf, t, &target.0)} - } - ControlType::F32_3 => { - quote! {murrelet_livecode::boop::BoopState3::boop_init_at_time(#new_conf, t, &target.0)} - } //vec3(self.0[0].boop(#new_conf, t, target.0[0]), self.0[1].boop(#new_conf, t, target.0[1]), self.0[2].boop(#new_conf, t, target.0[2]))}, - ControlType::Color => { - quote! {murrelet_livecode::boop::BoopStateHsva::boop_init_at_time(#new_conf, t, &target.0)} - } //hsva(self.0[0].boop(#new_conf, t, target.0[0]), self.0[1].boop(#new_conf, t, target.0[1]), self.0[2].boop(#new_conf, t, target.0[2]), self.0[3].boop(#new_conf, t, target.0[3])).into_lin_srgba()}, - // ControlType::LinSrgbaUnclamped => quote!{0: murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.0)}, - ControlType::Bool => quote! {target.0}, // sorry, nothing fancy for bools yet - ControlType::LazyNodeF32 => quote! { target.0.clone() }, - // _ => quote!{0: self.0.boop(conf, t, &target.0) as #orig_ty} // try to convert back to usize/etc - _ => panic!("boop doesn't have {:?} yet", self.0), - } - // quote!{#name: self.#name.boop_init_at_time(conf, t, &target.#name)} - } -} - -pub(crate) struct FieldTokensBoop { - pub(crate) for_struct: TokenStream2, - pub(crate) for_world: TokenStream2, - pub(crate) for_boop_init: TokenStream2, - pub(crate) for_boop_weird: TokenStream2, -} - -impl GenFinal for FieldTokensBoop { - fn make_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2 { - let new_ident = idents.new_ident; - let name = idents.name; - let vis = idents.vis; - - let for_struct = variants.iter().map(|x| x.for_struct.clone()); - let for_world = variants.iter().map(|x| x.for_world.clone()); - let for_boop_init = variants.iter().map(|x| x.for_boop_init.clone()); - let for_boop_weird = variants.iter().map(|x| x.for_boop_weird.clone()); - - quote! { - #[derive(Debug, Clone)] - #vis struct #new_ident { - #(#for_struct,)* - } - - impl murrelet_livecode::boop::BoopFromWorld<#name> for #new_ident { - fn boop(&mut self, conf: &murrelet_livecode::boop::BoopConf, t: f32, target: &#name) -> #name { - #name { - #(#for_world,)* - } - } - - fn boop_init_at_time(conf: &murrelet_livecode::boop::BoopConf, t: f32, target: &#name) -> Self { - #new_ident { - #(#for_boop_init,)* - } - } - - fn any_weird_states(&self) -> bool { - // todo, not the most efficient but not sure how to #(#something||)* - vec![#(#for_boop_weird,)*].iter().any(|x| *x) - } - } - } - } - - fn make_enum_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2 { - let new_ident = idents.new_ident; - let vis = idents.vis; - let name = idents.name; - - let for_struct = variants.iter().map(|x| x.for_struct.clone()); - let for_world = variants.iter().map(|x| x.for_world.clone()); - let for_boop_init = variants.iter().map(|x| x.for_boop_init.clone()); - let for_boop_weird = variants.iter().map(|x| x.for_boop_weird.clone()); - - quote! { - #[derive(Debug, Clone)] - #[allow(non_camel_case_types)] - #vis enum #new_ident { - #(#for_struct,)* - } - impl murrelet_livecode::boop::BoopFromWorld<#name> for #new_ident { - fn boop(&mut self, conf: &murrelet_livecode::boop::BoopConf, t: f32, target: &#name) -> #name { - match (self, target) { - #(#for_world,)* - _ => { - // // the enum kind changed, so reset - // *self = Self::boop_init_at_time(conf, t, &target); - target.clone() - } - } - } - - fn boop_init_at_time(conf: &murrelet_livecode::boop::BoopConf, t: f32, target: &#name) -> Self { - match target { - #(#for_boop_init,)* - } - } - - fn any_weird_states(&self) -> bool { - match self { - #(#for_boop_weird,)* - } - } - } - } - } - - fn make_newtype_struct_final( - idents: ParsedFieldIdent, - variants: Vec, - ) -> TokenStream2 { - let new_ident = idents.new_ident; - let name = idents.name; - let vis = idents.vis; - - let for_struct = variants.iter().map(|x| x.for_struct.clone()); - let for_world = variants.iter().map(|x| x.for_world.clone()); - let for_boop_init = variants.iter().map(|x| x.for_boop_init.clone()); - let for_boop_weird = variants.iter().map(|x| x.for_boop_weird.clone()); - - quote! { - #[derive(Debug, Clone)] - #vis struct #new_ident(#(#for_struct,)*); - - impl murrelet_livecode::boop::BoopFromWorld<#name> for #new_ident { - fn boop(&mut self, conf: &murrelet_livecode::boop::BoopConf, t: f32, target: &#name) -> #name { - #name(#(#for_world,)*) - } - - fn boop_init_at_time(conf: &murrelet_livecode::boop::BoopConf, t: f32, target: &#name) -> Self { - #new_ident(#(#for_boop_init,)*) - } - - fn any_weird_states(&self) -> bool { - // todo, not the most efficient but not sure how to #(#something||)* - vec![#(#for_boop_weird,)*].iter().any(|x| *x) - } - } - } - } - - fn new_ident(name: syn::Ident) -> syn::Ident { - update_to_boop_ident(name) - } - - // begin parsing the different types of fields - - fn from_newtype_struct(idents: StructIdents, parent_ident: syn::Ident) -> FieldTokensBoop { - // f32, Vec2, etc - - let ctrl = idents.control_type(); - - let for_struct = { - let t = BoopFieldType(ctrl).to_token(); - quote! {#t} - }; - let for_world = BoopFieldType(ctrl).for_newtype_world(idents.clone()); - // send parent, not ident - let for_boop_init = BoopFieldType(ctrl).for_newtype_boop_init(parent_ident.clone()); - let for_boop_weird = if ctrl == ControlType::Bool || ctrl == ControlType::LazyNodeF32 { - quote! {false} - } else { - quote! {self.0.any_weird_states()} - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - // e.g. TileAxisLocs::V(TileAxisVs) - fn from_unnamed_enum(idents: EnumIdents) -> FieldTokensBoop { - let variant_ident = idents.variant_ident(); - let name = idents.enum_ident(); - let new_enum_ident = Self::new_ident(name.clone()); - - let yaml_name = &format!("{}:{}", name, variant_ident); - - let unnamed = idents.data.fields.fields; - - // for struct - if unnamed.len() != 1 { - panic!("multiple fields not supported") - }; - let t = unnamed.first().unwrap().clone().ty; - let parsed_data_type = ident_from_type(&t); - - let is_lazy = parsed_data_type.main_how_to.is_lazy(); - - let (for_struct, for_world, for_boop_init, for_boop_weird) = if is_lazy { - // if it's lazy, we don't support boop on it yet, so just create a placeholder type when it's this variant - let for_struct = quote! { #variant_ident }; - - let for_world = quote! { - (#new_enum_ident::#variant_ident, #name::#variant_ident(tar)) => { - #name::#variant_ident(tar.clone()) - } - }; - - let for_boop_init = - quote! { #name::#variant_ident(targ) => #new_enum_ident::#variant_ident }; - - let for_boop_weird = quote! { #new_enum_ident::#variant_ident => false }; - - (for_struct, for_world, for_boop_init, for_boop_weird) - } else { - let new_type = update_to_boop_ident(parsed_data_type.main_type.clone()); - let for_struct = quote! { #variant_ident(#new_type) }; - - let for_world = quote! { - (#new_enum_ident::#variant_ident(s), #name::#variant_ident(tar)) => { - #name::#variant_ident(s.boop(&conf.copy_with_new_current_boop(#yaml_name), t, &tar)) - } - }; - - let for_boop_init = quote! { #name::#variant_ident(targ) => #new_enum_ident::#variant_ident(#new_type::boop_init_at_time(&conf.copy_with_new_current_boop(#yaml_name), t, &targ)) }; - - let for_boop_weird = - quote! { #new_enum_ident::#variant_ident(s) => s.any_weird_states() }; - - (for_struct, for_world, for_boop_init, for_boop_weird) - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - // e.g. TileAxis::Diag - fn from_unit_enum(idents: EnumIdents) -> FieldTokensBoop { - let variant_ident = idents.variant_ident(); - let name = idents.enum_ident(); - let new_enum_ident = Self::new_ident(name.clone()); - - // no-op - let for_struct = { - quote! { #variant_ident } - }; - let for_world = { - quote! { (#new_enum_ident::#variant_ident, #name::#variant_ident) => #name::#variant_ident } - }; - let for_boop_init = { - quote! { #name::#variant_ident => #new_enum_ident::#variant_ident } - }; - // is never weird - let for_boop_weird = { - quote! { #new_enum_ident::#variant_ident => false } - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - // s: String; - fn from_noop_struct(idents: StructIdents) -> FieldTokensBoop { - let name = idents.name(); - - let for_struct = { - quote! {#name: ()} - }; - let for_world = { - quote! {#name: target.#name.clone()} - }; - let for_boop_init = { - quote! {#name: ()} - }; - - let for_boop_weird = { - quote! {false} - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - // f32, Vec2, etc - fn from_type_struct(idents: StructIdents) -> FieldTokensBoop { - let name = idents.name(); - - let ctrl = idents.control_type(); - - let for_struct = { - let t = BoopFieldType(ctrl).to_token(); - quote! {#name: #t} - }; - let for_world = BoopFieldType(ctrl).for_world(idents.clone()); - let for_boop_init = BoopFieldType(ctrl).for_boop_init(idents.clone()); - let for_boop_weird = if ctrl == ControlType::Bool || ctrl == ControlType::LazyNodeF32 { - quote! {false} - } else { - quote! {self.#name.any_weird_states()} - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - // v: Vec - // no promises about vectors that change over time, but we try - fn from_recurse_struct_vec(idents: StructIdents) -> FieldTokensBoop { - let name = idents.name(); - let orig_ty = idents.orig_ty(); - - let yaml_name = idents.name().to_string(); - - let parsed_type_info = ident_from_type(&orig_ty); - let how_to_control_internal = parsed_type_info.how_to_control_internal(); - let wrapper = parsed_type_info.wrapper_type(); - - let for_struct = { - let internal_type = match how_to_control_internal { - HowToControlThis::WithType(_, c) => BoopFieldType(*c).to_token(), - HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => { - let original_internal_type = parsed_type_info.internal_type(); - let name = Self::new_ident(original_internal_type.clone()); - quote! {#name} - } - HowToControlThis::WithNone(_) => { - let original_internal_type = parsed_type_info.internal_type(); - quote! {#original_internal_type} - } - e => panic!("need vec something {:?}", e), - }; - - let new_ty = match wrapper { - VecDepth::NotAVec => unreachable!("huh, parsing a not-vec in the vec function"), // why is it in this function? - VecDepth::Vec => quote! {Vec<#internal_type>}, - VecDepth::VecVec => quote! {Vec>}, - }; - quote! {#name: #new_ty} - }; - - let for_world = { - if how_to_control_internal.needs_to_be_evaluated() { - match wrapper { - VecDepth::NotAVec => unreachable!(), - VecDepth::Vec => { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - quote! { - #name: { - let (new_x, vals) = murrelet_livecode::boop::combine_boop_vecs_for_world(#new_conf, t, &mut self.#name, &target.#name); - self.#name = new_x; // update the values - vals - } - } - } - VecDepth::VecVec => { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - quote! { - #name: { - let (new_x, vals) = murrelet_livecode::boop::combine_boop_vec_vecs_for_world(#new_conf, t, &mut self.#name, &target.#name); - self.#name = new_x; // update the values - vals - } - } - } - } - } else { - quote! {#name: target.#name.clone()} - } - }; - - let for_boop_init = { - if how_to_control_internal.needs_to_be_evaluated() { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - - match wrapper { - VecDepth::NotAVec => unreachable!(), - VecDepth::Vec => quote! { - #name: { - murrelet_livecode::boop::combine_boop_vecs_for_init(#new_conf, t, &target.#name) - } - }, - VecDepth::VecVec => quote! { - #name: { - let mut result = Vec::with_capacity(self.#name.len()); - for internal_row in &target.#name { - result.push( - murrelet_livecode::boop::combine_boop_vecs_for_init(#new_conf, t, &internal_row) - ) - } - result - } - }, - } - } else { - quote! {#name: target.#name.clone()} - } - }; - - let for_boop_weird = { - if how_to_control_internal.needs_to_be_evaluated() { - match wrapper { - VecDepth::NotAVec => unreachable!(), - VecDepth::Vec => quote! { - self.#name.iter().any(|x| x.any_weird_states() ) - }, - VecDepth::VecVec => quote! { - #name: { - let mut any_weird_states = false; - for internal_row in &self.#name { - any_weird_states &= - internal_row.iter().any(|x| x.any_weird_states() ); - } - result - } - }, - } - } else { - quote! {false} - } - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - fn from_newtype_recurse_struct_vec(idents: StructIdents) -> Self { - let orig_ty = idents.orig_ty(); - - let (for_struct, should_o) = { - let (new_ty, should_o) = { - let (ref_lc_ident, should_o) = if let DataFromType { - second_type: Some(second_ty_ident), - .. - } = ident_from_type(&orig_ty) - { - let infer = HowToControlThis::from_type_str( - second_ty_ident.clone().to_string().as_ref(), - ); - - match infer { - HowToControlThis::WithType(_, c) => (BoopFieldType(c).to_token(), true), - HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => { - // check if this is important!! - // let name = idents.config.new_ident(second_ty_ident.clone()); - let name = Self::new_ident(second_ty_ident.clone()); - (quote! {#name}, true) - } - HowToControlThis::WithNone(_) => (quote! {#second_ty_ident}, false), - e => panic!("need vec something {:?}", e), - } - } else { - panic!("vec missing second type"); - }; - - (quote! {Vec<#ref_lc_ident>}, should_o) - }; - (quote! {#new_ty}, should_o) - }; - let for_world = { - if should_o { - let new_conf = quote! { &conf.clone() }; - quote! { - { - let (new_x, vals) = murrelet_livecode::boop::combine_boop_vecs_for_world(#new_conf, t, &mut self.0, &target.0); - self.0 = new_x; // update the values - vals - } - } - } else { - quote! {target.0.clone()} - } - }; - - let for_boop_init = { - if should_o { - let new_conf = quote! { &conf.clone() }; - quote! { - { - murrelet_livecode::boop::combine_boop_vecs_for_init(#new_conf, t, &target.0) - } - } - } else { - quote! {target.0.clone()} - } - }; - - let for_boop_weird = { - if should_o { - quote! { - self.0.iter().any(|x| x.any_weird_states() ) - } - } else { - quote! {false} - } - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - fn from_recurse_struct_struct(idents: StructIdents) -> FieldTokensBoop { - let name = idents.name(); - let orig_ty = idents.orig_ty(); - let yaml_name = name.to_string(); - - let new_ty = { - let DataFromType { main_type, .. } = ident_from_type(&orig_ty); - let ref_lc_ident = Self::new_ident(main_type.clone()); - quote! {#ref_lc_ident} - }; - - let for_struct = { - quote! {#name: #new_ty} - }; - let for_world = { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - quote! {#name: self.#name.boop(#new_conf, t, &target.#name)} - }; - - let for_boop_init = { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - quote! {#name: #new_ty::boop_init_at_time(#new_conf, t, &target.#name)} - }; - - let for_boop_weird = { - quote! {self.#name.any_weird_states()} - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - // UnitCells - fn from_recurse_struct_unitcell(idents: StructIdents) -> FieldTokensBoop { - // this is similar to vec, but then we rewrap with the UnitCell info - let name = idents.name(); - let orig_ty = idents.orig_ty(); - let yaml_name = name.to_string(); - - let parsed_type_info = ident_from_type(&orig_ty); - let how_to_control_internal = parsed_type_info.how_to_control_internal(); - - let for_struct: TokenStream2 = { - let new_ty = match how_to_control_internal { - HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => { - let internal_type = parsed_type_info.internal_type(); - let name = update_to_boop_ident(internal_type.clone()); - quote! {Vec<#name>} - } - - HowToControlThis::WithRecurse(_, RecursiveControlType::StructLazy) => { - quote! {()} - } - - e => panic!("need boop something {:?}", e), - }; - - quote! {#name: #new_ty} - }; - - let for_world = { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - - if how_to_control_internal.is_lazy() { - quote! {#name: target.#name.clone() } - } else { - quote! { - #name: { - // // split the target into nodes and sequencer - let (targets, details): (Vec<_>, Vec<_>) = target.#name.iter().map(|x| {(*(x.node).clone(), x.detail.clone()) }).unzip(); - let (new_x, vals) = murrelet_livecode::boop::combine_boop_vecs_for_world(#new_conf, t, &mut self.#name, &targets); - self.#name = new_x; // update the values - vals.into_iter().zip(details.into_iter()).map(|(node, detail)| { - murrelet_livecode::unitcells::UnitCell::new(node, detail) - }).collect() - } - } - } - }; - - let for_boop_init = { - if how_to_control_internal.is_lazy() { - quote! {#name: ()} - } else { - let new_conf = quote! {&conf.copy_with_new_current_boop(#yaml_name)}; - - quote! { - #name: { - murrelet_livecode::boop::combine_boop_vecs_for_init(#new_conf, t, &target.#name.iter().map(|x| *(x.node).clone()).collect()) - } - } - } - }; - - let for_boop_weird = { - if how_to_control_internal.is_lazy() { - quote! {false} - } else { - quote! {self.#name.iter().any(|x| x.any_weird_states())} - } - }; - - FieldTokensBoop { - for_struct, - for_world, - for_boop_init, - for_boop_weird, - } - } - - fn from_recurse_struct_lazy(idents: StructIdents) -> Self { - Self::from_noop_struct(idents) - } -} diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_cached.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_cached.rs new file mode 100644 index 0000000..27d94cf --- /dev/null +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_cached.rs @@ -0,0 +1,116 @@ +use darling::{ast, FromDeriveInput, FromField, FromVariant}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::DeriveInput; + +use crate::parser::ident_from_type; + +#[derive(Debug, FromField, Clone)] +#[darling(attributes(cached))] +pub(crate) struct LivecodeFieldReceiver { + pub(crate) ident: Option, + pub(crate) ty: syn::Type, +} + +// for enums +#[derive(Debug, FromVariant, Clone)] +#[darling(attributes(cached))] +pub(crate) struct LivecodeVariantReceiver {} + +#[derive(Debug, Clone, FromDeriveInput)] +#[darling(attributes(cached), supports(struct_named))] +pub(crate) struct LivecodeReceiver { + ident: syn::Ident, + data: ast::Data, +} + +pub fn impl_cache_traits(ast: DeriveInput) -> TokenStream2 { + let ast_receiver = LivecodeReceiver::from_derive_input(&ast).unwrap(); + + match &ast_receiver.data { + ast::Data::Enum(_) => unreachable!("hm, only works on structs"), + ast::Data::Struct(fields) => parse_cache(&ast_receiver.ident, &fields.fields), + } +} + +fn parse_cache(name: &syn::Ident, fields: &[LivecodeFieldReceiver]) -> TokenStream2 { + let mut getter_funcs: Vec = vec![]; + let mut check_funcs: Vec = vec![]; + let mut init_funcs: Vec = vec![]; + let mut to_be_filled_funcs: Vec = vec![]; + let mut conf_arguments: Vec = vec![]; + for f in fields { + if let Some(ident) = &f.ident { + let ident = ident.clone(); + let data = ident_from_type(&f.ty); + + // if it uses our type, we use that is our giveaway + if data.main_type.to_string().eq("CachedCompute") { + // there should be a function called compute_$ident + + let expected_compute_name = format!("compute_{}", ident); + let expected_compute_ident = syn::Ident::new(&expected_compute_name, ident.span()); + + let inside_type = data.inside_type().to_quote(); + + let func = quote! { + pub fn #ident(&self) -> &#inside_type { + self.#ident.get_or_init(|| self.#expected_compute_ident()) + } + }; + + getter_funcs.push(func); + + let check = quote! { + self.#ident.has_been_set() + }; + + check_funcs.push(check); + + let init = quote! { + self.#ident() + }; + + init_funcs.push(init); + + let to_be_filled = quote! { + #ident: CachedCompute::new() + }; + + to_be_filled_funcs.push(to_be_filled); + } else { + // they're passed in with the same name in the arguments + let orig_type = f.ty.clone(); + let new_conf_argument = quote! { + #ident: #orig_type + }; + conf_arguments.push(new_conf_argument); + let to_be_filled = quote! { + #ident + }; + to_be_filled_funcs.push(to_be_filled); + } + } + } + + quote! { + impl #name { + #(#getter_funcs)* + + fn cached_has_been_set(&self) -> bool { + true #( && #check_funcs )* + } + + fn init_all_cached(&self) { + #(#init_funcs;)* + } + + fn new(#(#conf_arguments,)*) -> Self { + #name { + #(#to_be_filled_funcs,)* + } + } + + } + } +} diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_graphics_trait.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_graphics_trait.rs index 39ec5fe..e8acb5d 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_graphics_trait.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_graphics_trait.rs @@ -30,6 +30,7 @@ enum GraphicKind { Drawer, Pipeline, Graphics, + ComputeTexture, // similar to graphics, but is a compute shader that outputs a texture Ref, DrawSrc, } @@ -39,6 +40,7 @@ impl GraphicKind { "drawer" => Self::Drawer, "pipeline" => Self::Pipeline, "graphics" => Self::Graphics, + "computetexture" => Self::ComputeTexture, "ref" => Self::Ref, "draw_src" => Self::DrawSrc, _ => panic!("unexpected kind"), @@ -76,6 +78,7 @@ fn parse_graphics( if let (Some(kind), Some(ident)) = (&f.kind, &f.ident) { let kind = GraphicKind::parse(kind); let ident = ident.clone(); + match kind { GraphicKind::Drawer => drawers.push(quote! {v.push(&self.#ident)}), GraphicKind::Pipeline => { @@ -84,7 +87,7 @@ fn parse_graphics( .expect("that's not a function!"); quote! { - if !#should_run_fn(render_in) { + if #should_run_fn(&livecoder, render_in) { v.push(&self.#ident as &dyn GraphicsRenderer); } } @@ -113,7 +116,7 @@ fn parse_graphics( .expect("that's not a function!"); quote! { - if !#should_run_fn(render_in) { + if !#should_run_fn(&livecoder, render_in) { v.push(&self.#ident as &dyn GraphicsRenderer); } } @@ -138,14 +141,23 @@ fn parse_graphics( let ctrl_ident = syn::Ident::new(ctrl_, name.span()); let ident_str = ident.to_string(); ctrl.push(quote! { - v.extend(ControlGraphicsRef::new( - #ident_str, - Box::new(livecoder.#ctrl_ident.clone()), - Some(self.#ident.clone()), - ).into_iter()) - }) + v.extend( + ControlGraphicsRef::new( + #ident_str, + Box::new(livecoder.#ctrl_ident.clone()), + Some(self.#ident.clone()), + ) + .into_iter() + .map(|c| Box::new(c) as Box) + ); + }); } } + GraphicKind::ComputeTexture => { + ctrl.push( + quote! {v.extend(self.#ident.control_graphics(&livecoder).into_iter())}, + ); + } } } } @@ -158,14 +170,14 @@ fn parse_graphics( #(#drawers;)* v } - fn gpu_pipelines(&self, render_in: &GraphicsRenderIn) -> Vec<&dyn GraphicsRenderer> { + fn gpu_pipelines<'a, 'b, 'c>(&'a self, livecoder: &'b #ctrlcls, render_in: &'c GraphicsRenderIn) -> Vec<&'a (dyn GraphicsRenderer + 'a)> { let mut v: Vec<&dyn GraphicsRenderer> = vec![]; #(#pipelines;)* v } - fn control_graphics<'a>(&'a self, livecoder: &'a #ctrlcls) -> Vec { - let mut v: Vec = vec![]; + fn control_graphics(&self, livecoder: &#ctrlcls) -> Vec> { + let mut v: Vec> = vec![]; #(#ctrl;)* v } diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_lazy.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_lazy.rs index 6a18683..5adc75a 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_lazy.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_lazy.rs @@ -15,19 +15,21 @@ impl LazyFieldType { ControlType::Bool => quote! {murrelet_livecode::lazy::LazyNodeF32}, // we'll just check if it's above 0 ControlType::F32 => quote! {murrelet_livecode::lazy::LazyNodeF32}, ControlType::F32_2 => { - quote! {Vec} + quote! {murrelet_livecode::lazy::LazyVec2} } ControlType::F32_3 => { - quote! {Vec} + quote! {murrelet_livecode::lazy::LazyVec3} } ControlType::Color => { - quote! {Vec} + quote! {murrelet_livecode::lazy::LazyMurreletColor} } ControlType::LazyNodeF32 => { // already lazy... quote! { murrelet_livecode::lazy::LazyNodeF32 } } - // ControlType::LinSrgbaUnclamped => quote!{[murrelet_livecode::livecode::ControlF32; 4]}, + ControlType::AnglePi => { + quote! { murrelet_livecode::lazy::LazyNodeF32 } + } _ => panic!("unitcell doesn't have this one yet"), } } @@ -40,15 +42,19 @@ impl LazyFieldType { ) -> TokenStream2 { match self.0 { ControlType::F32_2 => { - quote! { murrelet_livecode::lazy::eval_lazy_vec2(#ident, ctx) } + quote! { #ident.eval_lazy(ctx) } } ControlType::F32_3 => { - quote! { murrelet_livecode::lazy::eval_lazy_vec3(#ident, ctx) } + quote! { #ident.eval_lazy(ctx) } } ControlType::Color => { - quote! { murrelet_livecode::lazy::eval_lazy_color(#ident, ctx) } + quote! { #ident.eval_lazy(ctx) } } ControlType::Bool => quote! {#ident.eval_lazy(ctx)? > 0.0}, + ControlType::AnglePi => { + // for number-like things, we also enable clamping! (it's a bit experimental though, be careful) + quote! {murrelet_common::AnglePi::new(#ident.eval_lazy(ctx)?)} + } _ => { // for number-like things, we also enable clamping! (it's a bit experimental though, be careful) let f32_out = match (f32min, f32max) { @@ -69,16 +75,19 @@ impl LazyFieldType { let orig_ty = idents.orig_ty(); match self.0 { ControlType::F32_2 => { - quote! { #name: glam::vec2(self.#name[0].eval_lazy(ctx)? as f32, self.#name[1].eval_lazy(ctx)? as f32)} + quote! { #name: self.#name.eval_lazy(ctx)? } } ControlType::F32_3 => { - quote! {#name: glam::vec3(self.#name[0].eval_lazy(ctx)? as f32, self.#name[1].eval_lazy(ctx)? as f32, self.#name[2].eval_lazy(ctx)? as f32)} + quote! { #name: self.#name.eval_lazy(ctx)? } } ControlType::Color => { - quote! {#name: murrelet_common::MurreletColor::hsva(self.#name[0].eval_lazy(ctx)? as f32, self.#name[1].eval_lazy(ctx)? as f32, self.#name[2].eval_lazy(ctx)? as f32, self.#name[3].eval_lazy(ctx)? as f32)} + quote! { #name: self.#name.eval_lazy(ctx)? } } ControlType::Bool => quote! {#name: self.#name.eval_lazy(ctx)? > 0.0}, ControlType::LazyNodeF32 => quote! {#name: self.#name.add_more_defs(ctx)? }, + ControlType::AnglePi => { + quote! {#name: murrelet_common::AnglePi::new(self.#name.eval_lazy(ctx)?)} + } _ => { // for number-like things, we also enable clamping! (it's a bit experimental though, be careful) let f32_out = match (idents.data.f32min, idents.data.f32max) { @@ -94,19 +103,76 @@ impl LazyFieldType { } } + fn for_world_option(&self, idents: StructIdents) -> TokenStream2 { + let name = idents.name(); + let orig_ty = idents.orig_ty(); + match self.0 { + ControlType::F32_2 => { + quote! { #name: self.#name.map(|name| glam::vec2(name[0].eval_lazy(ctx)? as f32, name[1].eval_lazy(ctx)? as f32))} + } + ControlType::F32_3 => { + quote! {#name: self.#name.map(|name| glam::vec3(name[0].eval_lazy(ctx)? as f32, name[1].eval_lazy(ctx)? as f32, name[2].eval_lazy(ctx)? as f32))} + } + ControlType::Color => { + quote! {#name: self.#name.map(|name| murrelet_common::MurreletColor::hsva(name[0].eval_lazy(ctx)? as f32, name[1].eval_lazy(ctx)? as f32, name[2].eval_lazy(ctx)? as f32, name[3].eval_lazy(ctx)? as f32))} + } + ControlType::Bool => quote! {#name: self.#name.map(|name| name.eval_lazy(ctx)? > 0.0)}, + ControlType::AnglePi => { + quote! {#name: self.#name.map(|name| murrelet_common::AnglePi::new(name.eval_lazy(ctx)?))} + } + ControlType::LazyNodeF32 => { + quote! {#name: { + if let Some(name) = &self.#name { + let a = name.add_more_defs(ctx)?; + Some(a) + } else { + None + } + } + } + } + _ => { + // for number-like things, we also enable clamping! (it's a bit experimental though, be careful) + let f32_out = match (idents.data.f32min, idents.data.f32max) { + (None, None) => quote! { + if let Some(name) = &self.#name { + let n = name.eval_lazy(ctx)?; + Some(n) + } else { + None + } + }, + (None, Some(max)) => { + quote! {f32::min(self.#name.map(|name| name.eval_lazy(ctx)?, #max))} + } + (Some(min), None) => { + quote! {f32::max(#min, self.#name.map(|name| name.eval_lazy(ctx)?))} + } + (Some(min), Some(max)) => { + quote! {f32::min(f32::max(#min, self.#name.map(|name| name.eval_lazy(ctx)?), #max))} + } + }; + quote! {#name: #f32_out as #orig_ty} + } + } + } + fn for_newtype_world(&self, idents: StructIdents) -> TokenStream2 { let orig_ty = idents.orig_ty(); match self.0 { ControlType::F32_2 => { - quote! {vec2(self.0[0].eval_lazy(ctx)? as f32, self.0[1].eval_lazy(ctx)? as f32)} + quote! { self.0.eval_lazy(ctx)? } + } + ControlType::F32_3 => { + quote! { self.0.eval_lazy(ctx)? } } - // ControlType::F32_3 => quote!{murrelet_livecode::livecode::ControlF32::vec3(&self.0, w)}, ControlType::Color => { - quote! {MurreletColor::hsva(self.0[0].eval_lazy(ctx)? as f32, self.0[1].eval_lazy(ctx)? as f32, self.0[2].eval_lazy(ctx)? as f32, self.0[3].eval_lazy(ctx)? as f32)} + quote! { self.0.eval_lazy(ctx)? } } - // ControlType::LinSrgbaUnclamped => quote!{murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.0, w)}, ControlType::Bool => quote! {self.0.eval_lazy(ctx)? > 0.0}, - // _ => quote!{self.0.eval_lazy(ctx)? as #orig_ty} + ControlType::AnglePi => { + quote! {murrelet_common::AnglePi::new(self.0.eval_lazy(ctx)?)} + } _ => { let f32_out = match (idents.data.f32min, idents.data.f32max) { (None, None) => quote! {self.0.eval_lazy(ctx)?}, @@ -125,6 +191,7 @@ impl LazyFieldType { pub(crate) struct FieldTokensLazy { pub(crate) for_struct: TokenStream2, pub(crate) for_world: TokenStream2, + pub(crate) for_more_defs: TokenStream2, } impl GenFinal for FieldTokensLazy { fn make_newtype_struct_final( @@ -137,6 +204,7 @@ impl GenFinal for FieldTokensLazy { let for_struct = variants.iter().map(|x| x.for_struct.clone()); let for_world = variants.iter().map(|x| x.for_world.clone()); + let for_more_defs = variants.iter().map(|x| x.for_more_defs.clone()); quote! { #[derive(Debug, Clone, Default, murrelet_livecode_derive::LivecodeOnly)] @@ -147,6 +215,9 @@ impl GenFinal for FieldTokensLazy { fn eval_lazy(&self, ctx: &murrelet_livecode::expr::MixedEvalDefs) -> murrelet_livecode::types::LivecodeResult<#name> { Ok(#name(#(#for_world,)*)) } + fn with_more_defs(&self, ctx: &murrelet_livecode::expr::MixedEvalDefs) -> murrelet_livecode::types::LivecodeResult { + Ok(Self(#(#for_more_defs,)*)) + } } } @@ -159,6 +230,7 @@ impl GenFinal for FieldTokensLazy { let for_struct = variants.iter().map(|x| x.for_struct.clone()); let for_world = variants.iter().map(|x| x.for_world.clone()); + let for_more_defs = variants.iter().map(|x| x.for_more_defs.clone()); quote! { #[derive(Debug, Clone, Default, murrelet_livecode_derive::LivecodeOnly)] @@ -173,6 +245,12 @@ impl GenFinal for FieldTokensLazy { #(#for_world,)* }) } + + fn with_more_defs(&self, ctx: &murrelet_livecode::expr::MixedEvalDefs) -> murrelet_livecode::types::LivecodeResult { + Ok(Self { + #(#for_more_defs,)* + }) + } } } } @@ -185,6 +263,7 @@ impl GenFinal for FieldTokensLazy { let for_struct = variants.iter().map(|x| x.for_struct.clone()); let for_world = variants.iter().map(|x| x.for_world.clone()); + let for_more_defs = variants.iter().map(|x| x.for_more_defs.clone()); quote! { #[derive(Debug, Clone, Default, murrelet_livecode_derive::LivecodeOnly)] @@ -204,10 +283,70 @@ impl GenFinal for FieldTokensLazy { #(#for_world,)* }) } + + fn with_more_defs(&self, ctx: &murrelet_livecode::expr::MixedEvalDefs) -> murrelet_livecode::types::LivecodeResult { + Ok(match self { + #new_enum_ident::DefaultNoop => #new_enum_ident::DefaultNoop, + #(#for_more_defs,)* + }) + } } } } + fn from_newtype_struct_lazy(idents: StructIdents, _parent_ident: syn::Ident) -> Self { + let orig_ty = idents.orig_ty(); + let parsed_type_info = ident_from_type(&orig_ty); + let internal_type = parsed_type_info.main_type; + + let for_struct = { + let new_inside_type = Self::new_ident(internal_type.clone()); + quote! {#new_inside_type} + }; + + let for_world = { + quote! { self.0.clone() } + }; + + let for_more_defs = { + quote! { self.0.with_more_defs(ctx)? } + }; + + FieldTokensLazy { + for_struct, + for_world, + for_more_defs, + } + } + + fn from_newtype_struct_struct( + idents: StructIdents, + _parent_ident: syn::Ident, + ) -> FieldTokensLazy { + let orig_ty = idents.orig_ty(); + let parsed_type_info = ident_from_type(&orig_ty); + let internal_type = parsed_type_info.main_type; + + let for_struct = { + let new_inside_type = Self::new_ident(internal_type.clone()); + quote! {#new_inside_type} + }; + + let for_world = { + quote! { self.0.eval_lazy(ctx)? } + }; + + let for_more_defs = { + quote! { self.0.for_more_defs(ctx)? } + }; + + FieldTokensLazy { + for_struct, + for_world, + for_more_defs, + } + } + fn from_newtype_struct(idents: StructIdents, _parent_idents: syn::Ident) -> FieldTokensLazy { let ctrl = idents.control_type(); @@ -218,9 +357,14 @@ impl GenFinal for FieldTokensLazy { let for_world = LazyFieldType(ctrl).for_newtype_world(idents.clone()); + let for_more_defs = { + quote! { self.0.with_more_defs(ctx)? } + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -259,9 +403,16 @@ impl GenFinal for FieldTokensLazy { quote! { #new_enum_ident::#variant_ident(s) => #name::#variant_ident(s.eval_lazy(ctx)?) } }; + let for_more_defs = if is_lazy { + quote! { #new_enum_ident::#variant_ident(s) => #new_enum_ident::#variant_ident(s.clone()) } + } else { + quote! { #new_enum_ident::#variant_ident(s) => #new_enum_ident::#variant_ident(s.with_more_defs(ctx)?) } + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -277,9 +428,14 @@ impl GenFinal for FieldTokensLazy { quote! { #new_enum_ident::#variant_ident => #name::#variant_ident } }; + let for_more_defs: TokenStream2 = { + quote! { #new_enum_ident::#variant_ident => #new_enum_ident::#variant_ident } + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -295,9 +451,14 @@ impl GenFinal for FieldTokensLazy { quote! {#name: self.#name.clone()} }; + let for_more_defs: TokenStream2 = { + quote! { #name: self.#name.clone() } + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -312,11 +473,42 @@ impl GenFinal for FieldTokensLazy { quote! {#back_to_quote #name: #t} }; + let _for_world = LazyFieldType(ctrl).for_world(idents.clone()); + let for_world = LazyFieldType(ctrl).for_world(idents.clone()); + let for_more_defs = { + quote! { #name: self.#name.with_more_defs(ctx)? } + }; FieldTokensLazy { for_struct, for_world, + for_more_defs, + } + } + + fn from_option(idents: StructIdents) -> Self { + let name = idents.name(); + let back_to_quote = idents.back_to_quote(); + + let s = ident_from_type(&idents.orig_ty()); + + let ctrl = s.second_how_to.unwrap().get_control_type(); + + let for_struct = { + let t = LazyFieldType(ctrl).to_token(); + quote! {#back_to_quote #name: Option<#t>} + }; + + let for_world = LazyFieldType(ctrl).for_world_option(idents.clone()); + let for_more_defs = { + quote! { #name: if let Some(value) = &self.#name { Some(value.with_more_defs(ctx)?) } else { None } } + }; + + FieldTokensLazy { + for_struct, + for_world, + for_more_defs, } } @@ -343,13 +535,18 @@ impl GenFinal for FieldTokensLazy { let name = Self::new_ident(target_type.clone()); quote! {#name} } - e => panic!("need vec something {:?}", e), + e => panic!("lazy1 need vec something {:?}", e), }; let new_ty = match wrapper { VecDepth::NotAVec => unreachable!("huh, parsing a not-vec in the vec function"), // why is it in this function? - VecDepth::Vec => quote! {Vec<#internal_type>}, + VecDepth::Vec => { + quote! {Vec>>} + } VecDepth::VecVec => todo!(), + VecDepth::VecControlVec => { + quote! { Vec>>> } + } }; quote! {#back_to_quote #name: #new_ty} }; @@ -357,20 +554,39 @@ impl GenFinal for FieldTokensLazy { match how_to_control_internal { HowToControlThis::WithType(_, c) => { // local variable... - let x = syn::Ident::new("x", idents.name().span()); - let c = - LazyFieldType(*c).for_world_func(x, idents.data.f32min, idents.data.f32max); - quote! {#name: self.#name.iter().map(|x| #c).collect::, _>>()?} + let x_ident = syn::Ident::new("x", idents.name().span()); + let c_expr = LazyFieldType(*c).for_world_func( + x_ident.clone(), + idents.data.f32min, + idents.data.f32max, + ); + quote! { + #name: { + let expanded = murrelet_livecode::types::lazy_expand_vec_list(&self.#name, ctx)?; + expanded + .into_iter() + .map(|#x_ident| #c_expr) + .collect::, _>>()? + } + } } HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => { - quote! {#name: self.#name.iter().map(|x| x.eval_lazy(ctx)).collect::, _>>()?} + quote! { + #name: { + let expanded = murrelet_livecode::types::lazy_expand_vec_list(&self.#name, ctx)?; + expanded + .into_iter() + .map(|x| x.eval_lazy(ctx)) + .collect::, _>>()? + } + } } HowToControlThis::WithNone(_) => { let target_type = parsed_type_info.internal_type(); let name = Self::new_ident(target_type.clone()); quote! {#name: self.#name.clone()} } - e => panic!("need vec something {:?}", e), + e => panic!("lazy2 need vec something {:?}", e), } // match wrapper { @@ -382,9 +598,17 @@ impl GenFinal for FieldTokensLazy { // } }; + let for_more_defs = quote! { + #name: self.#name + .iter() + .map(|item| item.with_more_defs(ctx)) + .collect::>>()? + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -407,7 +631,7 @@ impl GenFinal for FieldTokensLazy { let name = Self::new_ident(internal_type); quote! {#name} } - e => panic!("need vec something {:?}", e), + e => panic!("lazy3 need vec something {:?}", e), }; quote! {Vec<#new_ty>} @@ -415,10 +639,14 @@ impl GenFinal for FieldTokensLazy { let for_world = { quote! {self.0.iter().map(|x| x.eval_lazy(ctx)).collect::, _>>()?} }; + let for_more_defs = { + quote! { self.0.iter().map(|x| x.with_more_defs(ctx)).collect::>>()? } + }; FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -464,9 +692,14 @@ impl GenFinal for FieldTokensLazy { } }; + let for_more_defs = { + quote! { #name: self.#name.clone() } + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } @@ -489,9 +722,14 @@ impl GenFinal for FieldTokensLazy { quote! {#name: self.#name.eval_lazy(ctx)?} }; + let for_more_defs = { + quote! { #name: self.#name.with_more_defs(ctx)? } + }; + FieldTokensLazy { for_struct, for_world, + for_more_defs, } } diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_livecode.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_livecode.rs index 63fc17c..95a1102 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_livecode.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_livecode.rs @@ -7,6 +7,7 @@ pub(crate) fn update_to_control_ident(name: syn::Ident) -> syn::Ident { prefix_ident("Control", name) } +#[derive(Debug)] pub(crate) struct LivecodeFieldType(pub ControlType); impl LivecodeFieldType { @@ -18,7 +19,22 @@ impl LivecodeFieldType { ControlType::F32_3 => quote! {[murrelet_livecode::livecode::ControlF32; 3]}, ControlType::Color => quote! {[murrelet_livecode::livecode::ControlF32; 4]}, ControlType::ColorUnclamped => quote! {[murrelet_livecode::livecode::ControlF32; 4]}, - // ControlType::EvalExpr => quote! {murrelet_livecode::expr::ControlExprF32}, + ControlType::AnglePi => quote! {murrelet_livecode::livecode::ControlF32}, + ControlType::LazyNodeF32 => quote! {murrelet_livecode::lazy::ControlLazyNodeF32}, + } + } + + pub fn to_token_lazy(&self) -> TokenStream2 { + match self.0 { + ControlType::F32_2 => quote! { murrelet_livecode::lazy::ControlLazyVec2 }, + ControlType::F32_3 => quote! { murrelet_livecode::lazy::ControlLazyVec3 }, + ControlType::Color => quote! { murrelet_livecode::lazy::ControlMurreletColor }, + ControlType::ColorUnclamped => { + todo!() //quote! { Vec } + } + ControlType::F32 => quote! {murrelet_livecode::livecode::ControlF32}, + ControlType::Bool => quote! {murrelet_livecode::livecode::ControlBool}, + ControlType::AnglePi => quote! {murrelet_livecode::livecode::ControlF32}, ControlType::LazyNodeF32 => quote! {murrelet_livecode::lazy::ControlLazyNodeF32}, } } @@ -41,6 +57,9 @@ impl LivecodeFieldType { quote! {murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.#name, w)?} } ControlType::LazyNodeF32 => quote! {self.#name.o(w)?}, + ControlType::AnglePi => { + quote! {murrelet_common::AnglePi::new(self.#name.o(w)?)} + } _ => { let f32_out = match (f32min, f32max) { (None, None) => quote! {self.#name.o(w)?}, @@ -55,6 +74,60 @@ impl LivecodeFieldType { } } + // usually can call for_world directly, but this is useful in Vec<> + pub(crate) fn for_world_no_name_option( + &self, + name: syn::Ident, + orig_ty: syn::Type, + f32min: Option, + f32max: Option, + ) -> TokenStream2 { + match self.0 { + ControlType::F32_2 => quote! {self.#name.map(|name| name.o(w)?)}, + ControlType::F32_3 => quote! {self.#name.map(|name| name.o(w)?)}, + ControlType::Color => quote! {self.#name.map(|name| name.o(w)?)}, + ControlType::ColorUnclamped => { + quote! {self.#name.map(|name| murrelet_livecode::livecode::ControlF32::hsva_unclamped(&name, w)?)} + } + ControlType::LazyNodeF32 => quote! { + if let Some(name) = &self.#name { + let a = name.o(w)?; + Some(a) + } else { + None + } + }, + ControlType::AnglePi => { + quote! { + if let Some(name) = &self.#name { + let a = name.o(w)?; + Some(murrelet_common::AnglePi::new(a)) + } else { + None + } + } + } + _ => { + let f32_out = match (f32min, f32max) { + (None, None) => quote! { + if let Some(name) = &self.#name { + let n = name.o(w)?; + Some(n) + } else { + None + } + }, + (None, Some(max)) => quote! {f32::min(self.#name.map(|name| name.o(w)?), #max)}, + (Some(min), None) => quote! {f32::max(#min, self.#name.map(|name| name.o(w)?))}, + (Some(min), Some(max)) => { + quote! {f32::min(f32::max(#min, self.#name.map(|name| name.o(w)?)), #max)} + } + }; + quote! {#f32_out as #orig_ty} + } + } + } + pub(crate) fn for_world(&self, idents: StructIdents) -> TokenStream2 { let name = idents.name(); let rest = self.for_world_no_name( @@ -66,6 +139,17 @@ impl LivecodeFieldType { quote! {#name: #rest} } + pub(crate) fn for_world_option(&self, idents: StructIdents) -> TokenStream2 { + let name = idents.name(); + let rest = self.for_world_no_name_option( + idents.name(), + idents.orig_ty(), + idents.data.f32min, + idents.data.f32max, + ); + quote! {#name: #rest} + } + pub(crate) fn for_newtype_world(&self, idents: StructIdents) -> TokenStream2 { let orig_ty = idents.orig_ty(); match self.0 { @@ -76,6 +160,9 @@ impl LivecodeFieldType { ControlType::ColorUnclamped => { quote! {murrelet_livecode::livecode::ControlF32::hsva_unclamped(&self.0, w)?} } + ControlType::AnglePi => { + quote! {murrelet_common::AnglePi::new(self.0.o(w)?)} + } _ => { let f32_out = match (idents.data.f32min, idents.data.f32max) { (None, None) => quote! {self.0.o(w)?}, @@ -124,14 +211,17 @@ impl GenFinal for FieldTokensLivecode { let for_variable_idents = variants.iter().map(|x| x.for_variable_idents.clone()); let for_function_idents = variants.iter().map(|x| x.for_function_idents.clone()); - let maybe_cfg_attr = if cfg!(feature = "schemars") { - quote! {, schemars::JsonSchema} + let (maybe_cfg_attr, additional) = if cfg!(feature = "schemars") { + ( + quote! {, schemars::JsonSchema}, + quote! {#[schemars(deny_unknown_fields)]}, + ) } else { - quote! {} + (quote! {}, quote! {}) }; - quote! { #[derive(Debug, Clone, serde::Deserialize #maybe_cfg_attr)] + #additional #vis struct #new_ident { #(#for_struct,)* } @@ -190,14 +280,17 @@ impl GenFinal for FieldTokensLivecode { let enum_tag = idents.tags; - let maybe_cfg_attr = if cfg!(feature = "schemars") { - quote! {, schemars::JsonSchema} + let (maybe_cfg_attr, additional) = if cfg!(feature = "schemars") { + ( + quote! {, schemars::JsonSchema}, + quote! {#[schemars(deny_unknown_fields)]}, + ) } else { - quote! {} + (quote! {}, quote! {}) }; - quote! { #[derive(Debug, Clone, serde::Deserialize #maybe_cfg_attr)] + #additional #[allow(non_camel_case_types)] #enum_tag #vis enum #new_ident { @@ -249,14 +342,18 @@ impl GenFinal for FieldTokensLivecode { let for_variable_idents = variants.iter().map(|x| x.for_variable_idents.clone()); let for_function_idents = variants.iter().map(|x| x.for_function_idents.clone()); - let maybe_cfg_attr = if cfg!(feature = "schemars") { - quote! {, schemars::JsonSchema} + let (maybe_cfg_attr, additional) = if cfg!(feature = "schemars") { + ( + quote! {, schemars::JsonSchema}, + quote! {#[schemars(deny_unknown_fields)]}, + ) } else { - quote! {} + (quote! {}, quote! {}) }; quote! { #[derive(Debug, Clone, serde::Deserialize #maybe_cfg_attr)] + #additional #vis struct #new_ident(#(#for_struct,)*); impl murrelet_livecode::livecode::LivecodeFromWorld<#name> for #new_ident { @@ -320,6 +417,53 @@ impl GenFinal for FieldTokensLivecode { } } + fn from_newtype_struct_lazy(idents: StructIdents, parent_ident: syn::Ident) -> Self { + Self::from_newtype_struct_struct(idents, parent_ident) + } + + fn from_newtype_struct_struct( + idents: StructIdents, + _parent_ident: syn::Ident, + ) -> FieldTokensLivecode { + // we need to get the internal struct type + let orig_ty = idents.orig_ty(); + let parsed_type_info = ident_from_type(&orig_ty); + let internal_type = parsed_type_info.main_type; + + let for_struct = { + if internal_type.to_string().starts_with("Lazy") { + catch_special_types(internal_type) + } else { + let t = Self::new_ident(internal_type); + quote! {#t} + } + }; + let for_world = { + quote! { self.0.o(w)? } + }; + + let for_to_control = { + quote! { self.0.to_control() } + }; + + let (for_variable_idents, for_function_idents) = if idents.how_to_control_this_is_none() { + (quote! { vec![] }, quote! { vec![] }) + } else { + ( + quote! {self.0.variable_identifiers()}, + quote! {self.0.function_identifiers()}, + ) + }; + + FieldTokensLivecode { + for_struct, + for_world, + for_to_control, + for_variable_idents, + for_function_idents, + } + } + // e.g. TileAxisLocs::V(TileAxisVs) fn from_unnamed_enum(idents: EnumIdents) -> FieldTokensLivecode { let variant_ident = idents.variant_ident(); @@ -447,6 +591,40 @@ impl GenFinal for FieldTokensLivecode { } } + fn from_option(idents: StructIdents) -> FieldTokensLivecode { + let serde = idents.serde().clone(); + let name = idents.name().clone(); + + let s = ident_from_type(&idents.orig_ty()); + + let ctrl = s.second_how_to.unwrap().get_control_type(); + let for_struct = { + let t = LivecodeFieldType(ctrl).to_token(); + quote! {#serde #name: Option<#t>} + }; + let for_world = LivecodeFieldType(ctrl).for_world_option(idents.clone()); + + let for_to_control = LivecodeFieldType(ctrl).for_control(idents.clone()); + + // we'll just use the trait (i want to try it for the above, but we'll come back to that!) + let (for_variable_idents, for_function_idents) = if idents.how_to_control_this_is_none() { + (quote! { vec![] }, quote! { vec![] }) + } else { + ( + quote! {self.#name.variable_identifiers()}, + quote! {self.#name.function_identifiers()}, + ) + }; + + FieldTokensLivecode { + for_struct, + for_world, + for_to_control, + for_variable_idents, + for_function_idents, + } + } + // Vec, Vec fn from_recurse_struct_vec(idents: StructIdents) -> FieldTokensLivecode { let serde = idents.serde(); @@ -457,9 +635,20 @@ impl GenFinal for FieldTokensLivecode { let how_to_control_internal = parsed_type_info.how_to_control_internal(); let wrapper = parsed_type_info.wrapper_type(); - let for_struct = { + let inner_is_lazy_struct = parsed_type_info + .second_how_to + .map(|h| h.is_lazy()) + .unwrap_or(false); + + let for_struct: TokenStream2 = { let src_type = match how_to_control_internal { - HowToControlThis::WithType(_, c) => LivecodeFieldType(*c).to_token(), + HowToControlThis::WithType(_, c) => { + if inner_is_lazy_struct { + LivecodeFieldType(*c).to_token_lazy() + } else { + LivecodeFieldType(*c).to_token() + } + } HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => { let target_type = parsed_type_info.internal_type(); let name = Self::new_ident(target_type.clone()); @@ -467,23 +656,39 @@ impl GenFinal for FieldTokensLivecode { } HowToControlThis::WithRecurse(_, RecursiveControlType::StructLazy) => { let original_internal_type = parsed_type_info.internal_type(); - let name = Self::new_ident(original_internal_type.clone()); - quote! {#name} + + catch_special_types(original_internal_type) } + // HowToControlThis::WithRecurse(_, RecursiveControlType::Vec) => { + // // for things like Lazy Vec2... + // println!("parsed_type_info {:?}", parsed_type_info); + // let original_internal_type = parsed_type_info.internal_type(); + // let lazy_inner = Self::new_ident(original_internal_type.clone()); + // quote! { Vec<#lazy_inner> } + // } HowToControlThis::WithNone(_) => { let target_type = parsed_type_info.internal_type(); quote! {#target_type} } - e => panic!("need vec something {:?}", e), + e => panic!("(livecode, recurse_struct_vec) need vec something {:?}", e), + }; + + let vec_elem_type: TokenStream2 = if inner_is_lazy_struct { + quote! {murrelet_livecode::types::DeserLazyControlVecElement} + } else { + quote! {murrelet_livecode::types::ControlVecElement} }; let new_ty = match wrapper { VecDepth::NotAVec => unreachable!("not a vec in a vec?"), VecDepth::Vec => { - quote! { Vec> } + quote! { Vec<#vec_elem_type<#src_type>> } } VecDepth::VecVec => { - quote! { Vec>> } + quote! { Vec>> } + } + VecDepth::VecControlVec => { + quote! { Vec<#vec_elem_type>> } } }; @@ -495,13 +700,18 @@ impl GenFinal for FieldTokensLivecode { match wrapper { VecDepth::NotAVec => unreachable!("not a vec in a vec?"), VecDepth::Vec => { - quote! { - #name: self.#name.iter() - .map(|x| x.eval_and_expand_vec(w)) - .collect::, _>>()? - .into_iter() - .flatten() - .collect()} + if inner_is_lazy_struct { + quote! { + #name: self.#name.iter() + .map(|x| x.o(w)) + .collect::, _>>()? + + } + } else { + quote! { + #name: murrelet_livecode::types::eval_and_expand_vec_list(&self.#name, w)? + } + } } VecDepth::VecVec => { quote! { @@ -509,34 +719,31 @@ impl GenFinal for FieldTokensLivecode { let mut result = Vec::with_capacity(self.#name.len()); for internal_row in &self.#name { result.push( - internal_row.iter() - .map(|x| x.eval_and_expand_vec(w)) - .collect::, _>>()? - .into_iter() - .flatten() - .collect() + murrelet_livecode::types::eval_and_expand_vec_list(internal_row, w)? ) } result } } } + + VecDepth::VecControlVec => { + quote! { + #name: { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + // DeserLazyControlVecElement -> LazyControlVecElement + let item = internal_row.o(w)?; + result.push(item) + } + result + } + } + } } } else { quote! {#name: self.#name.clone()} } - - // match infer { - // HowToControlThis::WithType(_, _c) => { - // quote! {#name: self.#name.iter().map(|x| x.eval_and_expand_vec(w, #debug_name)).collect::, _>>()?.into_iter().flatten().collect()} - // } - // HowToControlThis::WithRecurse(_, _) => { - // quote! {#name: self.#name.iter().map(|x| x.eval_and_expand_vec(w, #debug_name)).collect::, _>>()?.into_iter().flatten().collect()} - // } - // HowToControlThis::WithNone(_) => { - // quote! {#name: self.#name.clone()} - // } - // } }; let for_to_control = { @@ -544,18 +751,69 @@ impl GenFinal for FieldTokensLivecode { match wrapper { VecDepth::NotAVec => unreachable!("not a vec in a vec?"), VecDepth::Vec => { - quote! { #name: self.#name.iter().map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())).collect::>() } + if inner_is_lazy_struct { + quote! { #name: self.#name.iter().map(|x| x.to_control()).collect::>() } + } else { + quote! { #name: self.#name.iter().map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())).collect::>() } + } } VecDepth::VecVec => { - quote! { - #name: { - let mut result = Vec::with_capacity(self.#name.len()); - for internal_row in &self.#name { - result.push( - internal_row.iter().map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())).collect::>() - ) + if inner_is_lazy_struct { + quote! { + #name: { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + + let item = internal_row.to_control(); + + result.push(murrelet_livecode::types::DeserLazyControlVecElement::raw(item)) + } + result + } + } + } else { + quote! { + #name: { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + result.push( + internal_row + .iter() + .map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())) + .collect::>() + ) + } + result + } + } + } + } + VecDepth::VecControlVec => { + if inner_is_lazy_struct { + quote! { + #name: { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + let c = internal_row.to_control(); + result.push(c) + } + result + } + } + } else { + quote! { + #name: { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + result.push( + internal_row + .iter() + .map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())) + .collect::>() + ) + } + result } - result } } } @@ -563,18 +821,6 @@ impl GenFinal for FieldTokensLivecode { } else { quote! {#name: self.#name.clone()} } - - // match infer { - // HowToControlThis::WithType(_, _c) => { - // quote! {#name: self.#name.iter().map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())).collect::>()} - // } - // HowToControlThis::WithRecurse(_, _) => { - // quote! {#name: self.#name.iter().map(|x| murrelet_livecode::types::ControlVecElement::raw(x.to_control())).collect::>()} - // } - // HowToControlThis::WithNone(_) => { - // quote! {#name: self.#name.clone()} - // } - // } }; let for_variable_idents = { @@ -597,6 +843,20 @@ impl GenFinal for FieldTokensLivecode { } } } + VecDepth::VecControlVec => { + quote! { + { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + let items = internal_row.variable_identifiers(); + result.extend( + items + ); + } + result + } + } + } } } else { quote! {vec![]} @@ -623,6 +883,18 @@ impl GenFinal for FieldTokensLivecode { } } } + VecDepth::VecControlVec => { + quote! { + { + let mut result = Vec::with_capacity(self.#name.len()); + for internal_row in &self.#name { + let item = internal_row.function_identifiers(); + result.extend(item); + } + result + } + } + } } } else { quote! {vec![]} @@ -646,7 +918,6 @@ impl GenFinal for FieldTokensLivecode { let for_struct = { let new_ty = { let DataFromType { main_type, .. } = ident_from_type(&orig_ty); - // let ref_lc_ident = idents.config.new_ident(main_type.clone()); let ref_lc_ident = Self::new_ident(main_type.clone()); quote! {#ref_lc_ident} }; @@ -724,6 +995,7 @@ impl GenFinal for FieldTokensLivecode { .unwrap_or(quote! {""}); let for_world = { + // todo, these look like the same if how_to_control_internal.is_lazy() { quote! {#name: { murrelet_livecode::unitcells::TmpUnitCells::new( @@ -808,13 +1080,28 @@ impl GenFinal for FieldTokensLivecode { let original_internal_type = parsed_type_info.internal_type(); quote! {#original_internal_type} } - e => panic!("need vec something {:?}", e), + e => panic!( + "(livecode, newtype_recurse_struct_vec) need vec something {:?}", + e + ), + }; + + let inner_is_lazy_struct = parsed_type_info + .second_how_to + .map(|h| h.is_lazy()) + .unwrap_or(false); + + let vec_elem_type: TokenStream2 = if inner_is_lazy_struct { + quote! {murrelet_livecode::types::DeserLazyControlVecElement} + } else { + quote! {murrelet_livecode::types::ControlVecElement} }; let new_ty = match wrapper { VecDepth::NotAVec => unreachable!("huh, parsing a not-vec in the vec function"), // why is it in this function? VecDepth::Vec => quote! {Vec<#internal_type>}, VecDepth::VecVec => quote! {Vec>}, + VecDepth::VecControlVec => quote! { Vec<#vec_elem_type>> }, }; quote! {#serde #new_ty} }; @@ -826,6 +1113,7 @@ impl GenFinal for FieldTokensLivecode { quote! {self.0.iter().map(|x| x.o(w)).collect::, _>>()?} } VecDepth::VecVec => unimplemented!(), + VecDepth::VecControlVec => unimplemented!(), } } else { quote! {self.0.clone()} @@ -866,6 +1154,47 @@ impl GenFinal for FieldTokensLivecode { } fn from_recurse_struct_lazy(idents: StructIdents) -> Self { - Self::from_recurse_struct_struct(idents) + let serde = idents.serde(); + let name = idents.name(); + let orig_ty = idents.orig_ty(); + + let for_struct = { + let new_ty = { + let DataFromType { main_type, .. } = ident_from_type(&orig_ty); + // eh, this is hacky + catch_special_types(main_type.clone()) + }; + + quote! {#serde #name: #new_ty} + }; + let for_world = { + quote! {#name: self.#name.o(w)?} + }; + let for_to_control = { + quote! {#name: self.#name.to_control()} + }; + + let for_variable_idents = quote! { self.#name.variable_identifiers() }; + let for_function_idents = quote! { self.#name.function_identifiers() }; + + FieldTokensLivecode { + for_struct, + for_world, + for_to_control, + for_variable_idents, + for_function_idents, + } + } +} + +fn catch_special_types(original_internal_type: syn::Ident) -> TokenStream2 { + let ctrl_ident = update_to_control_ident(original_internal_type.clone()); + + match original_internal_type.to_string().as_str() { + "LazyVec2" => quote! { murrelet_livecode::lazy::ControlLazyVec2 }, + "LazyNodeF32" => quote! { murrelet_livecode::lazy::ControlLazyNodeF32 }, + "LazyVec3" => quote! { murrelet_livecode::lazy::ControlLazyVec3 }, + "LazyMurreletColor" => quote! { murrelet_livecode::lazy::ControlLazyMurreletColor }, + _ => quote! { #ctrl_ident }, } } diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_nestedit.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_nestedit.rs index f0009d5..347d83e 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_nestedit.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/derive_nestedit.rs @@ -134,9 +134,18 @@ impl GenFinal for FieldTokensNestEdit { name.clone() } - fn from_newtype_struct(_idents: StructIdents, parent_ident: syn::Ident) -> FieldTokensNestEdit { - // let name = idents.control_type(); + fn from_newtype_struct_lazy(idents: StructIdents, parent_ident: syn::Ident) -> Self { + Self::from_newtype_struct_struct(idents, parent_ident) + } + + fn from_newtype_struct_struct( + idents: StructIdents, + parent_ident: syn::Ident, + ) -> FieldTokensNestEdit { + Self::from_newtype_struct(idents, parent_ident) + } + fn from_newtype_struct(_idents: StructIdents, parent_ident: syn::Ident) -> FieldTokensNestEdit { // these will fall to todo!() let for_nestedit = quote! { #parent_ident(self.0.nest_update(mods)) @@ -287,6 +296,37 @@ impl GenFinal for FieldTokensNestEdit { } } + fn from_option(idents: StructIdents) -> Self { + let name = idents.name(); + let yaml_name = name.to_string(); + + // we'll just use the trait! (unless it's none, then we bail + let for_nestedit = match idents.how_to_control_this() { + HowToControlThis::WithNone(_) => quote! { + #name: self.#name.clone() + }, + _ => quote! { + #name: self.#name.as_ref().map(|name| name.nest_update(mods.next_loc(#yaml_name))) + }, + }; + + let for_nestedit_get = quote! { + [#yaml_name, rest @ ..] => self.#name.as_ref().map(|name| name.nest_get(rest)).unwrap_or(Ok("".to_string())) + }; + + let for_nestedit_get_newtype = quote! { + _ => self.#name.map(|name| name.nest_get(getter)).unwrap_or(Ok("".to_string())) + }; + + FieldTokensNestEdit { + kind: "type struct".to_owned(), + for_nestedit, + for_nestedit_get, + for_nestedit_get_newtype: Some(for_nestedit_get_newtype), + for_nestedit_get_flatten: None, + } + } + // v: Vec fn from_recurse_struct_vec(idents: StructIdents) -> FieldTokensNestEdit { let name = idents.name(); diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/lib.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/lib.rs index c262859..614442d 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/lib.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/lib.rs @@ -5,7 +5,7 @@ extern crate proc_macro; -mod derive_boop; +mod derive_cached; mod derive_graphics_trait; mod derive_lazy; mod derive_livecode; @@ -14,7 +14,6 @@ mod parser; mod toplevel; use darling::FromDeriveInput; -use derive_boop::FieldTokensBoop; use derive_graphics_trait::impl_graphics_trait; use derive_lazy::FieldTokensLazy; use derive_livecode::FieldTokensLivecode; @@ -23,10 +22,12 @@ use parser::{GenFinal, LivecodeReceiver}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use syn::parse_macro_input; -use toplevel::{impl_all_the_traits, top_level_livecode}; +use toplevel::{impl_all_the_traits, top_level_livecode, top_level_livecode_json}; use quote::quote; +use crate::derive_cached::impl_cache_traits; + fn livecode_parse_ast(rec: LivecodeReceiver) -> TokenStream2 { FieldTokensLivecode::from_ast(rec) } @@ -35,10 +36,6 @@ fn lazy_parse_ast(rec: LivecodeReceiver) -> TokenStream2 { FieldTokensLazy::from_ast(rec) } -fn boop_parse_ast(rec: LivecodeReceiver) -> TokenStream2 { - FieldTokensBoop::from_ast(rec) -} - fn nestedit_parse_ast(rec: LivecodeReceiver) -> TokenStream2 { FieldTokensNestEdit::from_ast(rec) } @@ -69,10 +66,7 @@ pub fn murrelet_livecode_derive_livecode(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); let ast_receiver = LivecodeReceiver::from_derive_input(&ast).unwrap(); - // livecode_parse_ast(ast_receiver.clone()).into() - // and then i realized i still need nested and lerpable too.... - let livecode = livecode_parse_ast(ast_receiver.clone()); let nested = nestedit_parse_ast(ast_receiver.clone()); @@ -90,13 +84,6 @@ pub fn murrelet_livecode_derive_lazy(input: TokenStream) -> TokenStream { lazy_parse_ast(ast_receiver.clone()).into() } -#[proc_macro_derive(Boop, attributes(livecode))] -pub fn murrelet_livecode_derive_boop(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as syn::DeriveInput); - let ast_receiver = LivecodeReceiver::from_derive_input(&ast).unwrap(); - boop_parse_ast(ast_receiver.clone()).into() -} - #[proc_macro_derive(NestEdit, attributes(livecode))] pub fn murrelet_livecode_derive_nestedit(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); @@ -111,6 +98,12 @@ pub fn murrelet_livecode_top_level_livecode(input: TokenStream) -> TokenStream { top_level_livecode(ast.ident).into() } +#[proc_macro_derive(TopLevelLiveCodeJson, attributes(livecode))] +pub fn murrelet_livecode_top_level_livecode_json(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as syn::DeriveInput); + top_level_livecode_json(ast.ident).into() +} + #[proc_macro_derive(LiveCoderTrait, attributes(livecode))] pub fn murrelet_livecode_livecoder_traits(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); @@ -122,3 +115,9 @@ pub fn murrelet_livecode_graphics(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); impl_graphics_trait(ast).into() } + +#[proc_macro_derive(Cached, attributes(cached))] +pub fn murrelet_cache_compute(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as syn::DeriveInput); + impl_cache_traits(ast).into() +} diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/parser.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/parser.rs index 842cda7..89e2559 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/parser.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/parser.rs @@ -25,6 +25,8 @@ where Self: Sized, { fn from_newtype_struct(_idents: StructIdents, parent_ident: syn::Ident) -> Self; + fn from_newtype_struct_struct(idents: StructIdents, parent_ident: syn::Ident) -> Self; + fn from_newtype_struct_lazy(idents: StructIdents, parent_ident: syn::Ident) -> Self; fn from_newtype_recurse_struct_vec(_idents: StructIdents) -> Self; fn from_unnamed_enum(idents: EnumIdents) -> Self; fn from_unit_enum(idents: EnumIdents) -> Self; @@ -34,10 +36,12 @@ where fn from_recurse_struct_struct(idents: StructIdents) -> Self; fn from_recurse_struct_unitcell(idents: StructIdents) -> Self; fn from_recurse_struct_lazy(idents: StructIdents) -> Self; + fn from_option(idents: StructIdents) -> Self; fn from_ast(ast_receiver: LivecodeReceiver) -> TokenStream2 { match ast_receiver.data { ast::Data::Enum(_) => Self::make_enum(&ast_receiver), + // it's a newtype! ast::Data::Struct(ast::Fields { style: ast::Style::Tuple, .. @@ -113,6 +117,12 @@ where } Self::from_recurse_struct_unitcell(idents) } + HowToControlThis::WithRecurse(_, RecursiveControlType::Option) => { + if DEBUG_THIS { + println!("-> from_option"); + } + Self::from_option(idents) + } } }) .collect::>(); @@ -211,11 +221,27 @@ where } Self::from_newtype_recurse_struct_vec(idents) } + HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => { + if DEBUG_THIS { + println!("-> from_newtype_struct_struct"); + } + Self::from_newtype_struct_struct(idents, name.clone()) + } + HowToControlThis::WithRecurse(_, RecursiveControlType::StructLazy) => { + if DEBUG_THIS { + println!("-> from_newtype_struct_struct"); + } + Self::from_newtype_struct_lazy(idents, name.clone()) + } + // creating a : Something in livecode // HowToControlThis::WithRecurse(_, RecursiveControlType::Struct) => Self::from_recurse_struct_struct(idents), // dealing with UnitCell // HowToControlThis::WithRecurse(_, RecursiveControlType::UnitCell) => Self::from_recurse_struct_unitcell(idents), - _ => panic!("newtype for this kind isn't implemented yet"), + _ => panic!( + "{:?} for this kind isn't implemented yet", + field.how_to_control_this() + ), } }) .collect::>(); @@ -348,7 +374,7 @@ impl LivecodeFieldReceiver { } SerdeDefault::DefaultImpl => quote! {#[serde(default)]}, SerdeDefault::Empty => { - // nace and general + // nice and general quote! {#[serde(default="murrelet_livecode::livecode::empty_vec")]} } _ => { @@ -356,18 +382,26 @@ impl LivecodeFieldReceiver { let how = self.how_to_control_this(); if is_lazy { - let serde_func = match &how { - HowToControlThis::WithRecurse(_, RecursiveControlType::Vec) => { - // weird and hardcoded for things like Lazy Vec2, which get turned into Vec... - serde.from_control_type(ControlType::LazyNodeF32, true) - } - _ => serde.from_control_type(how.get_control_type(), false), - }; - let r = lazy_version_of_default_serde(&serde_func); - - quote! {#[serde(default=#r)]} + if matches!( + how, + HowToControlThis::WithRecurse(_, RecursiveControlType::StructLazy) + ) { + quote! { #[serde(default)] } + } else { + let serde_func = match &how { + HowToControlThis::WithRecurse(_, RecursiveControlType::Vec) => { + // weird and hardcoded for things like Lazy Vec2, which get turned into Vec... + serde.from_control_type(ControlType::LazyNodeF32, true) + } + _ => serde.from_control_type(how.get_control_type(), false), + }; + let r = lazy_version_of_default_serde(&serde_func); + + quote! {#[serde(default=#r)]} + } } else { let r = serde.from_control_type(how.get_control_type(), false); + quote! {#[serde(default=#r)]} } } @@ -513,6 +547,7 @@ pub enum ControlType { Color, ColorUnclamped, LazyNodeF32, + AnglePi, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -520,8 +555,9 @@ pub(crate) enum RecursiveControlType { Struct, StructLazy, // just a way to stop some features from propogating.. Vec, - UnitCell, // special type that builds up an expression context - // Array, + UnitCell, + Option, // special type that builds up an expression context + // Array, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -582,6 +618,7 @@ impl HowToControlThis { OverrideOrInferred::Override, RecursiveControlType::Vec, ), + "a" => HowToControlThis::WithType(OverrideOrInferred::Override, ControlType::AnglePi), // "expr" => { // HowToControlThis::WithType(OverrideOrInferred::Override, ControlType::EvalExpr) // } @@ -610,6 +647,10 @@ impl HowToControlThis { OverrideOrInferred::Inferred, RecursiveControlType::Vec, ), + "Option" => HowToControlThis::WithRecurse( + OverrideOrInferred::Inferred, + RecursiveControlType::Option, + ), "Vec3" => HowToControlThis::WithType(OverrideOrInferred::Inferred, ControlType::F32_3), "String" => HowToControlThis::WithNone(OverrideOrInferred::Inferred), // some special types from this library @@ -624,6 +665,9 @@ impl HowToControlThis { "LazyNodeF32" => { HowToControlThis::WithType(OverrideOrInferred::Inferred, ControlType::LazyNodeF32) } + "AnglePi" => { + HowToControlThis::WithType(OverrideOrInferred::Inferred, ControlType::AnglePi) + } // _ => HowToControlThis::WithNone(OverrideOrInferred::Inferred) _ => { if value.starts_with("Lazy") { @@ -658,6 +702,9 @@ impl SerdeDefault { (ControlType::F32, SerdeDefault::Zeros, _) => { "murrelet_livecode::livecode::_auto_default_f32_0".to_string() } + (ControlType::AnglePi, SerdeDefault::Zeros, _) => { + "murrelet_livecode::livecode::_auto_default_f32_0".to_string() + } (ControlType::F32, SerdeDefault::Ones, _) => { "murrelet_livecode::livecode::_auto_default_f32_1".to_string() } @@ -718,10 +765,12 @@ pub(crate) struct DataFromType { pub(crate) main_type: syn::Ident, pub(crate) second_type: Option, pub(crate) third_type: Option, // so we coulddd use a vec her + pub(crate) fourth_type: Option, // so we coulddd use a vec her pub(crate) main_how_to: HowToControlThis, pub(crate) second_how_to: Option, pub(crate) third_how_to: Option, // so we coulddd use a vec her + pub(crate) fourth_how_to: Option, // so we coulddd use a vec her } impl DataFromType { fn new_from_list(types: Vec) -> DataFromType { @@ -730,6 +779,7 @@ impl DataFromType { let main_type = types[0].clone(); let second_type = types.get(1).cloned(); let third_type = types.get(2).cloned(); + let fourth_type = types.get(3).cloned(); let main_how_to = HowToControlThis::from_type_str(&main_type.to_string()); let second_how_to = second_type @@ -738,19 +788,26 @@ impl DataFromType { let third_how_to = third_type .as_ref() .map(|x| HowToControlThis::from_type_str(&x.to_string())); + let fourth_how_to = fourth_type + .as_ref() + .map(|x| HowToControlThis::from_type_str(&x.to_string())); Self { main_type, second_type, third_type, + fourth_type, main_how_to, second_how_to, third_how_to, + fourth_how_to, } } pub(crate) fn how_to_control_internal(&self) -> &HowToControlThis { - if let Some(third) = &self.third_how_to { + if let Some(fourth) = &self.fourth_how_to { + fourth + } else if let Some(third) = &self.third_how_to { third } else if let Some(second) = &self.second_how_to { second @@ -777,18 +834,58 @@ impl DataFromType { Some(HowToControlThis::WithRecurse(_, RecursiveControlType::Vec)) => { VecDepth::VecVec } - Some(_) => VecDepth::Vec, + Some(_) => { + // let s = self.second_type.as_ref().map(|x| x.to_string()).clone(); + if matches!( + self.third_how_to, + Some(HowToControlThis::WithRecurse(_, RecursiveControlType::Vec)) + ) { + VecDepth::VecControlVec + } else { + VecDepth::Vec + } + } None => unreachable!("vec should have a type??"), } } _ => VecDepth::NotAVec, } } + + pub(crate) fn inside_type(&self) -> Self { + Self { + main_type: self.second_type.clone().unwrap(), + second_type: self.third_type.clone(), + third_type: self.fourth_type.clone(), + fourth_type: None, + main_how_to: self.second_how_to.unwrap(), + second_how_to: self.third_how_to, + third_how_to: self.fourth_how_to, + fourth_how_to: None, + } + } + + pub(crate) fn to_quote(&self) -> TokenStream2 { + let main_type = self.main_type.clone(); + match (&self.second_type, &self.third_type, &self.fourth_type) { + (None, None, None) => quote! { #main_type }, + (Some(second_type), None, None) => quote! { #main_type<#second_type> }, + (Some(second_type), Some(third_type), None) => { + quote! { #main_type<#second_type<#third_type>> } + } + (Some(second_type), Some(third_type), Some(fourth_type)) => { + quote! { #main_type<#second_type<#third_type<#fourth_type>>> } + } + _ => unreachable!(), + } + } } +#[derive(Debug)] pub(crate) enum VecDepth { NotAVec, Vec, + VecControlVec, // nested vec, but outside is a control vec VecVec, } @@ -798,7 +895,9 @@ pub fn recursive_ident_from_path(t: &syn::Type, acc: &mut Vec) { let s = path.segments.last().unwrap(); let main_type = s.ident.clone(); - acc.push(main_type); + if main_type != "WrappedLazyType" { + acc.push(main_type); + } if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, diff --git a/murrelet_livecode_macros/murrelet_livecode_derive/src/toplevel.rs b/murrelet_livecode_macros/murrelet_livecode_derive/src/toplevel.rs index 0701e97..e4a9b4b 100644 --- a/murrelet_livecode_macros/murrelet_livecode_derive/src/toplevel.rs +++ b/murrelet_livecode_macros/murrelet_livecode_derive/src/toplevel.rs @@ -15,8 +15,43 @@ pub(crate) fn top_level_livecode(ident: syn::Ident) -> TokenStream2 { impl LiveCoderLoader for #control_ident { fn _app_config(&self) -> &murrelet_perform::perform::ControlAppConfig { &self.app } - fn parse(text: &str) -> Result { - serde_yaml::from_str(&text) + fn parse(text: &str) -> murrelet_livecode::types::LivecodeResult { + serde_yaml::from_str(&text).map_err(|err| { + let line = if let Some(location) = err.location() { + format!("{},{}", location.line(), location.column()) + } else { + "".to_owned() + }; + murrelet_livecode::types::LivecodeError::SerdeLoc(line, err.to_string()) + }) + } + } + + impl murrelet_perform::perform::ConfCommon for #conf_ident { + fn config_app_loc(&self) -> &murrelet_perform::perform::AppConfig { &self.app } + } + + impl murrelet_perform::perform::CommonTrait for #conf_ident {} + impl murrelet_perform::perform::CommonTrait for #control_ident {} + impl murrelet_perform::perform::LiveCodeCommon<#conf_ident> for #control_ident {} + } +} + +// ugly quick fix, just generate this code +pub(crate) fn top_level_livecode_json(ident: syn::Ident) -> TokenStream2 { + let conf_ident = ident.clone(); + let control_ident = update_to_control_ident(ident.clone()); + + quote! { + type LiveCode = LiveCoder<#conf_ident, #control_ident>; + + impl LiveCoderLoader for #control_ident { + fn _app_config(&self) -> &murrelet_perform::perform::ControlAppConfig { &self.app } + + fn parse(text: &str) -> murrelet_livecode::types::LivecodeResult { + serde_json::from_str(&text).map_err(|err| { + LivecodeError::JsonParse(err.to_string()) + }) } } diff --git a/murrelet_perform/Cargo.toml b/murrelet_perform/Cargo.toml index d9f8916..c814239 100644 --- a/murrelet_perform/Cargo.toml +++ b/murrelet_perform/Cargo.toml @@ -18,22 +18,26 @@ schemars = [ ] [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } -murrelet_livecode = { version = "0.1.2", path = "../murrelet_livecode/", default-features = false } -murrelet_draw = { version = "0.1.2", path = "../murrelet_draw/", default-features = false } -murrelet_livecode_macros = { version = "0.1.2", path = "../murrelet_livecode_macros/", default-features = false } -murrelet_livecode_derive = { version = "0.1.2", path = "../murrelet_livecode_macros/murrelet_livecode_derive/", default-features = false } -lerpable = "0.0.2" +murrelet_common = { workspace = true } +murrelet_livecode = { workspace = true, default-features = false } +murrelet_draw = { workspace = true, default-features = false } +murrelet_livecode_macros = { workspace = true, default-features = false } +murrelet_livecode_derive = { workspace = true, default-features = false } +lerpable = { version = "0.0.3" } serde = { version = "1.0.104", features = ["derive"] } serde_yaml = "0.9.17" evalexpr = "11.1.0" rand = "0.8" itertools = "0.10.5" regex = "1.7.3" -glam = "0.28.0" +glam = { version = "0.28.0", features = ["serde"] } palette = "0.7.6" anyhow = "1.0.86" schemars = { version = "0.8.21", optional = true } +murrelet_gui = { workspace = true, features = ["glam"] } clap = { version = "4.5.23", features = ["derive"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { workspace = true } diff --git a/murrelet_perform/src/asset_loader.rs b/murrelet_perform/src/asset_loader.rs index f55b2b4..583573a 100644 --- a/murrelet_perform/src/asset_loader.rs +++ b/murrelet_perform/src/asset_loader.rs @@ -2,12 +2,46 @@ use std::{collections::HashMap, path::Path}; use itertools::Itertools; use lerpable::Lerpable; -use murrelet_common::{Asset, Assets}; +use murrelet_common::{ + Assets, JsonAssetLookup, RasterAsset, RasterAssetLookup, VectorAsset, VectorLayersAssetLookup, +}; +use murrelet_gui::CanMakeGUI; use murrelet_livecode_derive::Livecode; -pub trait AssetLoader { +pub trait VectorAssetLoader { fn is_match(&self, file_extension: &str) -> bool; - fn load(&self, layers: &[&str], filename: &Path) -> Asset; + fn load(&self, layers: &[&str], filename: &Path) -> VectorAsset; +} + +pub trait RasterAssetLoader { + fn is_match(&self, file_extension: &str) -> bool; + fn load(&self, filename: &Path) -> RasterAsset; +} + +#[derive(Livecode, Lerpable, Clone, Debug)] +pub struct JsonStringFile { + #[livecode(kind = "none")] + name: String, + #[livecode(kind = "none")] + content: String, + // probably will want to add something to normalize the nums coming in... +} +impl JsonStringFile { + pub fn path(&self) -> &Path { + Path::new(&self.name) + } +} + +#[derive(Livecode, Lerpable, Clone, Debug)] +pub struct RasterFile { + #[livecode(kind = "none")] + name: String, + // probably will want to add something to normalize the nums coming in... +} +impl RasterFile { + pub fn path(&self) -> &Path { + Path::new(&self.name) + } } #[derive(Livecode, Lerpable, Clone, Debug)] @@ -27,35 +61,70 @@ impl PolylineLayerFile { } pub fn _empty_filenames() -> ControlAssetFilenames { - ControlAssetFilenames { files: vec![] } + ControlAssetFilenames { + vector_files: vec![], + raster_files: vec![], + json_files: vec![], + } } pub fn _empty_filenames_lazy() -> ControlLazyAssetFilenames { - ControlLazyAssetFilenames { files: vec![] } + ControlLazyAssetFilenames { + vector_files: vec![], + raster_files: vec![], + json_files: vec![], + } +} + +pub struct AssetLoaders { + vector: Vec>, + raster: Vec>, +} + +impl AssetLoaders { + pub fn new( + vector: Vec>, + raster: Vec>, + ) -> Self { + Self { vector, raster } + } + + pub fn empty() -> AssetLoaders { + Self { + vector: vec![], + raster: vec![], + } + } } #[derive(Livecode, Lerpable, Clone, Debug)] pub struct AssetFilenames { // hmm, the parsers are all in a different part of the code - files: Vec, + vector_files: Vec, + raster_files: Vec, + json_files: Vec, // just load as a string } impl AssetFilenames { pub fn empty() -> Self { - Self { files: Vec::new() } + Self { + vector_files: Vec::new(), + raster_files: Vec::new(), + json_files: Vec::new(), + } } - pub fn load(&self, load_funcs: &[Box]) -> Assets { + pub fn load_polylines(&self, load_funcs: &AssetLoaders) -> Assets { let mut m = HashMap::new(); - for filename in &self.files { + for filename in &self.vector_files { let path = filename.path(); - println!("loading file {:?}", filename.path()); + println!("loading vector file {:?}", filename.path()); // depending on the filetype... if let Some(ext) = path.extension() { let ext_str = ext.to_str(); - for func in load_funcs { + for func in &load_funcs.vector { if func.is_match(ext_str.unwrap()) { let filename_stem = path .file_stem() @@ -69,6 +138,39 @@ impl AssetFilenames { } } - Assets::new(m) + let polylines = VectorLayersAssetLookup::new(m); + + let mut raster = RasterAssetLookup::empty(); + for filename in &self.raster_files { + let path = filename.path(); + println!("loading raster file {:?}", filename.path()); + + if let Some(ext) = path.extension() { + let ext_str = ext.to_str(); + for func in &load_funcs.raster { + if func.is_match(ext_str.unwrap()) { + let filename_stem = path + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .into_owned(); + raster.insert(filename_stem, func.load(path)); + } + } + } + } + + let mut json = JsonAssetLookup::empty(); + for s in &self.json_files { + json.insert(s.name.clone(), s.content.clone()); + } + + Assets::new(polylines, raster, json) + } +} + +impl CanMakeGUI for AssetFilenames { + fn make_gui() -> murrelet_gui::MurreletGUISchema { + murrelet_gui::MurreletGUISchema::Skip } } diff --git a/murrelet_perform/src/cli.rs b/murrelet_perform/src/cli.rs index 2a6da81..4908089 100644 --- a/murrelet_perform/src/cli.rs +++ b/murrelet_perform/src/cli.rs @@ -1,8 +1,59 @@ -use std::path::PathBuf; +use std::{path::PathBuf, str::FromStr}; use clap::Parser; -#[derive(Parser, Debug)] +#[derive(Debug, Clone, Copy)] +pub struct TextureDimensions { + pub width: u32, + pub height: u32, +} + +impl TextureDimensions { + pub fn as_dims(&self) -> [u32; 2] { + [self.width, self.height] + } +} + +impl FromStr for TextureDimensions { + type Err = String; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split('x').collect(); + if parts.len() != 2 { + return Err("Dimensions must be in format WIDTHxHEIGHT".to_string()); + } + + let width = parts[0].parse::().map_err(|_| "Invalid width")?; + let height = parts[1].parse::().map_err(|_| "Invalid height")?; + + Ok(TextureDimensions { width, height }) + } +} + +impl ToString for TextureDimensions { + fn to_string(&self) -> String { + format!("{}x{}", self.width, self.height) + } +} + +impl Default for TextureDimensions { + fn default() -> Self { + Self { + // width: 3000, + // height: 2000, + // width: 3840, + // height: 1646, + width: 800, + height: 800, + // width: 2000, + // height: 2000, + // width: 750, + // height: 750, + } + } +} + +#[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None, allow_hyphen_values = true)] pub struct BaseConfigArgs { pub config_path: PathBuf, @@ -10,6 +61,32 @@ pub struct BaseConfigArgs { #[arg(long, help = "record video")] pub capture: bool, + #[arg(short, long, default_value_t = Default::default())] + pub resolution: TextureDimensions, // window resolution + #[arg(long, default_value_t = 1, value_parser = clap::value_parser!(u32).range(1..=8))] + pub texture_multiplier: u32, // controls number of pixels the shaders work on + + #[arg(long)] + pub earlystop: Option, + #[arg(trailing_var_arg = true)] pub sketch_args: Vec, } +impl BaseConfigArgs { + pub fn texture_dims(&self) -> TextureDimensions { + TextureDimensions { + width: self.resolution.width * self.texture_multiplier, + height: self.resolution.height * self.texture_multiplier, + } + } + + #[allow(dead_code)] + pub(crate) fn config_path(&self) -> PathBuf { + self.config_path.clone() + } + + #[allow(dead_code)] + pub(crate) fn should_capture(&self) -> bool { + self.capture + } +} diff --git a/murrelet_perform/src/load.rs b/murrelet_perform/src/load.rs index 7262804..e86d17d 100644 --- a/murrelet_perform/src/load.rs +++ b/murrelet_perform/src/load.rs @@ -29,11 +29,11 @@ pub fn preprocess_yaml>(text: &str, loc: P) -> String let pattern = r"(?m)^( *)\[\[([^\[\]]+)\]\]"; - let re = Regex::new(&pattern).unwrap(); + let re = Regex::new(pattern).unwrap(); let mut new_text = text.to_owned(); - for cap in re.captures_iter(&text) { + for cap in re.captures_iter(text) { // println!("cap {:?}", cap); let spaces = cap[1].len(); let filename = cap[2].to_string(); diff --git a/murrelet_perform/src/perform.rs b/murrelet_perform/src/perform.rs index 27336dc..83b5b37 100644 --- a/murrelet_perform/src/perform.rs +++ b/murrelet_perform/src/perform.rs @@ -1,15 +1,17 @@ #![allow(dead_code)] -use glam::{vec3, Mat4, Vec2}; +use glam::{vec2, Vec2}; use lerpable::Lerpable; -use murrelet_common::{Assets, AssetsRef, LivecodeUsage}; +use murrelet_common::{ + Assets, AssetsRef, LivecodeUsage, LivecodeValue, SimpleTransform2d, SimpleTransform2dStep, +}; use murrelet_common::{LivecodeSrc, LivecodeSrcUpdateInput, MurreletAppInput}; use murrelet_common::{MurreletColor, TransformVec2}; -use murrelet_livecode::lazy::ControlLazyNodeF32; +use murrelet_gui::MurreletGUI; +use murrelet_livecode::expr::{MixedEvalDefs, MixedEvalDefsRef}; +use murrelet_livecode::lazy::{ControlLazyMurreletColor, ControlLazyNodeF32, LazyNodeF32}; use murrelet_livecode::state::{LivecodeTimingConfig, LivecodeWorldState}; -use murrelet_livecode::types::{ - AdditionalContextNode, ControlVecElement, LivecodeError, LivecodeResult, -}; -use std::collections::HashMap; +use murrelet_livecode::types::{AdditionalContextNode, LivecodeResult}; +use std::collections::{HashMap, HashSet}; use std::fs; use murrelet_common::run_id; @@ -20,7 +22,7 @@ use murrelet_livecode::livecode::*; use murrelet_livecode_derive::Livecode; use crate::asset_loader::*; -use crate::cli::BaseConfigArgs; +use crate::cli::{BaseConfigArgs, TextureDimensions}; use crate::reload::*; use clap::Parser; @@ -37,20 +39,32 @@ pub trait ConfCommon: CommonTrait { fn config_app_loc(&self) -> &AppConfig; } +#[derive(Debug, Clone, Copy, Livecode, MurreletGUI, Lerpable)] +pub enum SvgSaveKind { + HTML, + Inkscape, +} + #[derive(Clone, Debug)] pub struct SvgDrawConfig { - size: f32, + size: f32, // todo, what's the difference between this and texture sizes? + pub resolution: Option, capture_path: Option, // if it's missing, we don't save (e.g. web browser) frame: u64, target_size: f32, // in mm margin_size: f32, + should_resize: bool, // sorry, something to force it not to resize my shapes on the web! + bg_color: Option, + output_kind: SvgSaveKind, } impl SvgDrawConfig { pub fn new( size: f32, + resolution: Option, capture_path: Option, target_size: f32, frame: u64, + output_kind: SvgSaveKind, ) -> SvgDrawConfig { SvgDrawConfig { size, @@ -58,9 +72,25 @@ impl SvgDrawConfig { target_size, margin_size: 10.0, frame, + should_resize: true, + resolution, + bg_color: None, + output_kind, } } + pub fn with_bg_color(&self, bg_color: MurreletColor) -> Self { + let mut c = self.clone(); + c.bg_color = Some(bg_color); + c + } + + pub fn with_no_resize(&self) -> Self { + let mut c = self.clone(); + c.should_resize = false; + c + } + pub fn full_target_width(&self) -> f32 { self.target_size + 2.0 * self.margin_size } @@ -76,23 +106,42 @@ impl SvgDrawConfig { self.capture_path.clone() } - pub fn transform_for_size(&self) -> Mat4 { - // okay so we take the width, since that's what looked okay on the screen - let size = self.size(); - let full_target_width = self.full_target_width() * 1.0; + pub fn transform_for_size(&self) -> SimpleTransform2d { + if self.should_resize { + // okay so we take the width, since that's what looked okay on the screen + let size = self.size(); + let full_target_width = self.full_target_width() * 1.0; - let translation_to_final = vec3(full_target_width, full_target_width, 0.0); - let s = self.target_size / size; - let scale = vec3(s, s, 1.0); + let translation_to_final = vec2(full_target_width, full_target_width); + let s = self.target_size / size; - // aiming for 100mm by 100mm, going from 0 to 10 - // operations go right to left! - Mat4::from_translation(translation_to_final) * Mat4::from_scale(scale) + // aiming for 100mm by 100mm, going from 0 to 10 + // operations go right to left! + SimpleTransform2d::new(vec![ + SimpleTransform2dStep::translate(translation_to_final), + SimpleTransform2dStep::scale_both(s), + ]) + } else { + SimpleTransform2d::noop() + } } pub fn frame(&self) -> u64 { self.frame } + + pub fn bg_color(&self) -> Option { + self.bg_color + } + + // hrm, for now, we have two main types, html, which i don't think will need + // layers(?), and inkscape. in any case, make_layers is also very inkscape-focused + pub fn make_layers(&self) -> bool { + match self.output_kind { + SvgSaveKind::HTML => false, + SvgSaveKind::Inkscape => true, + } + } } impl TransformVec2 for SvgDrawConfig { @@ -194,13 +243,8 @@ fn _default_bg_color() -> [ControlF32; 4] { ] } -fn _default_bg_color_lazy() -> Vec> { - vec![ - ControlVecElement::raw(ControlLazyNodeF32::Float(0.0)), - ControlVecElement::raw(ControlLazyNodeF32::Float(0.0)), - ControlVecElement::raw(ControlLazyNodeF32::Float(0.0)), - ControlVecElement::raw(ControlLazyNodeF32::Float(1.0)), - ] +fn _default_bg_color_lazy() -> ControlLazyMurreletColor { + ControlLazyMurreletColor::new_default(0.0, 0.0, 0.0, 1.0) } fn _default_svg_size() -> ControlF32 { @@ -217,9 +261,31 @@ fn _default_svg_save_lazy() -> ControlLazyNodeF32 { ControlLazyNodeF32::Bool(false) } +impl Default for ControlAppConfigTiming { + fn default() -> Self { + Self { + bpm: _default_bpm(), + beats_per_bar: _default_beats_per_bar(), + fps: _default_fps(), + realtime: ControlBool::Raw(true), + } + } +} + +impl Default for ControlLazyAppConfigTiming { + fn default() -> Self { + Self { + bpm: _default_bpm_lazy(), + beats_per_bar: _default_beats_per_bar_lazy(), + fps: _default_fps_lazy(), + realtime: ControlLazyNodeF32::Bool(true), + } + } +} + // this stuff adjusts how time works, so needs to be split off pretty early #[allow(dead_code)] -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct AppConfigTiming { #[livecode(serde_default = "_default_bpm")] pub bpm: f32, @@ -267,30 +333,43 @@ fn _reset_b_lazy() -> ControlLazyNodeF32 { } #[allow(dead_code)] -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct SvgConfig { #[livecode(serde_default = "_default_svg_size")] pub size: f32, #[livecode(serde_default = "_default_svg_save")] - pub save: bool, // trigger for svg save + pub save: bool, + #[livecode(serde_default = "_default_svg_kind")] + output_kind: SvgSaveKind, // trigger for svg save } impl Default for ControlSvgConfig { fn default() -> Self { Self { size: _default_svg_size(), save: _default_svg_save(), + output_kind: _default_svg_kind(), } } } + impl Default for ControlLazySvgConfig { fn default() -> Self { Self { size: _default_svg_size_lazy(), save: _default_svg_save_lazy(), + output_kind: _default_svg_kind_lazy(), } } } +// set this in websites! +fn _default_svg_kind_lazy() -> ControlLazySvgSaveKind { + ControlLazySvgSaveKind::Inkscape +} + +fn _default_svg_kind() -> ControlSvgSaveKind { + ControlSvgSaveKind::Inkscape +} fn _default_gpu_debug_next() -> ControlBool { #[cfg(feature = "for_the_web")] { @@ -330,7 +409,7 @@ fn _default_gpu_color_channel_lazy() -> ControlLazyNodeF32 { } #[allow(dead_code)] -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct GpuConfig { #[livecode(serde_default = "_default_gpu_debug_next")] debug_next: bool, @@ -374,8 +453,90 @@ fn _default_should_reset_lazy() -> ControlLazyNodeF32 { ControlLazyNodeF32::Bool(false) } +fn _default_redraw() -> ControlF32 { + ControlF32::Int(1) +} + +fn _default_redraw_lazy() -> LazyNodeF32 { + LazyNodeF32::simple_number(1.0) +} + +fn _default_reload() -> ControlBool { + ControlBool::Raw(true) +} + +fn _default_reload_lazy() -> LazyNodeF32 { + LazyNodeF32::simple_number(1.0) +} + +fn _default_reload_rate() -> ControlF32 { + ControlF32::Int(1) +} + +fn _default_reload_rate_lazy() -> LazyNodeF32 { + LazyNodeF32::simple_number(1.0) +} + +fn _default_time() -> ControlAppConfigTiming { + ControlAppConfigTiming::default() +} + +fn _default_ctx() -> AdditionalContextNode { + AdditionalContextNode::new_dummy() +} + +fn _default_ctx_lazy() -> AdditionalContextNode { + AdditionalContextNode::new_dummy() +} + +fn _default_svg() -> ControlSvgConfig { + ControlSvgConfig::default() +} + +impl Default for ControlAppConfig { + fn default() -> Self { + Self { + should_reset: _default_should_reset(), + debug: ControlBool::Raw(false), + capture: ControlBool::Raw(false), + seed: _default_seed(), + width: _default_width(), + bg_alpha: _default_bg_alpha(), + clear_bg: _default_clear_bg(), + bg_color: _default_bg_color(), + capture_frame: _default_capture_frame(), + redraw: _default_redraw(), + reload: _default_reload(), + reload_rate: _default_reload_rate(), + time: _default_time(), + ctx: _default_ctx(), + svg: _default_svg(), + gpu: _default_gpu(), + reload_on_bar: _default_reload_on_bar(), + assets: _default_assets(), + lerp_rate: _default_lerp_rate(), + } + } +} + +fn _default_lerp_rate() -> ControlF32 { + ControlF32::Int(0) +} + +fn _default_assets() -> ControlAssetFilenames { + _empty_filenames() +} + +fn _default_reload_on_bar() -> ControlBool { + ControlBool::Raw(false) +} + +fn _default_gpu() -> ControlGpuConfig { + ControlGpuConfig::default() +} + #[allow(dead_code)] -#[derive(Debug, Clone, Livecode, Lerpable)] +#[derive(Debug, Clone, Livecode, MurreletGUI, Lerpable)] pub struct AppConfig { #[livecode(serde_default = "_default_should_reset")] pub should_reset: bool, // should reset audio and time, @@ -401,8 +562,10 @@ pub struct AppConfig { pub reload: bool, // should reload and draw, good for slow drawing things #[livecode(serde_default = "0")] pub reload_rate: u64, // controls should_redraw, how many frames between redraw. if < 1, always defer to reload + #[livecode(serde_default = "default")] pub time: AppConfigTiming, #[livecode(kind = "none")] + #[livecode(serde_default = "_default_ctx")] pub ctx: AdditionalContextNode, #[livecode(serde_default = "default")] pub svg: SvgConfig, @@ -454,6 +617,7 @@ impl AppConfig { // todo, this is all a little weird (svg save path), i should revisit it.. pub struct LilLiveConfig<'a> { + resolution: Option, save_path: Option<&'a PathBuf>, run_id: u64, w: &'a LivecodeWorldState, @@ -465,22 +629,22 @@ pub fn svg_save_path(lil_liveconfig: &LilLiveConfig) -> SvgDrawConfig { } pub fn svg_save_path_with_prefix(lil_liveconfig: &LilLiveConfig, prefix: &str) -> SvgDrawConfig { - let capture_path = if let Some(save_path) = lil_liveconfig.save_path { - Some(capture_frame_name( + let capture_path = lil_liveconfig.save_path.map(|save_path| { + capture_frame_name( save_path, lil_liveconfig.run_id, lil_liveconfig.w.actual_frame_u64(), prefix, - )) - } else { - None - }; + ) + }); SvgDrawConfig::new( lil_liveconfig.app_config.width, + lil_liveconfig.resolution, capture_path, lil_liveconfig.app_config.svg.size, lil_liveconfig.w.actual_frame_u64(), + lil_liveconfig.app_config.svg.output_kind, ) } @@ -518,6 +682,8 @@ where assets: AssetsRef, maybe_args: Option, // should redesign this... lerp_pct: f32, // moving between things + used_variable_names: HashSet, + outgoing_msgs: Vec<(String, String, LivecodeValue)>, // addr, name, value } impl LiveCoder where @@ -527,15 +693,9 @@ where pub fn new_web( conf: String, livecode_src: LivecodeSrc, - load_funcs: &[Box], + load_funcs: &AssetLoaders, ) -> LivecodeResult> { - let controlconfig = ControlConfType::parse(&conf).map_err(|err| { - if let Some(error) = err.location() { - LivecodeError::SerdeLoc(error, err.to_string()) - } else { - LivecodeError::Raw(err.to_string()) - } - })?; + let controlconfig = ControlConfType::parse(&conf)?; Self::new_full(controlconfig, None, livecode_src, load_funcs, None) } @@ -543,7 +703,7 @@ where pub fn new( save_path: PathBuf, livecode_src: LivecodeSrc, - load_funcs: &[Box], + load_funcs: &AssetLoaders, ) -> LiveCoder { let controlconfig = ControlConfType::fs_load(); @@ -563,13 +723,19 @@ where controlconfig: ControlConfType, save_path: Option, livecode_src: LivecodeSrc, - load_funcs: &[Box], + load_funcs: &AssetLoaders, maybe_args: Option, ) -> LivecodeResult> { let run_id = run_id(); let util = LiveCodeUtil::new()?; + let used_variable_names = controlconfig + .variable_identifiers() + .into_iter() + .map(|x| x.name) + .collect(); + let mut s = LiveCoder { run_id, controlconfig: controlconfig.clone(), @@ -584,6 +750,8 @@ where assets: Assets::empty_ref(), maybe_args, lerp_pct: 1.0, // start in the done state! + used_variable_names, + outgoing_msgs: vec![], }; // hrm, before doing most things, load the assets (but we'll do this line again...) @@ -593,7 +761,7 @@ where let w = s.world(); let app_conf = s.controlconfig._app_config().o(w)?; - let assets = app_conf.assets.load(load_funcs); + let assets = app_conf.assets.load_polylines(load_funcs); s.assets = assets.to_ref(); // use the object to create a world and generate the configs @@ -625,13 +793,13 @@ where let w = self.world(); - let mut target = self.controlconfig.o(&w)?; + let mut target = self.controlconfig.o(w)?; let mut lerp_change = 0.0; // todo, make this optional if target.config_app_loc().should_lerp() { if self.lerp_pct < 1.0 { - let old_target = self.prev_controlconfig.o(&w)?; + let old_target = self.prev_controlconfig.o(w)?; target = old_target.lerpify(&target, &self.lerp_pct); } @@ -647,11 +815,9 @@ where self.lerp_pct += lerp_change; if self.lerp_pct >= 1.0 { + // todo, just move it out... if let Some(new_target) = &self.queued_configcontrol { - self.prev_controlconfig = self.controlconfig.clone(); - self.controlconfig = new_target.clone(); - self.lerp_pct = 0.0; - self.queued_configcontrol = None; + self.update_config_directly(new_target.clone())?; } } @@ -662,12 +828,13 @@ where self.svg_save_path_with_prefix("") } - pub fn to_lil_liveconfig(&self) -> LivecodeResult { + pub fn to_lil_liveconfig(&self) -> LivecodeResult> { Ok(LilLiveConfig { save_path: self.save_path.as_ref(), run_id: self.run_id, w: self.world(), app_config: self.app_config(), + resolution: self.maybe_args.as_ref().map(|x| x.resolution), }) } @@ -690,6 +857,22 @@ where self.controlconfig = d; self.lerp_pct = 0.0; // reloaded, so time to reload it! } + + // set the current vars + let variables_iter = self + .controlconfig + .variable_identifiers() + .into_iter() + .chain(self.prev_controlconfig.variable_identifiers()); + let variables = if let Some(queued) = &self.queued_configcontrol { + variables_iter + .chain(queued.variable_identifiers()) + .map(|x| x.name) + .collect::>() + } else { + variables_iter.map(|x| x.name).collect::>() + }; + self.used_variable_names = variables; } else if let Err(e) = result { eprintln!("Error {}", e); } @@ -698,17 +881,19 @@ where // web one, callback pub fn update_config_to(&mut self, text: &str) -> Result<(), String> { match ControlConfType::cb_reload_and_update_info(&mut self.util, text) { - Ok(d) => { - self.prev_controlconfig = self.controlconfig.clone(); - self.controlconfig = d; - self.queued_configcontrol = None; - self.lerp_pct = 0.0; - Ok(()) - } + Ok(d) => self.update_config_directly(d).map_err(|x| x.to_string()), Err(e) => Err(e), } } + pub fn update_config_directly(&mut self, control_conf: ControlConfType) -> LivecodeResult<()> { + self.prev_controlconfig = self.controlconfig.clone(); + self.controlconfig = control_conf; + self.queued_configcontrol = None; + self.lerp_pct = 0.0; + Ok(()) + } + /// if the bg_alpha is above 0.5 or clear_bg is true pub fn should_reset_bg(&self) -> bool { self.world().actual_frame_u64() <= 1 || self.app_config().should_clear_bg() @@ -718,6 +903,10 @@ where self.app_config().bg_alpha() } + pub fn add_outgoing_msg(&mut self, addr: String, name: String, value: LivecodeValue) { + self.outgoing_msgs.push((addr, name, value)); + } + // called every frame pub fn update(&mut self, app: &MurreletAppInput, reload: bool) -> LivecodeResult<()> { // use the previous frame's world for this @@ -729,7 +918,8 @@ where self.livecode_src.update(&update_input); - if app.elapsed_frames() % 20 == 0 { + // todo, set this as a variable? + if app.elapsed_frames().is_multiple_of(1) { let variables = self .controlconfig .variable_identifiers() @@ -746,7 +936,9 @@ where }) .collect::>(); - self.livecode_src.feedback(&variables); + let outgoing_msgs = std::mem::take(&mut self.outgoing_msgs); + + self.livecode_src.feedback(&variables, &outgoing_msgs); } // needs to happen before checking is on bar @@ -754,10 +946,8 @@ where // if we can reload whenever, do that. otherwise only reload on bar - if reload { - if !self.app_config().reload_on_bar() || self.world().time().is_on_bar() { - self.reload_config(); - } + if reload && (!self.app_config().reload_on_bar() || self.world().time().is_on_bar()) { + self.reload_config(); } if self.app_config().should_reset() { @@ -785,15 +975,30 @@ where let ctx = &self.controlconfig._app_config().ctx; - let world = self - .util - .world(&self.livecode_src, &timing_conf, ctx, self.assets.clone())?; + let mut world = + self.util + .world(&self.livecode_src, &timing_conf, ctx, self.assets.clone())?; + + let mut md = MixedEvalDefs::new(); + + for x in self.used_variable_names.difference(&world.vars()) { + // argh, so used_variable_names includes non-global things, but right now i'm global + // so just do the stuff I care about right now, osc things... + if x.starts_with("oo_") { + if world.actual_frame_u64() % 1000 == 0 { + println!("adding default value for {:?}", x); + } + md.set_val(x, murrelet_common::LivecodeValue::Float(0.0)); + } + } + world.update_with_defs(MixedEvalDefsRef::new(md)); // i'm setting this so it should be okay.. self.cached_world = Some(world); Ok(()) } pub fn world(&self) -> &LivecodeWorldState { + // self.cached_world.as_ref().unwrap() self.cached_world.as_ref().unwrap() } @@ -815,17 +1020,15 @@ where } pub fn capture_frame_name(&self, frame: u64, prefix: &str) -> Option { - if let Some(save_path) = &self.save_path { - Some(capture_frame_name(&save_path, self.run_id, frame, prefix)) - } else { - None - } + self.save_path + .as_ref() + .map(|save_path| capture_frame_name(save_path, self.run_id, frame, prefix)) } // model.livecode.capture_logic(|img_name: PathBuf| { app.main_window().capture_frame(img_name); } ); pub fn capture(&self, capture_frame_fn: F) -> LivecodeResult<()> where - F: Fn(PathBuf) -> (), + F: Fn(PathBuf), { let frame = self.world().actual_frame_u64(); @@ -853,7 +1056,7 @@ where pub fn capture_with_fn(&self, capture_frame_fn: F) -> LivecodeResult<()> where - F: Fn(PathBuf) -> (), + F: Fn(PathBuf), { let w = self.world(); @@ -869,7 +1072,7 @@ where || should_capture { let frame_freq = 1; - if frame % frame_freq == 0 { + if frame.is_multiple_of(frame_freq) { self.capture(capture_frame_fn)?; } } @@ -885,15 +1088,15 @@ where pub fn should_reload(&self) -> bool { let w = self.world(); let reload_rate = self.app_config().reload_rate; - let reload_rate_says_so = - reload_rate >= 1 && w.actual_frame() as u64 % self.app_config().reload_rate == 0; + let reload_rate_says_so = reload_rate >= 1 + && (w.actual_frame() as u64).is_multiple_of(self.app_config().reload_rate); let config_says_so = self.app_config().reload; reload_rate_says_so || config_says_so } pub fn should_redraw(&self) -> bool { let w = self.world(); - let redraw_says_so = w.actual_frame() as u64 % self.app_config().redraw == 0; + let redraw_says_so = (w.actual_frame() as u64).is_multiple_of(self.app_config().redraw); let save_says_so = self.app_config().svg.save; // might have other things.. redraw_says_so || save_says_so @@ -924,6 +1127,10 @@ where self.world().time().seconds_between_render_times() } + pub fn args(&self) -> BaseConfigArgs { + self.maybe_args.clone().unwrap() + } + pub fn sketch_args(&self) -> Vec { if let Some(args) = &self.maybe_args { args.sketch_args.clone() @@ -931,4 +1138,8 @@ where vec![] } } + + pub fn run_id(&self) -> u64 { + self.run_id + } } diff --git a/murrelet_perform/src/reload.rs b/murrelet_perform/src/reload.rs index 002970f..a7afb23 100644 --- a/murrelet_perform/src/reload.rs +++ b/murrelet_perform/src/reload.rs @@ -25,12 +25,12 @@ pub trait LiveCoderLoader: Sized { fn _app_config(&self) -> &ControlAppConfig; // usually just serde_yaml::from_str(&str) - fn parse(text: &str) -> Result; + fn parse(text: &str) -> LivecodeResult; fn fs_parse>( text: &str, includes_dir: P, - ) -> Result { + ) -> Result { let preprocessed = crate::load::preprocess_yaml(text, includes_dir); //serde_yaml::from_str(&stripped_json) Self::parse(&preprocessed) @@ -39,14 +39,14 @@ pub trait LiveCoderLoader: Sized { fn fs_parse_data, P2: AsRef>( filename: P, includes_dir: P2, - ) -> Result { + ) -> Result { let mut file = fs::File::open(filename).unwrap(); let mut data = String::new(); std::io::Read::read_to_string(&mut file, &mut data).unwrap(); Self::fs_parse(&data, includes_dir) } - fn _fs_load() -> Result { + fn _fs_load() -> Result { let args: Vec = env::args().collect(); Self::fs_parse_data(&args[1], &args[2]) } @@ -86,15 +86,15 @@ pub trait LiveCoderLoader: Sized { let mut latest_time = MurreletTime::epoch(); for entry in - fs::read_dir(dir).map_err(|e| LivecodeError::Io(format!("template error"), e))? + fs::read_dir(dir).map_err(|e| LivecodeError::Io("template error".to_string(), e))? { - let entry = entry.map_err(|e| LivecodeError::Io(format!("template error"), e))?; + let entry = entry.map_err(|e| LivecodeError::Io("template error".to_string(), e))?; let metadata = entry .metadata() - .map_err(|e| LivecodeError::Io(format!("template error"), e))?; + .map_err(|e| LivecodeError::Io("template error".to_string(), e))?; let modified_time_s = metadata .modified() - .map_err(|e| LivecodeError::Io(format!("template error"), e))?; + .map_err(|e| LivecodeError::Io("template error".to_string(), e))?; let modified_time = MurreletTime::from_epoch_time( modified_time_s @@ -115,7 +115,7 @@ pub trait LiveCoderLoader: Sized { fn cb_reload_and_update_info(util: &mut LiveCodeUtil, text: &str) -> Result { util.reset_info(); - match Self::parse(&text) { + match Self::parse(text) { Ok(x) => { util.update_info_reloaded(); Ok(x) @@ -141,7 +141,7 @@ pub trait LiveCoderLoader: Sized { })?; let modified = filename .modified() - .map_err(|err| LivecodeError::Io(format!("error finding modified type"), err))?; + .map_err(|err| LivecodeError::Io("error finding modified type".to_string(), err))?; let current_modified = murrelet_time_from_system(modified); @@ -163,11 +163,7 @@ pub trait LiveCoderLoader: Sized { } Err(err) => { util.update_info_error(); - if let Some(error) = err.location() { - Err(LivecodeError::SerdeLoc(error, err.to_string())) - } else { - Err(LivecodeError::Raw(err.to_string())) - } + Err(err) } } } else { diff --git a/murrelet_schema/Cargo.toml b/murrelet_schema/Cargo.toml new file mode 100644 index 0000000..3fba5aa --- /dev/null +++ b/murrelet_schema/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "murrelet_schema" +version = "0.1.2" +edition = "2021" + +[features] +default = [] +glam = ["dep:glam"] + +[dependencies] +murrelet_schema_derive = { workspace = true } +murrelet_common = { workspace = true } + +anyhow = "1.0.86" +thiserror = "2.0.11" +serde = { version = "1.0.104", features = ["derive"] } +serde_json = "1.0.48" + +glam = { version = "0.23", optional = true } + +[[example]] +name = "tests" +path = "examples/tests.rs" diff --git a/murrelet_schema/examples/tests.rs b/murrelet_schema/examples/tests.rs new file mode 100644 index 0000000..cf50077 --- /dev/null +++ b/murrelet_schema/examples/tests.rs @@ -0,0 +1,127 @@ +// use std::collections::HashMap; + +// use murrelet_schema::*; +// use murrelet_schema_derive::MurreletSchema; + +// #[derive(MurreletSchema)] +// pub struct BasicTypes { +// a_number: f32, +// b_number: usize, +// c_number: u64, +// d_number: i32, +// bool: bool, +// something: Vec, +// s: String, +// } + +// fn custom_func() -> MurreletSchema { +// MurreletSchema::Val(MurreletPrimitive::Num) +// } + +// #[derive(MurreletSchema)] +// pub struct OverridesAndRecursive { +// a_number: f32, +// something: Vec, +// #[murrelet_schema(func = "custom_func")] +// label: String, +// #[murrelet_schema(kind = "skip")] +// b: HashMap, +// } + +// #[derive(MurreletSchema)] +// enum EnumTest { +// A, +// B(OverridesAndRecursive), +// } + +// #[derive(MurreletSchema)] +// struct SimpleNewtype(f32); + +// // // fn lerp_partial(&self, pct: T) -> Self { +// // // SimpleNewtype(pct.lerp_pct() as f32) +// // // } +// // // } + +// // // #[derive(Debug, Clone, MurreletUX)] + +// fn main() { +// // let b = BasicTypes{ +// // a_number: 1.0, +// // b_number: -10.0, +// // }; +// let test_val = BasicTypes::make_schema(); + +// let basic_types_schema = MurreletSchema::Struct( +// "BasicTypes".to_string(), +// vec![ +// ( +// "a_number".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Num), +// ), +// ( +// "b_number".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Num), +// ), +// ( +// "c_number".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Num), +// ), +// ( +// "d_number".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Num), +// ), +// ( +// "bool".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Bool), +// ), +// ( +// "something".to_owned(), +// MurreletSchema::list(MurreletSchema::Val(MurreletPrimitive::Num)), +// ), +// ( +// "s".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::String), +// ), +// ], +// ); + +// assert_eq!(test_val, basic_types_schema); + +// let test_val = OverridesAndRecursive::make_schema(); + +// let overrides_and_recursive_schema = MurreletSchema::Struct( +// "OverridesAndRecursive".to_string(), +// vec![ +// ( +// "a_number".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Num), +// ), +// ( +// "something".to_owned(), +// MurreletSchema::list(basic_types_schema), +// ), +// ( +// "label".to_owned(), +// MurreletSchema::Val(MurreletPrimitive::Num), +// ), // make sure it calls the override +// ("b".to_owned(), MurreletSchema::Skip), +// ], +// ); +// assert_eq!(test_val, overrides_and_recursive_schema); + +// let test_val = EnumTest::make_schema(); + +// assert_eq!( +// test_val, +// MurreletSchema::Enum( +// "EnumTest".to_string(), +// vec![ +// (MurreletEnumVal::Unit("A".to_owned())), +// (MurreletEnumVal::Unnamed("B".to_owned(), overrides_and_recursive_schema)), +// ], +// false +// ) +// ); +// } + +fn main() {} diff --git a/murrelet_schema/src/lib.rs b/murrelet_schema/src/lib.rs new file mode 100644 index 0000000..97b1306 --- /dev/null +++ b/murrelet_schema/src/lib.rs @@ -0,0 +1,195 @@ +use std::collections::BTreeMap; + +use anyhow::{anyhow, Result}; +use serde::Serialize; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum MurreletPrimitive { + Bool, // should be a ControlBool + Num, // should be a ControlF32 + Color, // expected to give h s v a + Defs, // make a ctx node + Vec2, // arbitrary vec2, also see Coords + Vec3, // arbitrary vec3 + Style, // murrelet style + Angle, // angle pi + Coords, // global coords, so the user can click things + String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum MurreletEnumVal { + Unnamed(String, Box), + Unit(String), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum MurreletSchema { + NewType(String, Box), + Struct(String, BTreeMap), + // hmm, we'll need to figure out how to provide this + Enum(String, BTreeMap, bool), + List(Box), + Val(MurreletPrimitive), + Skip, +} +impl MurreletSchema { + pub fn new_type(name: String, m: MurreletSchema) -> Self { + Self::NewType(name, Box::new(m)) + } + + pub fn list(m: MurreletSchema) -> Self { + Self::List(Box::new(m)) + } + + pub fn as_enum(&self) -> Option<&BTreeMap> { + if let Self::Enum(_, v, _) = self { + Some(v) + } else { + None + } + } + + pub fn as_new_type(&self) -> Option<&Box> { + if let Self::NewType(_, v) = self { + Some(v) + } else { + None + } + } + + pub fn unwrap_to_struct_fields(self) -> BTreeMap { + match self { + MurreletSchema::Struct(_, items) => items, + _ => unreachable!("tried to flatten a struct that wasn't a struct"), + } + } + + pub fn update_with_hints(&self, s: &std::collections::HashMap) -> Result { + // basically we go through s and then traverse until we find the spot we need to update + + let mut n = self.clone(); + + for (key, new_type) in s.iter() { + let location = key.split(".").collect::>(); + + let kind = match new_type.to_lowercase().as_str() { + "vec2" => Ok(MurreletPrimitive::Vec2), + "f32" => Ok(MurreletPrimitive::Num), + "number" => Ok(MurreletPrimitive::Num), + "num" => Ok(MurreletPrimitive::Num), + "color" => Ok(MurreletPrimitive::Color), + "string" => Ok(MurreletPrimitive::String), + _ => Result::Err(anyhow!(format!("unsupported schema hint, {}", new_type))), + }?; + + n.update_with_one_hint(key, &location, &kind)?; + } + + Ok(n) + } + + pub fn update_with_one_hint( + &mut self, + original_location: &str, // for debugging + location: &[&str], + value: &MurreletPrimitive, + ) -> Result<()> { + // basically we go through s and then traverse until we find the spot we need to update + + if location.is_empty() { + // we're there! + // return Result::Err(anyhow!(format!("couldn't find {} in {:?}!", original_location, self))); + *self = MurreletSchema::Val(value.clone()); + return Ok(()); + } + + match self { + MurreletSchema::NewType(_, murrelet_schema) => { + murrelet_schema.update_with_one_hint(original_location, location, value) + } + MurreletSchema::Struct(_, items) => { + if let Some((first_name, rest)) = location.split_first() { + let mut found_match = false; + for (name, schema) in items { + if name == first_name { + found_match = true; + schema.update_with_one_hint(original_location, rest, value)?; + break; + } + } + if !found_match { + Result::Err(anyhow!(format!("{} didn't match", first_name))) + } else { + Ok(()) + } + } else { + Result::Err(anyhow!("missing")) + } + } + MurreletSchema::Enum(_, _, _) => todo!(), + // need to do this one... + MurreletSchema::List(murrelet_schema) => { + murrelet_schema.update_with_one_hint(original_location, location, value) + } + // i think these should be handled in the struct level! + MurreletSchema::Val(_) => todo!(), + MurreletSchema::Skip => Result::Err(anyhow!("hm, trying to edit a skip")), + } + } +} + +// this should be on the Control version +pub trait CanMakeSchema: Sized { + fn make_schema() -> MurreletSchema; +} + +macro_rules! impl_can_make_schema_for_num { + ($ty:ty) => { + impl CanMakeSchema for $ty { + fn make_schema() -> MurreletSchema { + MurreletSchema::Val(MurreletPrimitive::Num) + } + } + }; +} + +impl_can_make_schema_for_num!(f32); +impl_can_make_schema_for_num!(f64); +impl_can_make_schema_for_num!(u32); +impl_can_make_schema_for_num!(u64); +impl_can_make_schema_for_num!(i32); +impl_can_make_schema_for_num!(i64); +impl_can_make_schema_for_num!(usize); + +impl CanMakeSchema for Vec { + fn make_schema() -> MurreletSchema { + MurreletSchema::List(Box::new(T::make_schema())) + } +} + +impl CanMakeSchema for String { + fn make_schema() -> MurreletSchema { + MurreletSchema::Val(MurreletPrimitive::String) + } +} + +impl CanMakeSchema for bool { + fn make_schema() -> MurreletSchema { + MurreletSchema::Val(MurreletPrimitive::Bool) + } +} + +#[cfg(feature = "glam")] +impl CanMakeSchema for glam::Vec2 { + fn make_schema() -> MurreletSchema { + MurreletSchema::Val(MurreletPrimitive::Vec2) + } +} + +#[cfg(feature = "glam")] +impl CanMakeSchema for glam::Vec3 { + fn make_schema() -> MurreletSchema { + MurreletSchema::Val(MurreletPrimitive::Vec3) + } +} diff --git a/murrelet_schema_derive/Cargo.toml b/murrelet_schema_derive/Cargo.toml new file mode 100644 index 0000000..da45749 --- /dev/null +++ b/murrelet_schema_derive/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "murrelet_schema_derive" +version = "0.1.2" +edition = "2021" + +[lib] +proc-macro = true + +[features] +debug_logging = ["log"] + +[dependencies] +syn = "2.0.15" +quote = "1.0.18" +proc-macro2 = "1.0.37" +darling = "0.20.3" + +log = { version = "0.4.25", optional = true } + diff --git a/murrelet_schema_derive/src/derive_schema.rs b/murrelet_schema_derive/src/derive_schema.rs new file mode 100644 index 0000000..d215b9e --- /dev/null +++ b/murrelet_schema_derive/src/derive_schema.rs @@ -0,0 +1,196 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::parser::*; + +pub(crate) struct FieldTokensSchema { + pub(crate) for_make_schema: TokenStream2, +} +impl GenFinal for FieldTokensSchema { + // Something(f32) + fn make_newtype_struct_final( + idents: ParsedFieldIdent, + variants: Vec, + ) -> TokenStream2 { + let name = idents.name; + let name_str = name.to_string(); + + let for_make_schema = variants.iter().map(|x| x.for_make_schema.clone()); + + quote! { + impl murrelet_schema::CanMakeSchema for #name { + fn make_schema() -> murrelet_schema::MurreletSchema { + murrelet_schema::MurreletSchema::new_type(#name_str.to_owned(), #(#for_make_schema,)*) + } + } + } + } + + fn make_struct_final( + idents: ParsedFieldIdent, + variants: Vec, + ) -> TokenStream2 { + let name = idents.name; + let name_str = name.to_string(); + + let for_make_schema = variants.iter().map(|x| x.for_make_schema.clone()); + + quote! { + impl murrelet_schema::CanMakeSchema for #name { + fn make_schema() -> murrelet_schema::MurreletSchema { + + let mut v = std::collections::BTreeMap::new(); + #(#for_make_schema;)* + + murrelet_schema::MurreletSchema::Struct(#name_str.to_owned(), v) + } + + } + } + } + + fn make_enum_final( + idents: ParsedFieldIdent, + variants: Vec, + is_untagged: bool, + ) -> TokenStream2 { + let name = idents.name; + let name_str = name.to_string(); + + let for_make_schema = variants.iter().map(|x| x.for_make_schema.clone()); + + quote! { + impl murrelet_schema::CanMakeSchema for #name { + fn make_schema() -> murrelet_schema::MurreletSchema { + let mut v = std::collections::BTreeMap::new(); + #(#for_make_schema;)* + + murrelet_schema::MurreletSchema::Enum(#name_str.to_owned(), v, #is_untagged) + } + + } + } + } + + fn from_override_enum(func: &str) -> FieldTokensSchema { + let method: syn::Path = syn::parse_str(func).expect("Custom method is invalid path!"); + + let for_make_schema = quote! { + #method() + }; + + FieldTokensSchema { for_make_schema } + } + + fn from_newtype_struct(idents: StructIdents, _parent_ident: syn::Ident) -> FieldTokensSchema { + let ty = convert_vec_type(&idents.data.ty); + + let for_make_schema = quote! { + #ty::make_schema() + }; + + FieldTokensSchema { for_make_schema } + } + + // e.g. TileAxisLocs::V(TileAxisVs) + fn from_unnamed_enum(idents: EnumIdents) -> FieldTokensSchema { + let variant_ident = idents.data.ident; + let ty = convert_vec_type(&idents.data.fields.fields.first().unwrap().ty); + let variant_ident_str = variant_ident.to_string(); + + let for_make_schema = quote! { + v.insert( + #variant_ident_str.to_string(), + murrelet_schema::MurreletEnumVal::Unnamed( + #variant_ident_str.to_string(), + Box::new(#ty::make_schema()), + ), + ) + }; + + FieldTokensSchema { for_make_schema } + } + + // e.g. TileAxis::Diag + fn from_unit_enum(idents: EnumIdents) -> FieldTokensSchema { + let variant_ident = idents.data.ident; + let variant_ident_str = variant_ident.to_string(); + + let for_make_schema = quote! { v.insert(#variant_ident_str.to_owned(), murrelet_schema::MurreletEnumVal::Unit(#variant_ident_str.to_owned())) }; + + FieldTokensSchema { for_make_schema } + } + + // // s: String with reference + // fn from_name(idents: StructIdents) -> FieldTokensSchema { + // let field_name = idents.data.ident.unwrap().to_string(); + + // let name_reference = idents + // .data + // .reference + // .expect("from name called without a reference!"); + + // let is_main = idents.data.is_ref_def.unwrap_or(false); + + // let for_make_schema = quote! { v.push((#field_name.to_owned(), murrelet_schema::MurreletSchema::Val(murrelet_schema::Value::Name(#name_reference.to_owned(), #is_main)))) }; + + // FieldTokensSchema { for_make_schema } + // } + + // skip + fn from_noop_struct(idents: StructIdents) -> FieldTokensSchema { + let field_name = idents.data.ident.unwrap().to_string(); + + let for_make_schema = + quote! { v.insert(#field_name.to_owned(), murrelet_schema::MurreletSchema::Skip) }; + + FieldTokensSchema { for_make_schema } + } + + // f32, Vec2, etc + fn from_type_struct(idents: StructIdents) -> FieldTokensSchema { + let field_name = idents.data.ident.unwrap(); + let field_name_str = field_name.to_string(); + // to call a static function, we need to + let kind = convert_vec_type(&idents.data.ty); + + let is_flat = idents.data.flatten.unwrap_or(false); + + let for_make_schema = if is_flat { + quote! { v.extend(#kind::make_schema().unwrap_to_struct_fields().into_iter()) } + } else { + quote! { v.insert(#field_name_str.to_owned(), #kind::make_schema()) } + }; + + FieldTokensSchema { for_make_schema } + } + + fn from_override_struct(idents: StructIdents, func: &str) -> FieldTokensSchema { + let field_name = idents.data.ident.unwrap(); + let field_name_str = field_name.to_string(); + + let method: syn::Path = syn::parse_str(func).expect("Custom method is invalid path!"); + + let for_make_schema = quote! { v.insert(#field_name_str.to_owned(), #method()) }; + + FieldTokensSchema { for_make_schema } + } +} + +// we need to use turbofish to call an associated function +fn convert_vec_type(ty: &syn::Type) -> TokenStream2 { + if let syn::Type::Path(type_path) = ty { + if let Some(last_segment) = type_path.path.segments.last() { + if last_segment.ident == "Vec" { + if let syn::PathArguments::AngleBracketed(angle_bracketed) = &last_segment.arguments + { + if let Some(inner_arg) = angle_bracketed.args.first() { + return quote! { Vec:: < #inner_arg > }; + } + } + } + } + } + + quote! { #ty } +} diff --git a/murrelet_schema_derive/src/lib.rs b/murrelet_schema_derive/src/lib.rs new file mode 100644 index 0000000..adf009d --- /dev/null +++ b/murrelet_schema_derive/src/lib.rs @@ -0,0 +1,16 @@ +extern crate proc_macro; + +use darling::FromDeriveInput; +use derive_schema::FieldTokensSchema; +use parser::{GenFinal, LivecodeReceiver}; +use proc_macro::TokenStream; + +mod derive_schema; +mod parser; + +#[proc_macro_derive(MurreletSchema, attributes(murrelet_schema))] +pub fn murrelet_livecode_derive_murrelet_gui(input: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(input as syn::DeriveInput); + let ast_receiver = LivecodeReceiver::from_derive_input(&ast).unwrap(); + FieldTokensSchema::from_ast(ast_receiver).into() +} diff --git a/murrelet_schema_derive/src/parser.rs b/murrelet_schema_derive/src/parser.rs new file mode 100644 index 0000000..d7b0352 --- /dev/null +++ b/murrelet_schema_derive/src/parser.rs @@ -0,0 +1,231 @@ +use darling::{ast, FromDeriveInput, FromField, FromVariant}; +use proc_macro2::TokenStream as TokenStream2; + +#[derive(Debug)] +pub(crate) struct ParsedFieldIdent { + pub(crate) name: syn::Ident, +} + +// trait and helpers needed to parse a variety of objects +pub(crate) trait GenFinal +where + Self: Sized, +{ + fn from_newtype_struct(_idents: StructIdents, parent_ident: syn::Ident) -> Self; + fn from_unnamed_enum(idents: EnumIdents) -> Self; + fn from_unit_enum(idents: EnumIdents) -> Self; + fn from_noop_struct(idents: StructIdents) -> Self; + // fn from_name(idents: StructIdents) -> Self; + fn from_type_struct(idents: StructIdents) -> Self; + // fn from_recurse_struct_vec(idents: StructIdents) -> Self; + + fn from_ast(ast_receiver: LivecodeReceiver) -> TokenStream2 { + match ast_receiver.data { + ast::Data::Enum(_) => Self::make_enum(&ast_receiver), + ast::Data::Struct(ast::Fields { + style: ast::Style::Tuple, + .. + }) => Self::make_newtype(&ast_receiver), + ast::Data::Struct(_) => Self::make_struct(&ast_receiver), + } + } + fn from_override_struct(idents: StructIdents, func: &str) -> Self; + fn from_override_enum(func: &str) -> Self; + + fn make_enum_final( + idents: ParsedFieldIdent, + variants: Vec, + is_untagged: bool, + ) -> TokenStream2; + fn make_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2; + fn make_newtype_struct_final(idents: ParsedFieldIdent, variants: Vec) -> TokenStream2; + + fn make_struct(s: &LivecodeReceiver) -> TokenStream2 { + let name = s.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_struct {}", Self::classname(), name.to_string()); + + // shouldn't be calling this with something that's not a struct.. + let fields = s.data.clone().take_struct().unwrap(); + + let livecodable_fields = fields + .iter() + .map(|field| { + let idents = StructIdents { + data: field.clone(), + }; + + match field.how_to_control_this() { + HowToControlThis::Skip => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_noop_struct"); + Self::from_noop_struct(idents) + } + // HowToControlThis::Name => { + // #[cfg(feature = "debug_logging")] + // log::info!("-> from_name"); + // Self::from_name(idents) + // } + HowToControlThis::SchemaType => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_type_struct"); + Self::from_type_struct(idents) + } + HowToControlThis::Override(func) => Self::from_override_struct(idents, &func), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_struct_final(idents, livecodable_fields) + } + + fn make_enum(e: &LivecodeReceiver) -> TokenStream2 { + let name = e.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_enum {}", Self::classname(), name.to_string()); + + let variants = e.data.clone().take_enum().unwrap(); + + // just go through and find ones that wrap around a type, and make sure those types are + let variants = variants + .iter() + .map(|variant| { + let ident = EnumIdents { + // enum_name: name.clone(), + data: variant.clone(), + }; + + match variant.fields.style { + ast::Style::Tuple => Self::from_unnamed_enum(ident), + ast::Style::Struct => panic!("enum named fields not supported yet"), + ast::Style::Unit => Self::from_unit_enum(ident), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + let is_untagged = if let Some(enum_tag) = &e.enum_tag { + enum_tag.as_str() == "external" + } else { + false + }; + + Self::make_enum_final(idents, variants, is_untagged) + } + + fn make_newtype(s: &LivecodeReceiver) -> TokenStream2 { + let name = s.ident.clone(); + + #[cfg(feature = "debug_logging")] + log::info!("{}::make_newtype {}", Self::classname(), name.to_string()); + + // shouldn't be calling this with something that's not a struct.. + let fields = s.data.clone().take_struct().unwrap(); + + let livecodable_fields = fields + .iter() + .map(|field| { + let idents = StructIdents { + data: field.clone(), + }; + + match field.how_to_control_this() { + HowToControlThis::SchemaType => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_newtype_struct"); + Self::from_newtype_struct(idents, name.clone()) + } + HowToControlThis::Skip => { + #[cfg(feature = "debug_logging")] + log::info!("-> from_newtype_recurse_struct_vec"); + Self::from_noop_struct(idents) + } + // HowToControlThis::Name => { + // #[cfg(feature = "debug_logging")] + // log::info!("-> from_name"); + // Self::from_name(idents) + // } + HowToControlThis::Override(func) => Self::from_override_enum(&func), + } + }) + .collect::>(); + + let idents = ParsedFieldIdent { name: name.clone() }; + + Self::make_newtype_struct_final(idents, livecodable_fields) + } +} + +#[derive(Debug, FromField, Clone)] +#[darling(attributes(murrelet_schema))] +pub(crate) struct LivecodeFieldReceiver { + pub(crate) ident: Option, + pub(crate) ty: syn::Type, + pub(crate) kind: Option, + pub(crate) func: Option, + pub(crate) flatten: Option, +} +impl LivecodeFieldReceiver { + fn how_to_control_this(&self) -> HowToControlThis { + if let Some(kind_val) = &self.kind { + if kind_val == "skip" { + HowToControlThis::Skip + // } else if kind_val == "reference" { + // HowToControlThis::Name + } else { + panic!("unexpected kind") + } + // } else if let Some(_) = &self.reference { + // HowToControlThis::Name + } else if let Some(func) = &self.func { + HowToControlThis::Override(func.to_owned()) + } else { + HowToControlThis::SchemaType + } + } +} + +// for enums +#[derive(Debug, FromVariant, Clone)] +#[darling(attributes(murrelet_schema))] +pub(crate) struct LivecodeVariantReceiver { + pub(crate) ident: syn::Ident, + pub(crate) fields: ast::Fields, +} + +#[derive(Debug, Clone, FromDeriveInput)] +#[darling(attributes(murrelet_schema), supports(any))] +pub(crate) struct LivecodeReceiver { + ident: syn::Ident, + data: ast::Data, + enum_tag: Option, +} +impl LivecodeReceiver {} + +// represents an enum +pub(crate) struct EnumIdents { + // pub(crate) enum_name: syn::Ident, + pub(crate) data: LivecodeVariantReceiver, +} + +impl EnumIdents {} + +#[derive(Clone, Debug)] +pub struct StructIdents { + pub(crate) data: LivecodeFieldReceiver, +} +impl StructIdents {} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum HowToControlThis { + Skip, // just do the default values + SchemaType, // build a gui for this type + // GUIVec, // GUI for a list + // Name, // a referenced thing, + Override(String), +} diff --git a/murrelet_src_audio/Cargo.toml b/murrelet_src_audio/Cargo.toml index b56fa83..7126788 100644 --- a/murrelet_src_audio/Cargo.toml +++ b/murrelet_src_audio/Cargo.toml @@ -8,9 +8,9 @@ description = "audio input functions for murrelet, a livecode framework" license = "AGPL-3.0-or-later" [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } +murrelet_common = { workspace = true } cpal = "0.14" rustfft = "6.1.0" -glam = "0.28.0" +glam = { version = "0.28.0", features = ["serde"] } itertools = "0.10.5" anyhow = "1.0.86" diff --git a/murrelet_src_midi/Cargo.toml b/murrelet_src_midi/Cargo.toml index 3b64af9..43bd3e2 100644 --- a/murrelet_src_midi/Cargo.toml +++ b/murrelet_src_midi/Cargo.toml @@ -8,7 +8,7 @@ description = "MIDI input functions for murrelet, a livecode framework" license = "AGPL-3.0-or-later" [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } +murrelet_common = { workspace = true } midir = "0.8.0" diff --git a/murrelet_src_midi/src/midi.rs b/murrelet_src_midi/src/midi.rs index ebe6236..8752b35 100644 --- a/murrelet_src_midi/src/midi.rs +++ b/murrelet_src_midi/src/midi.rs @@ -49,7 +49,11 @@ impl IsLivecodeSrc for MidiMng { vals } - fn feedback(&mut self, variables: &HashMap) { + fn feedback( + &mut self, + variables: &HashMap, + _outgoing_msgs: &[(String, String, LivecodeValue)], + ) { if let Some(out) = self.out.get_mut(&MidiDevice::MidiTwister) { let mut twister = TwisterController { out }; diff --git a/murrelet_src_osc/Cargo.toml b/murrelet_src_osc/Cargo.toml index 9245ed3..7e00616 100644 --- a/murrelet_src_osc/Cargo.toml +++ b/murrelet_src_osc/Cargo.toml @@ -9,7 +9,7 @@ license = "AGPL-3.0-or-later" [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } +murrelet_common = { workspace = true } uuid = "1.8.0" rosc = "~0.10" diff --git a/murrelet_src_osc/src/osc.rs b/murrelet_src_osc/src/osc.rs index 3e72161..f6b07b5 100644 --- a/murrelet_src_osc/src/osc.rs +++ b/murrelet_src_osc/src/osc.rs @@ -1,5 +1,7 @@ #![allow(dead_code)] -use murrelet_common::{print_expect, IsLivecodeSrc, LivecodeSrcUpdateInput, LivecodeValue}; +use murrelet_common::{ + print_expect, IsLivecodeSrc, LivecodeSrcUpdateInput, LivecodeUsage, LivecodeValue, +}; use rosc::{OscPacket, OscType}; use std::collections::HashMap; use std::net::UdpSocket; @@ -15,17 +17,54 @@ const OSC_PREFIX: &str = "/livecode/"; impl IsLivecodeSrc for OscMng { fn update(&mut self, _: &LivecodeSrcUpdateInput) { // drain all the messages available - for _ in 0..10 { + let mut i = 0; + let count = 300; + for _ in 0..count { let r = self.cxn.check_and_maybe_update(&mut self.values); if r.is_err() { break; } // leave early + i += 1 + } + if i >= count - 1 { + println!("that's a lot of osc messages to go through!"); } } fn to_exec_funcs(&self) -> Vec<(String, murrelet_common::LivecodeValue)> { self.values.to_livecode_vals() } + + fn feedback( + &mut self, + _variables: &HashMap, + outgoing_msgs: &[(String, String, LivecodeValue)], + ) { + for (_addr, name, vals) in outgoing_msgs.iter() { + match vals { + LivecodeValue::Float(v) => { + let args: Vec = vec![OscType::Float(*v as f32)]; + let osc_path = format!("{}{}", OSC_PREFIX, name); + + let msg = rosc::OscMessage { + addr: osc_path.to_string(), + args, + }; + let packet_data = rosc::encoder::encode(&OscPacket::Message(msg)); + if let Ok(pd) = packet_data { + if let Some(a) = &self.cxn.target_addr { + print_expect( + self.cxn.send_socket.send_to(&pd, a), + "failed to send osc", + ); + } + } + } + LivecodeValue::Bool(_) => {} + LivecodeValue::Int(_) => {} + } + } + } } pub struct OscMng { @@ -56,7 +95,7 @@ impl OscValues { } impl OscMng { - pub fn new_from_str(ip_address: &str) -> Self { + pub fn new_from_str(ip_address: &str, smoothed: bool, target_addr: Option) -> Self { let addr = match SocketAddrV4::from_str(ip_address) { Ok(addr) => addr, Err(_) => panic!( @@ -65,7 +104,7 @@ impl OscMng { ), }; - let cxn = OscCxn::new(&addr); + let cxn = OscCxn::new(&addr, smoothed, target_addr); Self { cxn, values: OscValues { @@ -77,20 +116,27 @@ impl OscMng { } pub struct OscCxn { + smoothed: bool, _osc_cxn: JoinHandle<()>, // keep it alive! pub osc_rx: Receiver, + send_socket: UdpSocket, + target_addr: Option, } impl OscCxn { pub fn check_and_maybe_update(&self, r: &mut OscValues) -> Result<(), mpsc::TryRecvError> { self.osc_rx.try_recv().map(|x| { - // r.msg = Some(x); + // println!("osc message {:?}", x); for (name, new_val) in x.to_livecode_vals().into_iter() { if let Some(old_val) = r.smooth_values.get(&name) { let actual_new_val = match (old_val, new_val) { (LivecodeValue::Float(old), LivecodeValue::Float(new)) => { - LivecodeValue::Float(*old * 0.9 + new * 0.1) + if self.smoothed { + LivecodeValue::Float(*old * 0.9 + new * 0.1) + } else { + LivecodeValue::Float(new) + } } _ => new_val, }; @@ -101,15 +147,22 @@ impl OscCxn { r.smooth_values.insert(name.clone(), new_val); } + // println!("{:?} {:?}", name, new_val); r.last_values.insert(name.clone(), new_val); // todo, probably good to get timestamp } }) } - pub fn new(addr: &A) -> Self { + pub fn new(addr: &A, smoothed: bool, target_addr: Option) -> Self { let (event_tx, event_rx) = mpsc::channel::(); let sock = UdpSocket::bind(addr).unwrap(); + let send_socket = sock + .try_clone() + .expect("Failed to clone socket for sending"); // Clone the socket + + println!("setting up osc"); + println!("sock {:?}", sock); let handle = std::thread::spawn(move || { let mut buf = [0u8; rosc::decoder::MTU]; @@ -132,8 +185,11 @@ impl OscCxn { }); OscCxn { + smoothed, _osc_cxn: handle, osc_rx: event_rx, + send_socket, + target_addr, } } } @@ -172,6 +228,7 @@ impl LivecodeOSC { } fn handle_packet(packet: &OscPacket) -> Option { + // println!("packet {:?}", packet); match packet { OscPacket::Message(msg) => { if let Some(osc_name) = msg.addr.as_str().strip_prefix(OSC_PREFIX) { diff --git a/murrelet_svg/Cargo.toml b/murrelet_svg/Cargo.toml index 2f25585..61f03dc 100644 --- a/murrelet_svg/Cargo.toml +++ b/murrelet_svg/Cargo.toml @@ -11,9 +11,10 @@ license = "AGPL-3.0-or-later" schemars = ["murrelet_perform/schemars", "murrelet_draw/schemars"] [dependencies] -murrelet_common = { version = "0.1.2", path = "../murrelet_common/" } -murrelet_perform = { version = "0.1.2", path = "../murrelet_perform/", default-features = false} -murrelet_draw = { version = "0.1.2", path = "../murrelet_draw/", default-features = false} -glam = "0.28.0" +murrelet_common = { workspace = true } +murrelet_perform = { workspace = true, default-features = false } +murrelet_draw = { workspace = true, default-features = false } +glam = { version = "0.28.0", features = ["serde"] } itertools = "0.10.5" svg = "0.10.0" +regex = "1.7.3" diff --git a/murrelet_svg/src/svg.rs b/murrelet_svg/src/svg.rs index 9cb94f9..ab76a25 100644 --- a/murrelet_svg/src/svg.rs +++ b/murrelet_svg/src/svg.rs @@ -13,10 +13,53 @@ use murrelet_draw::{ curve_drawer::CurveDrawer, draw::{MurreletColorStyle, MurreletStyle}, newtypes::RGBandANewtype, - style::{MurreletCurve, MurreletPath, StyledPath, StyledPathSvgFill}, + style::{MurreletCurve, MurreletPath, MurreletPathAnnotation, StyledPath, StyledPathSvgFill}, + svg::{SvgPathDef, SvgShape, TransformedSvgShape}, }; use murrelet_perform::perform::SvgDrawConfig; -use svg::{node::element::path::Data, Document, Node}; +use svg::{ + node::element::{path::Data, Group}, + Document, Node, +}; + +pub struct MurreletSvgAttributes(Vec<(String, String)>); +impl MurreletSvgAttributes { + pub fn add(&mut self, key: &str, value: &str) { + self.0.push((key.to_string(), value.to_string())) + } + + fn new(v: Vec<(String, String)>) -> Self { + Self(v) + } + + fn empty() -> Self { + Self(vec![]) + } + + fn new_single(key: &str, value: &str) -> Self { + let mut v = Self::empty(); + v.add(key, value); + v + } + + fn iter(&self) -> std::slice::Iter<'_, (String, String)> { + self.0.iter() + } + + fn add_other(&mut self, other: &Self) { + self.0.extend(other.0.clone()); + } + + fn add_float(&mut self, key: &str, stroke_weight: f32) { + self.add(key, &format!("{}", stroke_weight)) + } + + fn add_px(&mut self, key: &str, val: f32) { + self.add(key, &format!("{}px", val)) + } +} + +// this area actually follows the spec more closely #[derive(Debug, Clone)] pub struct StyledText { @@ -44,21 +87,111 @@ impl StyledText { } } +// this can take all of our favorite types, and returns the Group that applies the shape's transforms +// and styles +pub trait ToStyledGroup { + fn to_group(&self, style: &MurreletStyle) -> Option; +} + pub trait ToSvgData { - fn to_svg(&self) -> Option; + fn to_svg_data(&self) -> Option; + + fn transform(&self) -> Option { + None + } + + fn make_path(&self, style: &MurreletStyle) -> Option { + if let Some(d) = self.to_svg_data() { + let mut d = d; + + if style.closed { + d = d.close(); + } + let mut p = svg::node::element::Path::new().set("d", d); + p = style.add_svg_attributes(p); + Some(p) + } else { + None + } + } +} + +// if you implement ToSvgData, we can get the group for you +impl ToStyledGroup for T { + fn to_group(&self, style: &MurreletStyle) -> Option { + if let Some(p) = self.make_path(style) { + let mut g = Group::new(); + if let Some(t) = self.transform() { + g = g.set("transform", t.to_svg_matrix()); + } + g.append(p); + Some(g) + } else { + None + } + } } impl ToSvgData for MurreletCurve { - fn to_svg(&self) -> Option { - self.curve().to_svg() + fn to_svg_data(&self) -> Option { + self.curve().to_svg_data() + // todo!() + } + + fn transform(&self) -> Option { + Some(self.mat4()) + } +} + +impl ToStyledGroup for TransformedSvgShape { + fn to_group(&self, style: &MurreletStyle) -> Option { + let mut g = Group::new(); + g = g.set("transform", self.t.to_mat4().to_svg_matrix()); + match &self.shape { + SvgShape::Rect(s) => { + let mut rect = svg::node::element::Rectangle::new() + .set("x", s.x) + .set("y", s.y) + .set("rx", s.rx) + .set("ry", s.ry) + .set("width", s.width) + .set("height", s.height); + + rect = style.add_svg_attributes(rect); + + g.append(rect); + + Some(g) + } + SvgShape::Circle(s) => { + let mut circ = svg::node::element::Circle::new() + .set("cx", s.x) + .set("cy", s.y) + .set("r", s.r); + + circ = style.add_svg_attributes(circ); + g.append(circ); + Some(g) + } + SvgShape::Path(svg_path) => { + if let Some(data) = svg_path.to_svg_data() { + let mut path = svg::node::element::Path::new().set("d", data); + path = style.add_svg_attributes(path); + g.append(path); + } + Some(g) + } + } } } -impl ToSvgData for MurreletPath { - fn to_svg(&self) -> Option { +impl ToStyledGroup for MurreletPath { + fn to_group(&self, style: &MurreletStyle) -> Option { match self { - MurreletPath::Polyline(path) => path.into_iter_vec2().collect_vec().to_svg(), - MurreletPath::Curve(c) => c.to_svg(), + MurreletPath::Polyline(path) => path.into_iter_vec2().collect_vec().to_group(style), + MurreletPath::Curve(c) => c.to_group(style), + MurreletPath::Svg(c) => c.to_group(style), + MurreletPath::MaskedCurve(_, _) => todo!(), //curve.to_group_with_mask(style, mask), } } } @@ -76,113 +209,161 @@ impl ToSvgMatrix for Mat4 { } } -trait AddSvgAttributes { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path; +// for component types, e.g. "color" +trait GetSvgAttributes { + fn get_svg_attributes(&self) -> MurreletSvgAttributes; + + fn add_svg_attributes(&self, p: T) -> T { + let mut p = p; + let updates = self.get_svg_attributes(); + for (key, value) in &updates.0 { + p.assign(key, value.clone()); + } + p + } } -impl AddSvgAttributes for StyledPathSvgFill { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { - p.set("fill", format!("url(#P{})", self.hash())) +impl GetSvgAttributes for StyledPathSvgFill { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { + MurreletSvgAttributes::new_single("fill", &format!("url(#P{})", self.hash())) } } -impl AddSvgAttributes for MurreletColor { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { - let [r, g, b, a] = self.into_rgba_components(); - let fill = format!( - "rgba({} {} {} / {})", - (r * 255.0) as i32, - (g * 255.0) as i32, - (b * 255.0) as i32, - a - ); - p.set("fill", fill) +impl GetSvgAttributes for MurreletColor { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { + let mut v = MurreletSvgAttributes::new_single("fill", &self.to_svg_rgb()); + v.add("fill-rule", "evenodd"); + if self.alpha() < 1.0 { + v.add("fill-opacity", &format!("{}", self.alpha())); + } + v } } -impl AddSvgAttributes for RGBandANewtype { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { - self.color().add_svg_attributes(p) +impl GetSvgAttributes for RGBandANewtype { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { + self.color().get_svg_attributes() } } -impl AddSvgAttributes for MurreletColorStyle { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { +impl GetSvgAttributes for MurreletColorStyle { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { match self { - MurreletColorStyle::Color(c) => c.add_svg_attributes(p), - MurreletColorStyle::RgbaFill(c) => c.add_svg_attributes(p), - MurreletColorStyle::SvgFill(c) => c.add_svg_attributes(p), + MurreletColorStyle::Color(c) => c.get_svg_attributes(), + MurreletColorStyle::RgbaFill(c) => c.get_svg_attributes(), + MurreletColorStyle::SvgFill(c) => c.get_svg_attributes(), } } } -impl AddSvgAttributes for MurreletPath { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { - let mut p = p; +impl GetSvgAttributes for MurreletPath { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { if let Some(t) = self.transform() { - p = p.set("transform", t.to_svg_matrix()); + MurreletSvgAttributes::new_single("transform", &t.to_svg_matrix()) + } else { + MurreletSvgAttributes::empty() } - p } } -impl AddSvgAttributes for MurreletStyle { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { - let mut p = p; - if self.stroke_weight > 0.0 { - p = p.set("stroke-width", self.stroke_weight / 10.0); - } +impl GetSvgAttributes for MurreletStyle { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { + let mut v = MurreletSvgAttributes::new_single("fill-rule", "evenodd"); - if self.filled { - p = self.color.add_svg_attributes(p); + match self.drawing_plan() { + murrelet_draw::draw::MurreletDrawPlan::Shader(fill) => { + v.add_other(&fill.get_svg_attributes()); + } + murrelet_draw::draw::MurreletDrawPlan::DebugPoints(_) => unimplemented!(), + murrelet_draw::draw::MurreletDrawPlan::FilledClosed => { + // v.add("fill", self.color.get_svg_attributes()); + + v.add_other(&self.color.get_svg_attributes()); - if self.stroke_weight > 0.0 { - p = p.set("stroke", "black"); + if self.stroke_weight > 0.0 { + v.add_float("stroke-width", self.stroke_weight); + v.add("stroke-linejoin", "round"); + v.add("stroke-linecap", "round"); + + let sc = self.stroke_color.as_color(); + v.add("stroke", &sc.hex()); + if sc.alpha() < 1.0 { + v.add("stroke-opacity", &format!("{}", sc.alpha())); + } + } + } + murrelet_draw::draw::MurreletDrawPlan::Outline => { + v.add("fill", "none"); + + if self.stroke_weight > 0.0 { + v.add_float("stroke-width", self.stroke_weight); + v.add("stroke-linejoin", "round"); + v.add("stroke-linecap", "round"); + + // let sc = self.color.as_color(); + let sc = match self.stroke_color { + MurreletColorStyle::Color(_) | MurreletColorStyle::RgbaFill(_) => { + self.stroke_color.as_color() + } + _ => self.color.as_color(), + }; + v.add("stroke", &sc.hex()); + if sc.alpha() < 1.0 { + v.add("stroke-opacity", &format!("{}", sc.alpha())); + } + } + } + murrelet_draw::draw::MurreletDrawPlan::Line => { + v.add("fill", "none"); + + if self.stroke_weight > 0.0 { + v.add_float("stroke-width", self.stroke_weight); + v.add("stroke-linejoin", "round"); + v.add("stroke-linecap", "round"); + + // let sc = self.color.as_color(); + let sc = match self.stroke_color { + MurreletColorStyle::Color(_) | MurreletColorStyle::RgbaFill(_) => { + self.stroke_color.as_color() + } + _ => self.color.as_color(), + }; + v.add("stroke", &sc.hex()); + if sc.alpha() < 1.0 { + v.add("stroke-opacity", &format!("{}", sc.alpha())); + } + } } - } else { - p = p.set("fill", "none"); - p = p.set("stroke", "black"); } - - p + v } } -impl AddSvgAttributes for StyledPath { - fn add_svg_attributes(&self, p: svg::node::element::Path) -> svg::node::element::Path { - let mut p = p; - p = self.path.add_svg_attributes(p); - p = self.style.add_svg_attributes(p); - p +impl GetSvgAttributes for StyledPath { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { + let mut c = self.annotations.get_svg_attributes(); + c.add_other(&self.path.get_svg_attributes()); + c.add_other(&self.style.get_svg_attributes()); + c } } -impl ToSvgData for StyledPath { - fn to_svg(&self) -> Option { - self.path.to_svg() +impl GetSvgAttributes for MurreletPathAnnotation { + fn get_svg_attributes(&self) -> MurreletSvgAttributes { + let mut a = MurreletSvgAttributes::empty(); + for (k, v) in self.vals() { + a.add(k, v); + } + a } } pub trait ToSvgPath { - fn make_path(&self) -> Option; + fn make_group(&self) -> Option; fn make_pattern(&self) -> Option<(String, svg::node::element::Pattern)>; } impl ToSvgPath for StyledPath { - fn make_path(&self) -> Option { - if let Some(d) = self.path.to_svg() { - let mut d = d; - if self.style.closed { - d = d.close(); - } - let mut p = svg::node::element::Path::new().set("d", d); - p = self.add_svg_attributes(p); - Some(p) - } else { - None - } - } - fn make_pattern(&self) -> Option<(String, svg::node::element::Pattern)> { if let MurreletColorStyle::SvgFill(f) = self.style.color { // ooookay so the whole purpose of this is to have pattern transform @@ -207,6 +388,12 @@ impl ToSvgPath for StyledPath { None } } + + fn make_group(&self) -> Option { + self.path + .to_group(&self.style) + .map(|x| self.annotations.add_svg_attributes(x)) + } } impl TransformVec2 for SvgDocCreator { @@ -244,7 +431,9 @@ impl SvgDocCreator { .set("x", text.loc.x) .set("y", text.loc.y) .set("text-anchor", "middle") + .set("font-family", "monospace".to_string()) .set("font-size", format!("{}px", text_size)) + .set("fill", text.style.color.as_color().hex()) .add(svg::node::Text::new(text.text.clone())); text @@ -268,7 +457,7 @@ impl SvgDocCreator { let mut patterns = Vec::new(); for path in layer.paths.iter() { - if let Some(d) = path.make_path() { + if let Some(d) = path.make_group() { // if it's using a svg pattern, we should add that attribute if let Some((key, pattern)) = path.make_pattern() { // the path already will have the id on it, so just make sure it's @@ -298,11 +487,14 @@ impl SvgDocCreator { let mut defs = vec![]; for (name, layer) in paths.layers.iter() { - let (g, patterns) = self.make_layer(name, layer, false); + let (g, patterns) = self.make_layer(name, layer, self.svg_draw_config.make_layers()); doc.append(g); for p in patterns { defs.push(p.to_string()); } + + // TODO REMOVE THISS + defs.push(name.clone()); } (doc, defs.into_iter().join("\n")) @@ -311,28 +503,49 @@ impl SvgDocCreator { // this one's meant for svgs for pen plotters, so it drops fill styles fn make_doc(&self, paths: &SvgPathCache) -> Document { let target_size = self.svg_draw_config.full_target_width(); // guides are at 10x 10x, gives 1cm margin + + let (view_box_x, view_box_y) = if let Some(r) = self.svg_draw_config.resolution { + let [width, height] = r.as_dims(); + // (width * 2, height * 2) // i'm not sure why i had this? + (width, height) + } else { + (800, 800) + }; let mut doc = Document::new() .set( "xmlns:inkscape", "http://www.inkscape.org/namespaces/inkscape", ) - .set( - "viewBox", - ( - target_size / 2.0, - target_size / 2.0, - target_size, - target_size, - ), - ) + .set("viewBox", (0, 0, view_box_x, view_box_y)) .set("width", format!("{:?}mm", target_size)) .set("height", format!("{:?}mm", target_size)); - for (name, layer) in paths.layers.iter() { - let (g, _) = self.make_layer(name, layer, true); - doc.append(g); + if let Some(bg_color) = self.svg_draw_config.bg_color() { + let mut bg_rect = svg::node::element::Rectangle::new() + .set("x", 0) + .set("y", 0) + .set("width", view_box_x) + .set("height", view_box_y) + .set("fill", bg_color.to_svg_rgb()); + + if bg_color.alpha() < 1.0 { + bg_rect = bg_rect.set("fill-opacity", bg_color.to_svg_rgb()); + } + doc = doc.add(bg_rect); } + // todo, maybe figure out defs? + let (group, _) = self.make_html(paths); + + let mut centering_group = svg::node::element::Group::new(); + centering_group = centering_group.set( + "transform", + format!("translate({}px, {}px)", view_box_x / 2, view_box_y / 2), + ); + centering_group = centering_group.add(group); + + doc = doc.add(centering_group); + doc } @@ -417,6 +630,12 @@ pub struct SvgLayer { paths: Vec, text: Vec, } +impl Default for SvgLayer { + fn default() -> Self { + Self::new() + } +} + impl SvgLayer { pub fn new() -> SvgLayer { SvgLayer { @@ -462,7 +681,7 @@ impl SvgPathCacheRef { self.0.borrow_mut().add_styled_text(layer, text) } - pub fn make_html(&self) -> Vec { + pub fn make_html(&self) -> (String, String) { self.0.borrow().make_html() } } @@ -497,28 +716,19 @@ impl SvgPathCache { } pub fn add_simple_path_no_transform(&mut self, layer: &str, line: Vec) { - let layer = self - .layers - .entry(layer.to_owned()) - .or_insert(SvgLayer::new()); + let layer = self.layers.entry(layer.to_owned()).or_default(); layer.paths.push(StyledPath::from_path(line)); } pub fn add_simple_path(&mut self, layer: &str, line: Vec) { - let layer = self - .layers - .entry(layer.to_owned()) - .or_insert(SvgLayer::new()); + let layer = self.layers.entry(layer.to_owned()).or_default(); layer.paths.push(StyledPath::from_path( self.config.transform_many_vec2(&line), )); } pub fn add_styled_path(&mut self, layer: &str, styled_path: StyledPath) { - let layer = self - .layers - .entry(layer.to_owned()) - .or_insert(SvgLayer::new()); + let layer = self.layers.entry(layer.to_owned()).or_default(); layer.paths.push( styled_path .transform_with_mat4_after(self.config.svg_draw_config().transform_for_size()), @@ -526,20 +736,14 @@ impl SvgPathCache { } pub fn add_styled_text(&mut self, layer: &str, text: StyledText) { - let layer = self - .layers - .entry(layer.to_owned()) - .or_insert(SvgLayer::new()); + let layer = self.layers.entry(layer.to_owned()).or_default(); layer .text .push(text.transform_with(self.config.svg_draw_config())); } pub fn clear(&mut self, layer: &str) { - let layer = self - .layers - .entry(layer.to_owned()) - .or_insert(SvgLayer::new()); + let layer = self.layers.entry(layer.to_owned()).or_default(); layer.clear(); } @@ -549,6 +753,7 @@ impl SvgPathCache { StyledPath { path: MurreletPath::Polyline(path.as_polyline()), style: MurreletStyle::new_outline(), + annotations: MurreletPathAnnotation::noop(), }, ) } @@ -563,23 +768,38 @@ impl SvgPathCache { // can add these to a document. I don't give the full svg so I can leave things // like defs alone and just update the paths and patternTransforms. - pub fn make_html(&self) -> Vec { + pub fn make_html(&self) -> (String, String) { let (paths, defs) = self.config.make_html(self); - vec![defs.to_string(), paths.to_string()] + (defs.to_string(), paths.to_string()) } } -pub trait ToSvg { - fn to_svg(&self) -> Option; +// why am i doing this again, it is a good question +impl ToSvgData for SvgPathDef { + fn to_svg_data(&self) -> Option { + let mut path = Data::new(); + + path = path.move_to(self.svg_move_to()); + + for v in self.cmds() { + match v { + murrelet_draw::svg::SvgCmd::Line(svg_to) => path = path.line_to(svg_to.params()), + murrelet_draw::svg::SvgCmd::CubicBezier(svg_cubic_bezier) => { + path = path.cubic_curve_to(svg_cubic_bezier.params()) + } + murrelet_draw::svg::SvgCmd::ArcTo(svg_arc) => { + path = path.elliptical_arc_to(svg_arc.params()) + } + } + } - fn to_svg_closed(&self) -> Option { - self.to_svg().map(|x| x.close()) + Some(path) } } -impl ToSvg for CurveDrawer { - fn to_svg(&self) -> Option { +impl ToSvgData for CurveDrawer { + fn to_svg_data(&self) -> Option { let segments = self.segments(); if segments.is_empty() { return None; @@ -598,45 +818,57 @@ impl ToSvg for CurveDrawer { } match curve { - murrelet_draw::curve_drawer::CurveSegment::Arc(a) => { - let f = a.first_point(); + murrelet_draw::curve_drawer::CurveSegment::CubicBezier(cb) => { + let f = cb.first_point(); // first make sure we're at the first point if curr_point != Some(f) { path = path.line_to((f.x, f.y)); } - let last_point = a.last_point(); - let params = ( - a.radius, - a.radius, // same as other rad because it's a circle - 0.0, // angle of ellipse doesn't matter, so 0 - if a.is_large_arc() { 1 } else { 0 }, // large arc flag - if a.is_ccw() { 1 } else { 0 }, // sweep-flag - last_point.x, - last_point.y, + cb.ctrl1().x, + cb.ctrl1().y, + cb.ctrl2().x, + cb.ctrl2().y, + cb.to().x, + cb.to().y, ); - path = path.elliptical_arc_to(params); + path = path.cubic_curve_to(params); + + curr_point = Some(cb.to()) + } + murrelet_draw::curve_drawer::CurveSegment::Arc(a) => { + let f = a.first_point(); + // first make sure we're at the first point + if curr_point != Some(f) { + path = path.line_to((f.x, f.y)); + } + let last_point = a.last_point(); + + // special cases for circles! + if let Some((hemi1, hemi2)) = a.is_full_circle_then_split() { + path = path.elliptical_arc_to(hemi1.svg_params().to_vec()); + path = path.elliptical_arc_to(hemi2.svg_params().to_vec()); + } else { + path = path.elliptical_arc_to(a.svg_params().to_vec()); + } curr_point = Some(last_point) } murrelet_draw::curve_drawer::CurveSegment::Points(a) => { let maybe_first_and_rest = a.points().split_first(); - match maybe_first_and_rest { - Some((f, rest)) => { - // first make sure we're at the first point - if curr_point != Some(*f) { - path = path.line_to((f.x, f.y)); - } - - for v in rest { - path = path.line_to((v.x, v.y)); - } - - curr_point = Some(a.last_point()) + if let Some((f, rest)) = maybe_first_and_rest { + // first make sure we're at the first point + if curr_point != Some(*f) { + path = path.line_to((f.x, f.y)); + } + + for v in rest { + path = path.line_to((v.x, v.y)); } - None => {} + + curr_point = Some(a.last_point()) } } } @@ -647,20 +879,14 @@ impl ToSvg for CurveDrawer { } // just connect the dots with lines -impl ToSvg for Vec { - fn to_svg(&self) -> Option { - if self.len() == 0 { +impl ToSvgData for Vec { + fn to_svg_data(&self) -> Option { + if self.is_empty() { return None; } - let mut curr_item: Vec2 = *self.first().unwrap(); - - let mut data = Data::new().move_to((curr_item.x, curr_item.y)); - - for loc in self[1..].iter() { - data = data.line_by((loc.x - curr_item.x, loc.y - curr_item.y)); - curr_item = *loc; - } - Some(data) + // todo, hmmm, see if we can consolidate this. + let cd = CurveDrawer::new_simple_points(self.clone(), false); + cd.to_svg_data() } }