This commit is contained in:
parent
e0f00392d2
commit
f06f741a74
|
@ -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,
|
||||
|
|
|
@ -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)
|
|
@ -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;
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# $@: list of file basenames for .exr files
|
||||
|
||||
mkdir -p bundle
|
||||
./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)
|
|
@ -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<dyn Error>> {
|
||||
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<PointState> = 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<f32> = 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<Point> = 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);
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue