This commit is contained in:
Spencer Killen 2023-12-29 16:21:24 -07:00
parent e0f00392d2
commit f06f741a74
Signed by: sjkillen
GPG Key ID: 3AF3117BA6FBB75B
8 changed files with 212 additions and 35 deletions

View File

@ -38,7 +38,7 @@ pub struct Point {
pub z: f32, pub z: f32,
} }
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
pub struct MDDFrame { pub struct MDDFrame {
pub frame_idx: usize, pub frame_idx: usize,
pub point_idx: usize, pub point_idx: usize,

9
tools/bundle/bundle.gd Normal file
View File

@ -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)

View File

@ -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;

View File

5
tools/pack.bash Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# $@: list of file basenames for .exr files
mkdir -p bundle
./pack_res.gd ++ $@

96
tools/pack_res.gd Executable file
View File

@ -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)

View File

@ -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 // 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; // TODO make images less than 16k
use openexr::core::channel_list::{CHANNEL_FLOAT, CHANNEL_UINT};
use openexr::core::channel_list::Channel;
use openexr::core::frame_buffer::{FrameBuffer, Slice}; use openexr::core::frame_buffer::{FrameBuffer, Slice};
use openexr::core::header::Header; use openexr::core::header::Header;
use openexr::core::output_file::OutputFile; use openexr::core::output_file::OutputFile;
use openexr::core::PixelType; use openexr::core::PixelType;
use openexr::rgba::{Rgba, RgbaChannels, RgbaOutputFile};
use openexr::tiled::TiledOutputFile;
use pointcache::{MDDFrame, MDDSeekableFile, Point, PointCache}; use pointcache::{MDDFrame, MDDSeekableFile, Point, PointCache};
use std::default;
use std::{error::Error, fs::File}; use std::{error::Error, fs::File};
// https://github.com/vfx-rs/openexr-rs/blob/25826b4f89bc768b565ba150d6f9c76876ad6bc3/src/core/output_file.rs#L275 // 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 // Godot doesn't seem to support Uint images, using f32 instead
fn write_mask_image( fn write_mask_image(
filename: &str, filename: &str,
@ -24,7 +37,7 @@ fn write_mask_image(
let height: usize = 1; let height: usize = 1;
let mut header = Header::from_dimensions(width as i32, height as i32); 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(); let mut frame_buffer = FrameBuffer::new();
@ -54,9 +67,9 @@ fn write_point_image(
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let mut header = Header::from_dimensions(width as i32, height as i32); 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());
header.channels_mut().insert("G", &CHANNEL_FLOAT); header.channels_mut().insert("G", &non_color_channel());
header.channels_mut().insert("B", &CHANNEL_FLOAT); header.channels_mut().insert("B", &non_color_channel());
let mut frame_buffer = FrameBuffer::new(); let mut frame_buffer = FrameBuffer::new();
@ -91,34 +104,42 @@ fn write_point_image(
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum PointState { enum PointState {
Unseen, Unseen,
OneValue(Point), OneValue {
Varying(usize), point_idx: usize,
point: Point,
},
Varying {
point: Point,
new_idx: usize,
count: usize,
},
} }
use PointState::*; use PointState::*;
impl PointState { impl PointState {
fn mask_value(&self) -> f32 { fn mask_value(&self) -> f32 {
match *self { match *self {
Unseen => panic!(), Unseen => panic!("no point should be unseen"),
OneValue(_) => -1.0, OneValue { .. } => -1.0,
Varying(idx) => idx as f32, 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() { fn main() {
let mdd_filename = std::env::args() let basename = std::env::args()
.nth(1) .nth(1)
.expect("expected first argument to be a file path to an .mdd file"); .expect("expected first argument to be the basename to an .mdd file in the CWD");
let path = std::env::args()
.nth(2) let mdd_filename = basename.clone() + ".mdd";
.expect("expected second argument to be an output directory");
let mut mdd_file = File::open(mdd_filename).expect("Mdd filename is not valid"); 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 (total_frames, total_points) = MDDSeekableFile::read_header(&mut mdd_file).unwrap();
let mut mask_last: Vec<PointState> = vec![Unseen; total_points as usize]; let mut mask_last: Vec<PointState> = vec![Unseen; total_points as usize];
let pc = MDDSeekableFile::read(mdd_file.try_clone().unwrap()).unwrap(); let pc = MDDSeekableFile::read(mdd_file.try_clone().unwrap()).unwrap();
let mut total_varying = 0; let mut total_varying = 0;
for frame in pc { for frame in pc {
@ -126,29 +147,67 @@ fn main() {
point, point_idx, .. point, point_idx, ..
} = frame.unwrap(); } = frame.unwrap();
match mask_last[point_idx] { match mask_last[point_idx] {
Unseen => mask_last[point_idx] = OneValue(point), Unseen => mask_last[point_idx] = OneValue { point_idx, point },
OneValue(old_point) => { OneValue {
if point != old_point { point_idx: old_point_idx,
mask_last[point_idx] = Varying(total_varying); 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; 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<f32> = mask_last.iter().map(PointState::mask_value).collect(); let mask: Vec<f32> = mask_last.iter().map(PointState::mask_value).collect();
// write_mask_image( write_mask_image(
// "/home/rat/cow/godot/vertex_animation/mask.exr", &(basename.clone() + "_mask.exr"),
// &pixels.collect(), &mask,
// width as usize, total_points as usize,
// ) )
// .unwrap(); .unwrap();
let mut pixels: Vec<Point> = Vec::with_capacity(total_varying * (total_frames as usize)); let mut pixels: Vec<Point> = Vec::with_capacity(total_varying * (total_frames as usize));
let pc = MDDSeekableFile::read(mdd_file.try_clone().unwrap()).unwrap(); 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| { pixels.extend(pc.filter_map(|frame| {
let MDDFrame { let MDDFrame {
@ -156,19 +215,22 @@ fn main() {
} = frame.unwrap(); } = frame.unwrap();
match mask_last[point_idx] { match mask_last[point_idx] {
Unseen => panic!(), Unseen => panic!(),
OneValue(_) => None, OneValue { .. } => None,
Varying(_) => Some(point), Varying { .. } => Some(point)
} }
})); }));
assert!(pixels.len() == total_varying * (total_frames as usize)); assert!(pixels.len() == total_varying * (total_frames as usize));
write_point_image( write_point_image(
"/home/rat/cow/godot/vertex_animation/test.exr", &(basename + ".exr"),
&pixels, &pixels,
total_varying, total_varying,
total_frames as usize, total_frames as usize,
) )
.unwrap(); .unwrap();
println!("Total {} varying points", total_varying);
} }

Binary file not shown.