diff --git a/src/lib.rs b/src/lib.rs index 031568f..f2c7874 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,7 +38,7 @@ pub struct Point { pub z: f32, } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct MDDFrame { pub frame_idx: usize, pub point_idx: usize, diff --git a/tools/bundle/bundle.gd b/tools/bundle/bundle.gd new file mode 100644 index 0000000..48e6372 --- /dev/null +++ b/tools/bundle/bundle.gd @@ -0,0 +1,9 @@ +extends Resource +@export var basenames: PackedStringArray +@export var shader: ShaderInclude +@export var image_cow: Texture2D +@export var image_mask_cow: Texture2D + +func attach(mat: ShaderMaterial): + mat.set_shader_parameter('image_cow', image_cow) + mat.set_shader_parameter('image_mask_cow', image_mask_cow) diff --git a/tools/bundle/bundle.gdshaderinc b/tools/bundle/bundle.gdshaderinc new file mode 100644 index 0000000..1ec587d --- /dev/null +++ b/tools/bundle/bundle.gdshaderinc @@ -0,0 +1,5 @@ + +uniform sampler2D image_cow: repeat_disable; +uniform sampler2D image_mask_cow: repeat_disable; +uniform bool enabled_cow = false; +uniform int start_time_cow; diff --git a/tools/output.exr b/tools/output.exr deleted file mode 100644 index e69de29..0000000 diff --git a/tools/pack.bash b/tools/pack.bash new file mode 100755 index 0000000..f7f6b2c --- /dev/null +++ b/tools/pack.bash @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# $@: list of file basenames for .exr files + +mkdir -p bundle +./pack_res.gd ++ $@ diff --git a/tools/pack_res.gd b/tools/pack_res.gd new file mode 100755 index 0000000..ea01304 --- /dev/null +++ b/tools/pack_res.gd @@ -0,0 +1,96 @@ +#!/usr/bin/env -S godot -s --headless +# To be called with args "++ basename, basename, ..." + +# Fragile script +# Relies on instantiating instead of using set_script +# Godot ignores exported properties unless you reload script +# Can't call methods directly because it's a parse error +# Also can't use .set() because then the properties aren't exported. + +@tool +extends SceneTree + +var bundle_name := "bundle.res" + +func _init(): + var script := GDScript.new() + var basenames := OS.get_cmdline_user_args() + script.source_code = make_all_headers(basenames, true) + script.reload() + var res: Resource = script.new() + + res.basenames = basenames + for basename in basenames: + add_images(res, basename) + + + var shader := ShaderInclude.new() + for basename in basenames: + shader.code += make_shader(basename) + + script.source_code = make_all_headers(basenames, false) + + + # Cannot use built-in scripts + # See: https://github.com/godotengine/godot/issues/85876 + script.resource_path = "res://bundle/bundle.gd" + ResourceSaver.save(script, "res://bundle/bundle.gd") + res.shader = shader + res.shader.resource_name = "shader" + shader.resource_path = "res://bundle/bundle.gdshaderinc" + ResourceSaver.save(shader, "res://bundle/bundle.gdshaderinc", ResourceSaver.FLAG_CHANGE_PATH) + ResourceSaver.save(res, "res://bundle/" + bundle_name, ResourceSaver.FLAG_CHANGE_PATH) + + quit() + + +func make_all_shaders(basenames): + for basename in basenames: + pass + +func make_shader(basename: String): + var code = "\nuniform sampler2D image_" + basename + ": repeat_disable;\n" + code += "uniform sampler2D image_mask_" + basename + ": repeat_disable;\n" + code += "uniform bool enabled_" + basename + " = false;\n" + code += "uniform int start_time_" + basename + ";\n" + return code + +func make_all_headers(basenames, include_setters): + var header := "extends Resource\n" + header += "@export var basenames: PackedStringArray\n" + header += "@export var shader: ShaderInclude\n" + for basename in basenames: + header += make_header(basename) + if include_setters: + header += make_setters(basename) + return header + attach_code(basenames) + +func attach_code(basenames): + var code := "\nfunc attach(mat: ShaderMaterial):\n" + for basename in basenames: + code += " mat.set_shader_parameter('image_"+basename+"', image_"+basename+")\n" + code += " mat.set_shader_parameter('image_mask_"+basename+"', image_mask_"+basename+")\n" + return code + +func make_header(basename: String): + var header := "@export var image_" + basename + ": Texture2D\n" + header += "@export var image_mask_" + basename + ": Texture2D\n" + return header + +func make_setters(basename: String): + var body := """ +func set_image_"""+basename+"""(image):\n image_""" + basename + """ = image +func set_image_mask_"""+basename+"""(image):\n image_mask_""" + basename + """ = image +""" + return body + + +func add_images(res, basename: String): + var img := Image.new() + img.load(basename + ".exr") + var tex := ImageTexture.create_from_image(img) + res.call("set_image_"+basename, tex) + img = Image.new() + img.load(basename + "_mask.exr") + tex = ImageTexture.create_from_image(img) + res.call("set_image_mask_"+basename, tex) diff --git a/tools/src/main.rs b/tools/src/main.rs index 29f4dd1..61eee31 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -1,19 +1,32 @@ +// Creates two image files for a given [basename].mdd file +// First image contains index of every active vertex in the bigger image file, vertices that don't move have a value of -1 in the mask image. + // Openexr library is too complex to go off documentation alone and no projects on github using library. Seek out test cases -use imath_traits::Zero; -use openexr::core::channel_list::{CHANNEL_FLOAT, CHANNEL_UINT}; +// TODO make images less than 16k + +use openexr::core::channel_list::Channel; use openexr::core::frame_buffer::{FrameBuffer, Slice}; use openexr::core::header::Header; use openexr::core::output_file::OutputFile; use openexr::core::PixelType; -use openexr::rgba::{Rgba, RgbaChannels, RgbaOutputFile}; -use openexr::tiled::TiledOutputFile; use pointcache::{MDDFrame, MDDSeekableFile, Point, PointCache}; -use std::default; use std::{error::Error, fs::File}; // https://github.com/vfx-rs/openexr-rs/blob/25826b4f89bc768b565ba150d6f9c76876ad6bc3/src/core/output_file.rs#L275 +pub const MAX_IMAGE_DIM: usize = 16384; +pub const MOVEMENT_EPSILON: f32 = 0.0000001; + +fn non_color_channel() -> Channel { + Channel { + type_: PixelType::Float.into(), + x_sampling: 1, + y_sampling: 1, + p_linear: false, + } +} + // Godot doesn't seem to support Uint images, using f32 instead fn write_mask_image( filename: &str, @@ -24,7 +37,7 @@ fn write_mask_image( let height: usize = 1; let mut header = Header::from_dimensions(width as i32, height as i32); - header.channels_mut().insert("R", &CHANNEL_FLOAT); + header.channels_mut().insert("R", &non_color_channel()); let mut frame_buffer = FrameBuffer::new(); @@ -54,9 +67,9 @@ fn write_point_image( ) -> Result<(), Box> { let mut header = Header::from_dimensions(width as i32, height as i32); - header.channels_mut().insert("R", &CHANNEL_FLOAT); - header.channels_mut().insert("G", &CHANNEL_FLOAT); - header.channels_mut().insert("B", &CHANNEL_FLOAT); + header.channels_mut().insert("R", &non_color_channel()); + header.channels_mut().insert("G", &non_color_channel()); + header.channels_mut().insert("B", &non_color_channel()); let mut frame_buffer = FrameBuffer::new(); @@ -91,34 +104,42 @@ fn write_point_image( #[derive(Debug, Clone, Copy)] enum PointState { Unseen, - OneValue(Point), - Varying(usize), + OneValue { + point_idx: usize, + point: Point, + }, + Varying { + point: Point, + new_idx: usize, + count: usize, + }, } use PointState::*; impl PointState { fn mask_value(&self) -> f32 { match *self { - Unseen => panic!(), - OneValue(_) => -1.0, - Varying(idx) => idx as f32, + Unseen => panic!("no point should be unseen"), + OneValue { .. } => -1.0, + Varying { new_idx, .. } => new_idx as f32, } } + fn point_changed(a: &Point, b: &Point) -> bool { + ((a.x - b.x).abs() + (a.y - b.y).abs() + (a.z - b.z).abs()) > MOVEMENT_EPSILON + } } fn main() { - let mdd_filename = std::env::args() + let basename = std::env::args() .nth(1) - .expect("expected first argument to be a file path to an .mdd file"); - let path = std::env::args() - .nth(2) - .expect("expected second argument to be an output directory"); + .expect("expected first argument to be the basename to an .mdd file in the CWD"); + + let mdd_filename = basename.clone() + ".mdd"; let mut mdd_file = File::open(mdd_filename).expect("Mdd filename is not valid"); let (total_frames, total_points) = MDDSeekableFile::read_header(&mut mdd_file).unwrap(); let mut mask_last: Vec = vec![Unseen; total_points as usize]; - let pc = MDDSeekableFile::read(mdd_file.try_clone().unwrap()).unwrap(); let mut total_varying = 0; for frame in pc { @@ -126,29 +147,67 @@ fn main() { point, point_idx, .. } = frame.unwrap(); match mask_last[point_idx] { - Unseen => mask_last[point_idx] = OneValue(point), - OneValue(old_point) => { - if point != old_point { - mask_last[point_idx] = Varying(total_varying); + Unseen => mask_last[point_idx] = OneValue { point_idx, point }, + OneValue { + point_idx: old_point_idx, + point: old_point, + } => { + assert_eq!(old_point_idx, point_idx); + if PointState::point_changed(&point, &old_point) { + mask_last[point_idx] = Varying { + point, + new_idx: total_varying, + count: 1, + }; total_varying += 1; } } - Varying(_) => {} + Varying { + point: old_point, + new_idx, + count, + } => { + if PointState::point_changed(&point, &old_point) { + mask_last[point_idx] = Varying { + point, + new_idx, + count: count + 1, + }; + } + } } } let mask: Vec = mask_last.iter().map(PointState::mask_value).collect(); - // write_mask_image( - // "/home/rat/cow/godot/vertex_animation/mask.exr", - // &pixels.collect(), - // width as usize, - // ) - // .unwrap(); + write_mask_image( + &(basename.clone() + "_mask.exr"), + &mask, + total_points as usize, + ) + .unwrap(); let mut pixels: Vec = Vec::with_capacity(total_varying * (total_frames as usize)); let pc = MDDSeekableFile::read(mdd_file.try_clone().unwrap()).unwrap(); + let pc: Vec<_> = pc.collect(); + let max_point_idx = pc + .iter() + .map(|frame| frame.as_ref().unwrap()) + .map(|MDDFrame { point_idx, .. }| point_idx) + .cloned() + .max() + .unwrap(); + assert_eq!(max_point_idx + 1, total_points as usize); + + let varying_count = 17; + let varied = mask_last.iter().filter(|x| match x { + Varying { count, .. } => *count > varying_count, + _ => false + }).count(); + println!("There were {} points that varied more than {} times", varied, varying_count); + + let pc = pc.into_iter(); pixels.extend(pc.filter_map(|frame| { let MDDFrame { @@ -156,19 +215,22 @@ fn main() { } = frame.unwrap(); match mask_last[point_idx] { Unseen => panic!(), - OneValue(_) => None, - Varying(_) => Some(point), + OneValue { .. } => None, + Varying { .. } => Some(point) } })); + + assert!(pixels.len() == total_varying * (total_frames as usize)); write_point_image( - "/home/rat/cow/godot/vertex_animation/test.exr", + &(basename + ".exr"), &pixels, total_varying, total_frames as usize, ) .unwrap(); + println!("Total {} varying points", total_varying); } diff --git a/tools/write_deep1.exr b/tools/write_deep1.exr deleted file mode 100644 index e633744..0000000 Binary files a/tools/write_deep1.exr and /dev/null differ