Skip to content

Commit

Permalink
Add --apply-any-animation option
Browse files Browse the repository at this point in the history
This disables our heuristic that the number of TRS curves in an
animation and the number of objects in a model must be the same.
If there too few TRS curves, we use the identity for the extra
object matrices.

This is needed for certain models in Ore ga Omae o Mamoru, eg.
the mecha boss.
  • Loading branch information
scurest committed Mar 11, 2018
1 parent bb18923 commit 7c39f76
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 54 deletions.
76 changes: 43 additions & 33 deletions src/convert/collada.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,21 +497,24 @@ fn write_library_controllers<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
}

fn write_library_animations<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
let num_objects = ctx.model.objects.len();
let any_animations = ctx.db.animations.iter().any(|a| a.objects_curves.len() == num_objects);

if !any_animations {
return Ok(()); // no matching animations
}

let matching_anims = ctx.db.animations.iter().enumerate()
.filter(|&(_, a)| a.objects_curves.len() == num_objects);
// COLLADA is dumb and doesn't allow an empty <library_animations>
// so we emit the open tag lazily if we find any. This flag remembers
// if we've emitted it yet.
let mut start_tag_emitted = false;

for anim_id in 0..ctx.db.animations.len() {
if !ctx.db.can_apply_anim(ctx.model_id, anim_id) {
continue;
}

write_lines!(w,
r##" <library_animations>"##;
)?;
if !start_tag_emitted {
write_lines!(w,
r##" <library_animations>"##;
)?;
start_tag_emitted = true;
}

for (anim_id, anim) in matching_anims {
let anim = &ctx.db.animations[anim_id];
let num_frames = anim.num_frames;

for joint_id in ctx.skel.tree.node_indices() {
Expand All @@ -520,7 +523,6 @@ fn write_library_animations<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
Transform::Object(id) => id,
_ => continue,
};
let trs_curves = &anim.objects_curves[object_id as usize];

write_lines!(w,
r##" <animation id="anim{anim_id}-joint{joint_id}">"##;
Expand Down Expand Up @@ -557,7 +559,10 @@ fn write_library_animations<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
anim_id = anim_id, joint_id = joint_id.index(), num_floats = 16 * num_frames, num_frames = num_frames,
mats = FnFmt(|f| {
for frame in 0..num_frames {
write!(f, "{} ", Mat(&trs_curves.sample_at(frame)))?;
let mat = anim.objects_curves.get(object_id as usize)
.map(|trs| trs.sample_at(frame))
.unwrap_or_else(|| Matrix4::one());
write!(f, "{} ", Mat(&mat))?;
}
Ok(())
}),
Expand Down Expand Up @@ -601,30 +606,33 @@ fn write_library_animations<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
}
}

write_lines!(w,
r##" </library_animations>"##;
)?;
if start_tag_emitted {
write_lines!(w,
r##" </library_animations>"##;
)?;
}

Ok(())
}


fn write_library_animation_clips<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
let num_objects = ctx.model.objects.len();
let any_animations = ctx.db.animations.iter().any(|a| a.objects_curves.len() == num_objects);
// Same as above.
let mut start_tag_emitted = false;

if !any_animations {
return Ok(()); // no matching animations
}

let matching_anims = ctx.db.animations.iter().enumerate()
.filter(|&(_, a)| a.objects_curves.len() == num_objects);
for anim_id in 0..ctx.db.animations.len() {
if !ctx.db.can_apply_anim(ctx.model_id, anim_id) {
continue;
}

write_lines!(w,
r##" <library_animation_clips>"##;
)?;
if !start_tag_emitted {
write_lines!(w,
r##" <library_animation_clips>"##;
)?;
start_tag_emitted = true;
}

for (anim_id, anim) in matching_anims {
let anim = &ctx.db.animations[anim_id];
check!(anim.num_frames != 0)?;
let end_time = (anim.num_frames - 1) as f64 * FRAME_LENGTH;

Expand All @@ -643,9 +651,11 @@ fn write_library_animation_clips<W: Write>(w: &mut W, ctx: &Ctx) -> Result<()> {
)?;
}

write_lines!(w,
r##" </library_animation_clips>"##;
)?;
if start_tag_emitted {
write_lines!(w,
r##" </library_animation_clips>"##;
)?;
}

Ok(())
}
Expand Down
54 changes: 39 additions & 15 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use clap::ArgMatches;
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use nitro::{Name, Model, Texture, Palette, Animation, Container};
use errors::Result;
use errors::{Result, ResultExt};
use util::cur::Cur;

pub type FileId = usize;
pub type ModelId = usize;
pub type TextureId = usize;
pub type PaletteId = usize;
pub type AnimationId = usize;
pub type MaterialId = usize;

#[derive(Default)]
Expand All @@ -30,6 +31,10 @@ pub struct Database {

pub textures_by_name: HashMap<Name, Vec<TextureId>>,
pub palettes_by_name: HashMap<Name, Vec<PaletteId>>,

/// If this is true, the heuristic that an animation applies to a model only
/// if they have the same number of objects is disabled.
pub apply_any_animation: bool,
}

pub enum ImageDesc {
Expand All @@ -48,7 +53,16 @@ impl Database {
.values_of_os("INPUT").unwrap()
.map(PathBuf::from)
.collect();
Database::build(file_paths)

let apply_any_animation =
matches
.is_present("apply_any_animation");

use std::default::Default;
let mut db: Database = Default::default();
db.build(file_paths)?;
db.apply_any_animation = apply_any_animation;
Ok(db)
}

pub fn print_status(&self) {
Expand All @@ -64,38 +78,38 @@ impl Database {
);
}

pub fn build(file_paths: Vec<PathBuf>) -> Result<Database> {
use std::default::Default;

let mut db: Database = Default::default();
db.file_paths = file_paths;
fn build(&mut self, file_paths: Vec<PathBuf>) -> Result<()> {
self.file_paths = file_paths;

debug!("Building database...");

for file_id in 0..db.file_paths.len() {
debug!("Processing {:?}...", db.file_paths[file_id]);
for file_id in 0..self.file_paths.len() {
debug!("Processing {:?}...", self.file_paths[file_id]);

// Hard-fail if we can't open the path. We don't expect the caller
// to know which files are valid Nitro files but we expect them to
// give us files we can actually open.
let buf = read_file(&db.file_paths[file_id])?;
let buf = read_file(&self.file_paths[file_id])
.chain_err(|| {
format!("couldn't read file: {}", &self.file_paths[file_id].to_string_lossy())
})?;

use nitro::container::read_container;
match read_container(Cur::new(&buf)) {
Ok(cont) => {
db.add_container(file_id, cont);
self.add_container(file_id, cont);
}
Err(e) => {
error!("error in file {}: {}",
db.file_paths[file_id].to_string_lossy(), e);
self.file_paths[file_id].to_string_lossy(), e);
}
}
}

db.build_by_name_maps();
db.build_material_table();
self.build_by_name_maps();
self.build_material_table();

Ok(db)
Ok(())
}

fn add_container(&mut self, file_id: FileId, cont: Container) {
Expand Down Expand Up @@ -232,6 +246,16 @@ impl Database {
warn!("multiple palettes named {:?}; using the first one", name);
Some(candidates[0])
}

/// Can we apply the given animation to the given model?
///
/// The default heuristic is "yes" if have the same number of objects.
pub fn can_apply_anim(&self, model_id: ModelId, anim_id: AnimationId) -> bool {
if self.apply_any_animation { return true; }
let model_num_objs = self.models[model_id].objects.len();
let anim_num_objs = self.animations[anim_id].objects_curves.len();
model_num_objs == anim_num_objs
}
}


Expand Down
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,18 @@ fn main2() -> Result<()> {
(about: "View models")
(alias: "v")
(@arg INPUT: +required +multiple "Nitro files")
(@arg apply_any_animation: --("apply-any-animation")
"Disable the heuristic that animation only apply to models with \
the same number of objects. Apply all animations to every model.")
)
(@subcommand convert =>
(about: "Convert models to COLLADA")
(alias: "c")
(@arg INPUT: +required +multiple "Nitro file")
(@arg OUTPUT: -o --output +required +takes_value "Output directory")
(@arg apply_any_animation: --("apply-any-animation")
"Disable the heuristic that animation only apply to models with \
the same number of objects. Apply all animations to every model.")
(@arg more_textures: --("more-textures") +hidden
"Try to extract more textures; only textures that are needed for a \
model are extracted by default (EXPERIMENTAL)")
Expand Down
12 changes: 10 additions & 2 deletions src/viewer/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,19 @@ impl GLPrimitives {

let objects: Vec<Matrix4<f64>> =
if let Some(ref anim_state) = view_state.anim_state {
// Get the object matrices by sampling the curves in the
// animation, padding with the identity is there aren't enough.
let anim = &db.animations[anim_state.anim_id];
anim.objects_curves.iter()
.map(|trs_curves| trs_curves.sample_at(anim_state.cur_frame))
(0..model.objects.len())
.map(|i| {
use cgmath::One;
anim.objects_curves.get(i)
.map(|trs| trs.sample_at(anim_state.cur_frame))
.unwrap_or_else(|| Matrix4::one())
})
.collect()
} else {
// Just read them from the model file.
model.objects.iter()
.map(|o| o.xform)
.collect()
Expand Down
10 changes: 6 additions & 4 deletions src/viewer/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,19 @@ impl ViewState {

pub fn advance_anim(&mut self, db: &Database, dir: Dir) {
let num_animations = db.animations.len();
let num_objects = db.models[self.model_id].objects.len();
let model_id = self.model_id;

// Represent anim_id as anim_id+1, freeing up 0 to represent "no animation".
let mut id = self.anim_state.as_ref()
.map(|anim_state| anim_state.anim_id + 1)
.unwrap_or(0);

// An animations can be applied to a model only if they have the same
// number of objects.
// Check whether we can apply the animation to this model.
let is_good = |id: usize| {
id == 0 || db.animations[id - 1].objects_curves.len() == num_objects
// No animation -- can always apply.
if id == 0 { return true; }

db.can_apply_anim(model_id, id - 1)
};

loop {
Expand Down

0 comments on commit 7c39f76

Please sign in to comment.