diff --git a/src/connection.rs b/src/connection.rs index f5d23c1..5375f86 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -7,7 +7,7 @@ //! out for ourselves. This modules contains the heuristics for that. use clap::ArgMatches; -use db::{Database, AnimationId, TextureId, PaletteId, ModelId, PatternId}; +use db::{Database, AnimationId, TextureId, PaletteId, ModelId, PatternId, MatAnimId}; use errors::Result; /// A Connection records interrelationships between Nitro resources, namely how @@ -25,6 +25,8 @@ pub struct ModelConnection { /// List of patterns that can be applied to the model (and how to apply /// them). pub patterns: Vec, + /// List of material animations that can be applied to the model. + pub mat_anims: Vec, } /// Result of resolving which texture/palette a material should use. @@ -118,7 +120,8 @@ impl Connection { let animations = find_applicable_animations(db, model_id, options); let patterns = find_applicable_patterns(db, model_id); - ModelConnection { materials, animations, patterns } + let mat_anims = find_applicable_mat_anims(db, model_id); + ModelConnection { materials, animations, patterns, mat_anims } }).collect(); if missing_textures { @@ -285,3 +288,20 @@ fn find_applicable_patterns(db: &Database, model_id: ModelId) -> Vec Vec { + let model = &db.models[model_id]; + db.mat_anims.iter().enumerate().filter_map(|(mat_anim_id, mat_anim)| { + // Check if all the tracks target valid materials + let valid = mat_anim.tracks.iter().all(|track| { + model.materials.iter().any(|mat| mat.name == track.name) + }); + if !valid { None } else { Some(MatAnimConnection { mat_anim_id }) } + }).collect() +} diff --git a/src/convert/collada/mod.rs b/src/convert/collada/mod.rs index fae42d8..86a6e3c 100644 --- a/src/convert/collada/mod.rs +++ b/src/convert/collada/mod.rs @@ -6,7 +6,7 @@ use cgmath::{Matrix4, One}; use convert::image_namer::ImageNamer; use db::{Database, ModelId}; use skeleton::{Skeleton, Transform, SMatrix}; -use primitives::{self, Primitives}; +use primitives::{self, Primitives, DynamicState}; use nitro::Model; use petgraph::{Direction}; use petgraph::graph::NodeIndex; @@ -42,7 +42,17 @@ pub fn write( let objects = &model.objects.iter() .map(|o| make_invertible(&o.matrix)) .collect::>(); - let prims = &Primitives::build(model, primitives::PolyType::TrisAndQuads, objects); + let uv_mats = &model.materials.iter() + .map(|mat| { + if mat.params.texcoord_transform_mode() == 1 { + mat.texture_mat + } else { + Matrix4::one() + } + }) + .collect::>(); + let state = DynamicState { objects, uv_mats }; + let prims = &Primitives::build(model, primitives::PolyType::TrisAndQuads, state); let skel = &Skeleton::build(model, objects); let ctx = Ctx { model_id, model, db, conn, image_namer, objects, prims, skel }; diff --git a/src/convert/gltf/mod.rs b/src/convert/gltf/mod.rs index 3ba54b2..7b75a62 100644 --- a/src/convert/gltf/mod.rs +++ b/src/convert/gltf/mod.rs @@ -6,10 +6,10 @@ mod primitive; use nitro::Model; use db::{Database, ModelId}; use connection::Connection; -use primitives::{Primitives, PolyType}; +use primitives::{Primitives, PolyType, DynamicState}; use skeleton::{Skeleton, Transform, SMatrix}; use super::image_namer::ImageNamer; -use cgmath::Matrix4; +use cgmath::{Matrix4, One}; use json::JsonValue; use self::gltf::{GlTF, Buffer, ByteVec, VecExt}; use self::object_trs::ObjectTRSes; @@ -45,7 +45,17 @@ pub fn to_gltf( let objects = rest_trses.objects.iter() .map(Matrix4::from) .collect::>(); - let prims = Primitives::build(model, PolyType::TrisAndQuads, &objects); + let uv_mats = model.materials.iter() + .map(|mat| { + if mat.params.texcoord_transform_mode() == 1 { + mat.texture_mat + } else { + Matrix4::one() + } + }) + .collect::>>(); + let state = DynamicState { objects: &objects, uv_mats: &uv_mats }; + let prims = Primitives::build(model, PolyType::TrisAndQuads, state); let prims = &encode_ngons(prims); let skel = &Skeleton::build(model, &objects); diff --git a/src/db.rs b/src/db.rs index 4379e66..ca057ba 100644 --- a/src/db.rs +++ b/src/db.rs @@ -15,7 +15,7 @@ pub type TextureId = usize; pub type PaletteId = usize; pub type AnimationId = usize; pub type PatternId = usize; -//pub type MatAnimId = usize; +pub type MatAnimId = usize; #[derive(Default)] pub struct Database { diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 5bd4e27..9749197 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -31,6 +31,15 @@ pub enum PolyType { TrisAndQuads, } +/// Dynamic state for a model (ie. stuff that changes during an animation, as +/// opposed to static state in the Model object). +pub struct DynamicState<'a> { + /// Object matrices to use. + pub objects: &'a [Matrix4], + /// UV-transform matrices to use for each material. + pub uv_mats: &'a [Matrix4], +} + /// Info about the result of a draw call, ie. the result of drawing a mesh (a set /// of GPU commands) while in a particular GPU state (matrix stack, bound material, /// etc.). @@ -78,8 +87,8 @@ impl Default for Vertex { implement_vertex!(Vertex, position, texcoord, color, normal); impl Primitives { - pub fn build(model: &Model, poly_type: PolyType, objects: &[Matrix4]) -> Primitives { - let mut b = Builder::new(model, poly_type, objects); + pub fn build(model: &Model, poly_type: PolyType, state: DynamicState) -> Primitives { + let mut b = Builder::new(model, poly_type, state); use nitro::render_cmds::Op; for op in &model.render_ops { match *op { @@ -125,7 +134,7 @@ impl GpuState { struct Builder<'a, 'b> { model: &'a Model, - objects: &'b [Matrix4], + state: DynamicState<'b>, poly_type: PolyType, gpu: GpuState, @@ -140,14 +149,13 @@ struct Builder<'a, 'b> { cur_draw_call: DrawCall, next_vertex: Vertex, - } impl<'a, 'b> Builder<'a, 'b> { - fn new(model: &'a Model, poly_type: PolyType, objects: &'b [Matrix4]) -> Builder<'a, 'b> { + fn new(model: &'a Model, poly_type: PolyType, state: DynamicState<'b>) -> Builder<'a, 'b> { Builder { model, - objects, + state, poly_type, gpu: GpuState::new(), vertices: vec![], @@ -174,6 +182,9 @@ impl<'a, 'b> Builder<'a, 'b> { let vert_len = self.vertices.len() as u16; let ind_len = self.indices.len(); + // Bind material + self.gpu.texture_matrix = self.state.uv_mats[mat_id as usize]; + self.cur_draw_call = DrawCall { vertex_range: vert_len..vert_len, index_range: ind_len..ind_len, @@ -215,7 +226,7 @@ impl<'a, 'b> Builder<'a, 'b> { } fn mul_by_object(&mut self, object_id: u8) { - self.gpu.mul_matrix(&self.objects[object_id as usize]); + self.gpu.mul_matrix(&self.state.objects[object_id as usize]); } fn blend(&mut self, terms: &[SkinTerm]) { @@ -369,12 +380,14 @@ fn run_gpu_cmds(b: &mut Builder, commands: &[u8]) { GpuCmd::TexCoord { texcoord } => { b.cur_draw_call.used_texcoords = true; + // Apply texture matrix + let texcoord = b.gpu.texture_matrix * vec4(texcoord.x, texcoord.y, 0.0, 1.0); + // Transform into OpenGL-type [0,1]x[0,1] texture space. let texcoord = Point2::new( texcoord.x / b.cur_texture_dim.0 as f64, 1.0 - texcoord.y / b.cur_texture_dim.1 as f64, // y-down to y-up ); - let texcoord = b.gpu.texture_matrix * vec4(texcoord.x, texcoord.y, 0.0, 0.0); b.next_vertex.texcoord = [texcoord.x as f32, texcoord.y as f32]; } GpuCmd::Color { color } => { diff --git a/src/viewer/viewer.rs b/src/viewer/viewer.rs index b1004ab..67f6006 100644 --- a/src/viewer/viewer.rs +++ b/src/viewer/viewer.rs @@ -1,12 +1,12 @@ use super::model_viewer::{ModelViewer, MaterialTextureBinding}; -use db::{Database, ModelId, AnimationId, PatternId}; +use db::{Database, ModelId, AnimationId, PatternId, MatAnimId}; use connection::Connection; use glium::Display; use glium::glutin::ElementState; use glium::{Frame, Surface}; use glium::glutin::VirtualKeyCode; -use nitro::{Model, Animation, Pattern}; -use primitives::{Primitives, PolyType}; +use nitro::{Model, Animation, Pattern, MaterialAnimation}; +use primitives::{Primitives, PolyType, DynamicState}; use cgmath::{Matrix4, InnerSpace, One, Vector3, vec3, vec2}; use super::fps::FpsCounter; use super::{FRAMERATE, BG_COLOR}; @@ -18,14 +18,21 @@ pub struct Viewer { /// ID of the viewed model. model_id: ModelId, + /// The connection has a list of all the animations that can be applied to /// this model. This is the index of the current animation in that list (or /// None if no animation is selected). anim_idx: Option, /// Current animation frame. anim_frame: u16, + pat_idx: Option, pat_anim_frame: u16, + + mat_anim_idx: Option, + mat_anim_frame: u16, + // TODO: refactor this + /// When single-stepping is enabled, the user advanced the animation /// manually. When disabled, the animation advanced with time (ie. plays /// normally). @@ -56,6 +63,7 @@ pub static CONTROL_HELP: &'static str = " OP Prev/Next Animation\n", " [] Single-step Animation\n", " KL Prev/Next Pattern Animation\n", + " ;' Prev/Next Material Animation\n", " Space Print Info\n", ); @@ -75,6 +83,8 @@ impl Viewer { anim_frame: 0, pat_idx: None, pat_anim_frame: 0, + mat_anim_idx: None, + mat_anim_frame: 0, single_stepping: false, move_vector: vec3(0.0, 0.0, 0.0), speed_idx: DEFAULT_SPEED_IDX, @@ -108,6 +118,7 @@ impl Viewer { self.next_frame(); } self.next_pattern_frame(display); + self.next_mat_anim_frame(); self.time_acc -= FRAMERATE; } } @@ -187,6 +198,18 @@ impl Viewer { self.change_pat_idx(display, new_pat_idx); } + // Next/prev material animation + Key::Apostrophe => { + let num = self.conn.models[self.model_id].mat_anims.len(); + let new_idx = maybe_next(self.mat_anim_idx, 0..num); + self.change_mat_anim_idx(new_idx); + } + Key::Semicolon => { + let num = self.conn.models[self.model_id].mat_anims.len(); + let new_idx = maybe_prev(self.mat_anim_idx, 0..num); + self.change_mat_anim_idx(new_idx); + } + // Speed up/down Key::LShift => { if self.speed_idx != SPEEDS.len() - 1 { @@ -252,6 +275,18 @@ impl Viewer { } else { write!(s, "No Pattern === ").unwrap() } + if let Some(mat_anim_id) = self.mat_anim_id() { + let mat_anim = self.cur_mat_anim().unwrap(); + write!(s, "{anim_name}[{anim_id}/{num_anims}] ({cur_frame}/{num_frames}) === ", + anim_name = mat_anim.name, + anim_id = mat_anim_id, + num_anims = self.db.mat_anims.len(), + cur_frame = self.mat_anim_frame, + num_frames = mat_anim.num_frames, + ).unwrap() + } else { + write!(s, "No Material Animation === ").unwrap() + } write!(s, "{:5.2}fps", self.fps_counter.fps()).unwrap(); } @@ -287,6 +322,18 @@ impl Viewer { println!("No Pattern Animation Playing") } + if let Some(mat_anim_id) = self.mat_anim_id() { + let mat_anim = self.cur_mat_anim().unwrap(); + println!("Material Animation: {:?} [{}/{}]", + mat_anim.name, + mat_anim_id, + self.db.mat_anims.len(), + ); + println!("Found in file: {}", self.db.file_paths[self.db.mat_anims_found_in[mat_anim_id]].display()); + } else { + println!("No Material Animation Playing") + } + println!(); } @@ -299,9 +346,21 @@ impl Viewer { self.stop_animations(); self.model_id = model_id; + let objects = self.cur_model().objects.iter() .map(|ob| ob.matrix) .collect::>>(); + let uv_mats = self.cur_model().materials.iter() + .map(|mat| { + if mat.params.texcoord_transform_mode() == 1 { + mat.texture_mat + } else { + Matrix4::one() + } + }) + .collect::>>(); + let state = DynamicState { objects: &objects, uv_mats: &uv_mats }; + let material_map = self.conn.models[self.model_id].materials.iter().map(|mat_conn| { match mat_conn.image_id() { Ok(Some(image_id)) => MaterialTextureBinding::ImageId(image_id), @@ -309,7 +368,7 @@ impl Viewer { Err(_) => MaterialTextureBinding::Missing, } }).collect(); - let prims = Primitives::build(self.cur_model(), PolyType::Tris, &objects); + let prims = Primitives::build(self.cur_model(), PolyType::Tris, state); self.model_viewer.change_model(display, &self.db, prims, material_map); } @@ -335,7 +394,17 @@ impl Viewer { .collect::>>() } }; - let prims = Primitives::build(self.cur_model(), PolyType::Tris, &objects); + let uv_mats = self.cur_model().materials.iter() + .map(|mat| { + if mat.params.texcoord_transform_mode() == 1 { + mat.texture_mat + } else { + Matrix4::one() + } + }) + .collect::>>(); + let state = DynamicState { objects: &objects, uv_mats: &uv_mats }; + let prims = Primitives::build(self.cur_model(), PolyType::Tris, state); self.model_viewer.update_vertices(&prims.vertices); } @@ -436,6 +505,16 @@ impl Viewer { self.update_materials(display); } + fn change_mat_anim_idx(&mut self, mat_anim_idx: Option) { + if self.mat_anim_idx == mat_anim_idx { + return; + } + + self.mat_anim_idx = mat_anim_idx; + self.mat_anim_frame = 0; + self.update_vertices(); + } + pub fn next_pattern_frame(&mut self, display: &Display) { let num_frames = self.cur_pattern().map(|pat| pat.num_frames); if let Some(num_frames) = num_frames { @@ -445,6 +524,15 @@ impl Viewer { } } + pub fn next_mat_anim_frame(&mut self) { + let num_frames = self.cur_mat_anim().map(|anim| anim.num_frames); + if let Some(num_frames) = num_frames { + self.mat_anim_frame += 1; + self.mat_anim_frame %= num_frames; + self.update_vertices(); + } + } + pub fn set_aspect_ratio(&mut self, aspect_ratio: f64) { self.model_viewer.aspect_ratio = aspect_ratio as f32; } @@ -461,6 +549,10 @@ impl Viewer { Some(&self.db.patterns[self.pattern_id()?]) } + fn cur_mat_anim(&self) -> Option<&MaterialAnimation> { + Some(&self.db.mat_anims[self.mat_anim_id()?]) + } + fn animation_id(&self) -> Option { let idx = self.anim_idx?; Some(self.conn.models[self.model_id].animations[idx]) @@ -471,6 +563,11 @@ impl Viewer { Some(self.conn.models[self.model_id].patterns[idx].pattern_id) } + fn mat_anim_id(&self) -> Option { + let idx = self.mat_anim_idx?; + Some(self.conn.models[self.model_id].mat_anims[idx].mat_anim_id) + } + pub fn speed(&self) -> f32 { SPEEDS[self.speed_idx] }