├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── scripts ├── id_rsa.enc └── travis-doc-upload.cfg ├── src ├── document.rs ├── lib.rs ├── obj.rs └── utils.rs └── test_assets ├── test.blend ├── test.dae ├── test_cube_triangles_geometry.dae └── test_noskeleton.dae /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | .DS_Store 11 | 12 | # Generated by Cargo 13 | /target/ 14 | Cargo.lock 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | script: 3 | - cargo build -v 4 | - cargo doc -v 5 | - cargo test -v 6 | after_success: 7 | - curl http://docs.piston.rs/travis-doc-upload.sh | sh 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "collada" 4 | version = "0.15.0" 5 | edition = "2018" 6 | authors = ["Steve Jahns "] 7 | keywords = ["collada", "3d", "format", "piston"] 8 | description = "A library for parsing COLLADA documents for mesh, skeletal and animation data" 9 | license = "MIT" 10 | readme = "README.md" 11 | repository = "https://github.com/PistonDevelopers/piston_collada" 12 | homepage = "https://github.com/PistonDevelopers/piston_collada" 13 | 14 | [lib] 15 | 16 | name = "collada" 17 | path = "src/lib.rs" 18 | 19 | [dependencies] 20 | 21 | log = "0.4" 22 | RustyXML = "0.3" 23 | vecmath = "1.0.0" 24 | quaternion = "1.0.0" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Steve Jahns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # piston_collada 2 | Rust library for parsing COLLADA files. 3 | 4 | [![Build Status](https://travis-ci.org/PistonDevelopers/piston_collada.png?branch=master)](https://travis-ci.org/PistonDevelopers/piston_collada) 5 | 6 | *Notice: This library is built around files exported from Blender 2.74* 7 | -------------------------------------------------------------------------------- /scripts/id_rsa.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/piston_collada/baadada54d05a9aec0970b78969a4c4bf221fd39/scripts/id_rsa.enc -------------------------------------------------------------------------------- /scripts/travis-doc-upload.cfg: -------------------------------------------------------------------------------- 1 | PROJECT_NAME=piston_collada 2 | DOCS_REPO=PistonDevelopers/docs.git 3 | SSH_KEY_TRAVIS_ID=0c52d3205f41 4 | -------------------------------------------------------------------------------- /src/document.rs: -------------------------------------------------------------------------------- 1 | use crate::obj::*; 2 | use crate::utils::*; 3 | use std::collections::HashMap; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use std::path::Path; 7 | use std::str::FromStr; 8 | use xml::Element; 9 | use xml::Xml::CharacterNode; 10 | 11 | use vecmath; 12 | use xml; 13 | 14 | use crate::{ 15 | Animation, BindData, BindDataSet, Joint, JointIndex, Skeleton, VertexWeight, 16 | ROOT_JOINT_PARENT_INDEX, 17 | }; 18 | 19 | enum GeometryBindingType { 20 | Polylist, 21 | Triangles, 22 | // TODO types below are not implemented 23 | // Lines, 24 | // Linestrips, 25 | // Polygons, 26 | // Trifans, 27 | // Tristrips, 28 | } 29 | 30 | impl GeometryBindingType { 31 | fn name(&self) -> &'static str { 32 | match *self { 33 | GeometryBindingType::Polylist => "polylist", 34 | GeometryBindingType::Triangles => "triangles", 35 | } 36 | } 37 | } 38 | 39 | /// 40 | /// Commonly used shading techniques 41 | /// Holds a description of the textures, samplers, shaders, parameters, and 42 | /// passes necessary for rendering this effect using one method. 43 | /// 44 | #[derive(Clone, Debug, PartialEq)] 45 | pub struct PhongEffect { 46 | pub emission: [f32; 4], 47 | pub ambient: [f32; 4], 48 | pub diffuse: [f32; 4], 49 | pub specular: [f32; 4], 50 | pub shininess: f32, 51 | pub index_of_refraction: f32, 52 | } 53 | /// 54 | /// Can be a plain color or point to a texture in the images library 55 | /// 56 | #[derive(Clone, Debug, PartialEq)] 57 | pub enum LambertDiffuse { 58 | Color([f32; 4]), 59 | Texture(String), 60 | } 61 | #[derive(Clone, Debug, PartialEq)] 62 | pub struct LambertEffect { 63 | pub emission: [f32; 4], 64 | pub diffuse: LambertDiffuse, 65 | pub index_of_refraction: f32, 66 | } 67 | 68 | #[derive(Clone, Debug, PartialEq)] 69 | pub enum MaterialEffect { 70 | Phong(PhongEffect), 71 | Lambert(LambertEffect), 72 | } 73 | 74 | // TODO: Add more effect types and then unify them under a technique enum. 75 | // Lambert 76 | // Blinn 77 | // Constant 78 | // 79 | 80 | pub struct ColladaDocument { 81 | pub root_element: xml::Element, // TODO figure out how to cache skeletal and skinning data, as we need to 82 | // access them multiple times 83 | } 84 | 85 | impl ColladaDocument { 86 | /// 87 | /// Construct a ColladaDocument for the XML document at the given path 88 | /// 89 | pub fn from_path(path: &Path) -> Result { 90 | let file_result = File::open(path); 91 | 92 | let mut file = match file_result { 93 | Ok(file) => file, 94 | Err(_) => return Err("Failed to open COLLADA file at path."), 95 | }; 96 | 97 | let mut xml_string = String::new(); 98 | match file.read_to_string(&mut xml_string) { 99 | Ok(_) => {} 100 | Err(_) => return Err("Failed to read COLLADA file."), 101 | }; 102 | 103 | ColladaDocument::from_str(&xml_string) 104 | } 105 | 106 | /// 107 | /// Construct a ColladaDocument from an XML string 108 | /// 109 | pub fn from_str(xml_string: &str) -> Result { 110 | match xml_string.parse() { 111 | Ok(root_element) => Ok(ColladaDocument { root_element }), 112 | Err(_) => Err("Error while parsing COLLADA document."), 113 | } 114 | } 115 | 116 | fn get_color(el: &Element) -> Option<[f32; 4]> { 117 | let v: Vec = parse_string_to_vector(el.content_str().as_str()); 118 | if v.len() == 4 { 119 | Some([v[0], v[1], v[2], v[3]]) 120 | } else { 121 | None 122 | } 123 | } 124 | 125 | fn get_lambert(&self, lamb: &Element, ns: Option<&str>) -> LambertEffect { 126 | let emission_color = lamb 127 | .get_child("emission", ns) 128 | .expect("lambert is missing emission") 129 | .get_child("color", ns) 130 | .expect("emission is missing color"); 131 | let emission = 132 | ColladaDocument::get_color(emission_color).expect("could not get emission color."); 133 | let diffuse_element = lamb 134 | .get_child("diffuse", ns) 135 | .expect("lambert is missing diffuse"); 136 | 137 | let diffuse_texture = diffuse_element.get_child("texture", ns); 138 | 139 | let diffuse; 140 | if let Some(texture) = diffuse_texture { 141 | diffuse = LambertDiffuse::Texture( 142 | texture 143 | .get_attribute("texture", None) 144 | .expect("No texture attribute on texture") 145 | .to_string(), 146 | ); 147 | } else { 148 | let diffuse_element_color = diffuse_element 149 | .get_child("color", ns) 150 | .expect("diffuse is missing color"); 151 | let diffuse_color = ColladaDocument::get_color(diffuse_element_color) 152 | .expect("could not get diffuse color."); 153 | diffuse = LambertDiffuse::Color(diffuse_color); 154 | } 155 | 156 | let index_of_refraction: f32 = lamb 157 | .get_child("index_of_refraction", ns) 158 | .expect("lambert is missing index_of_refraction") 159 | .get_child("float", ns) 160 | .expect("index_of_refraction is missing float") 161 | .content_str() 162 | .as_str() 163 | .parse() 164 | .ok() 165 | .expect("could not parse index_of_refraction"); 166 | 167 | LambertEffect { 168 | diffuse, 169 | emission, 170 | index_of_refraction, 171 | } 172 | } 173 | 174 | fn get_phong(&self, phong: &Element, ns: Option<&str>) -> PhongEffect { 175 | let emission_color = phong 176 | .get_child("emission", ns) 177 | .expect("phong is missing emission") 178 | .get_child("color", ns) 179 | .expect("emission is missing color"); 180 | let emission = 181 | ColladaDocument::get_color(emission_color).expect("could not get emission color."); 182 | let ambient_color = phong 183 | .get_child("ambient", ns) 184 | .expect("phong is missing ambient") 185 | .get_child("color", ns) 186 | .expect("ambient is missing color"); 187 | let ambient = 188 | ColladaDocument::get_color(ambient_color).expect("could not get ambient color."); 189 | let diffuse = phong 190 | .get_child("diffuse", ns) 191 | .expect("phong is missing diffuse"); 192 | let diffuse_color = diffuse 193 | .get_child("color", ns) 194 | .expect("diffuse is missing color"); 195 | let diffuse = 196 | ColladaDocument::get_color(diffuse_color).expect("could not get diffuse color."); 197 | let specular_color = phong 198 | .get_child("specular", ns) 199 | .expect("phong is missing specular") 200 | .get_child("color", ns) 201 | .expect("specular is missing color"); 202 | let specular = 203 | ColladaDocument::get_color(specular_color).expect("could not get specular color."); 204 | let shininess: f32 = phong 205 | .get_child("shininess", ns) 206 | .expect("phong is missing shininess") 207 | .get_child("float", ns) 208 | .expect("shininess is missing float") 209 | .content_str() 210 | .as_str() 211 | .parse() 212 | .ok() 213 | .expect("could not parse shininess"); 214 | let index_of_refraction: f32 = phong 215 | .get_child("index_of_refraction", ns) 216 | .expect("phong is missing index_of_refraction") 217 | .get_child("float", ns) 218 | .expect("index_of_refraction is missing float") 219 | .content_str() 220 | .as_str() 221 | .parse() 222 | .ok() 223 | .expect("could not parse index_of_refraction"); 224 | 225 | PhongEffect { 226 | ambient, 227 | diffuse, 228 | emission, 229 | index_of_refraction, 230 | shininess, 231 | specular, 232 | } 233 | } 234 | 235 | /// 236 | /// Returns the library of effects. 237 | /// Current only supports Phong shading. 238 | /// 239 | pub fn get_effect_library(&self) -> HashMap { 240 | let ns = self.get_ns(); 241 | let lib_effs = self 242 | .root_element 243 | .get_child("library_effects", ns) 244 | .expect("Could not get library_effects from the document."); 245 | lib_effs 246 | .get_children("effect", ns) 247 | .flat_map(|el: &Element| -> Option<(String, MaterialEffect)> { 248 | let id = el 249 | .get_attribute("id", None) 250 | .unwrap_or_else(|| panic!("effect is missing its id. {:#?}", el)); 251 | let prof = el.get_child("profile_COMMON", ns)?; 252 | let tech = prof.get_child("technique", ns)?; 253 | let phong = tech.get_child("phong", ns); 254 | if let Some(p) = phong { 255 | let phong_effect = self.get_phong(p, ns); 256 | return Some((id.to_string(), MaterialEffect::Phong(phong_effect))); 257 | }; 258 | let lambert = tech.get_child("lambert", ns); 259 | if let Some(lam) = lambert { 260 | let lambert_effect = self.get_lambert(lam, ns); 261 | return Some((id.to_string(), MaterialEffect::Lambert(lambert_effect))); 262 | }; 263 | 264 | None 265 | }) 266 | .collect() 267 | } 268 | 269 | pub fn get_material_to_effect(&self) -> HashMap { 270 | let ns = self.get_ns(); 271 | let lib_mats = self 272 | .root_element 273 | .get_child("library_materials", ns) 274 | .expect("Could not get library_materials from the document"); 275 | lib_mats 276 | .get_children("material", ns) 277 | .flat_map(|el| { 278 | let id = el 279 | .get_attribute("id", None) 280 | .unwrap_or_else(|| panic!("material is missing its id. {:#?}", el)); 281 | let mut url: String = el 282 | .get_child("instance_effect", ns) 283 | .expect("could not get material instance_effect") 284 | .get_attribute("url", None) 285 | .expect("could not get material instance_effect url attribute") 286 | .to_string(); 287 | if url.remove(0) == '#' { 288 | Some((id.to_string(), url)) 289 | } else { 290 | None 291 | } 292 | }) 293 | .collect() 294 | } 295 | 296 | /// 297 | /// Returns a hashmap of 298 | /// 299 | pub fn get_images(&self) -> HashMap { 300 | let ns = self.get_ns(); 301 | let lib_images = self 302 | .root_element 303 | .get_child("library_images", ns) 304 | .expect("Could not get library_images from the document"); 305 | lib_images 306 | .get_children("image", ns) 307 | .flat_map(|el| { 308 | let id = el 309 | .get_attribute("id", None) 310 | .expect(&format!("image is missing its id. {:#?}", el)) 311 | .to_string(); 312 | let file_name = el 313 | .get_child("init_from", ns) 314 | .expect("Could not get image from the element") 315 | .content_str(); 316 | Some((id, file_name)) 317 | }) 318 | .collect() 319 | } 320 | 321 | /// 322 | /// Return a vector of all Animations in the Collada document 323 | /// 324 | pub fn get_animations(&self) -> Option> { 325 | match self 326 | .root_element 327 | .get_child("library_animations", self.get_ns()) 328 | { 329 | Some(library_animations) => { 330 | let animations = library_animations.get_children("animation", self.get_ns()); 331 | Some(animations.filter_map(|a| self.get_animation(a)).collect()) 332 | } 333 | None => None, 334 | } 335 | } 336 | 337 | /// 338 | /// Construct an Animation struct for the given element if possible 339 | /// 340 | fn get_animation(&self, animation_element: &Element) -> Option { 341 | let channel_element = animation_element 342 | .get_child("channel", self.get_ns()) 343 | .expect("Missing channel element in animation element"); 344 | 345 | let target = channel_element 346 | .get_attribute("target", None) 347 | .expect("Missing target attribute in animation channel element"); 348 | 349 | let sampler_element = animation_element 350 | .get_child("sampler", self.get_ns()) 351 | .expect("Missing sampler element in animation element"); 352 | 353 | // Note: Assuming INPUT for animation is 'time' 354 | let time_input = self 355 | .get_input(sampler_element, "INPUT") 356 | .expect("Missing input element for animation INPUT (sample time)"); 357 | 358 | let sample_times = self 359 | .get_array_for_input(animation_element, time_input) 360 | .expect("Missing / invalid source for animation INPUT"); 361 | 362 | // Note: Assuming OUTPUT for animation is a pose matrix 363 | let pose_input = self 364 | .get_input(sampler_element, "OUTPUT") 365 | .expect("Missing input element for animation OUTPUT (pose transform)"); 366 | 367 | let sample_poses_flat = self 368 | .get_array_for_input(animation_element, pose_input) 369 | .expect("Missing / invalid source for animation OUTPUT"); 370 | 371 | // Convert flat array of floats into array of matrices 372 | let sample_poses = to_matrix_array(sample_poses_flat); 373 | 374 | Some(Animation { 375 | target: target.to_string(), 376 | sample_times, 377 | sample_poses, 378 | }) 379 | } 380 | 381 | /// 382 | /// Populate and return an ObjSet for the meshes in the Collada document 383 | /// 384 | pub fn get_obj_set(&self) -> Option { 385 | let library_geometries = (self 386 | .root_element 387 | .get_child("library_geometries", self.get_ns()))?; 388 | let geometries = library_geometries.get_children("geometry", self.get_ns()); 389 | let objects = geometries.filter_map(|g| self.get_object(g)).collect(); 390 | 391 | Some(ObjSet { 392 | material_library: None, 393 | objects, 394 | }) 395 | } 396 | 397 | /// 398 | /// Populate and return a BindDataSet from the Collada document 399 | /// 400 | pub fn get_bind_data_set(&self) -> Option { 401 | let library_controllers = (self 402 | .root_element 403 | .get_child("library_controllers", self.get_ns()))?; 404 | let controllers = library_controllers.get_children("controller", self.get_ns()); 405 | let bind_data = controllers.filter_map(|c| self.get_bind_data(c)).collect(); 406 | Some(BindDataSet { bind_data }) 407 | } 408 | 409 | /// 410 | /// 411 | /// 412 | pub fn get_skeletons(&self) -> Option> { 413 | let library_visual_scenes = (self 414 | .root_element 415 | .get_child("library_visual_scenes", self.get_ns()))?; 416 | let visual_scene = (library_visual_scenes.get_child("visual_scene", self.get_ns()))?; 417 | 418 | let bind_data_set = (self.get_bind_data_set())?; 419 | 420 | let skeleton_ids: Vec<&str> = pre_order_iter(visual_scene) 421 | .filter(|e| e.name == "skeleton") 422 | .filter_map(|s| { 423 | if let CharacterNode(ref id) = s.children[0] { 424 | Some(&id[..]) 425 | } else { 426 | None 427 | } 428 | }) 429 | .map(|id| id.trim_start_matches('#')) 430 | .collect(); 431 | 432 | if skeleton_ids.is_empty() { 433 | return None; 434 | } 435 | 436 | let skeletons = pre_order_iter(visual_scene) 437 | .filter(|e| e.name == "node") 438 | .filter(|e| has_attribute_with_value(e, "id", skeleton_ids[0])) 439 | .filter_map(|e| self.get_skeleton(e, &bind_data_set.bind_data[0])) 440 | .collect(); 441 | 442 | Some(skeletons) 443 | } 444 | 445 | fn get_skeleton(&self, root_element: &Element, bind_data: &BindData) -> Option { 446 | let mut parent_index_stack: Vec = vec![ROOT_JOINT_PARENT_INDEX]; 447 | let mut joints = Vec::new(); 448 | let mut bind_poses = Vec::new(); 449 | 450 | for (joint_index, (joint_element, depth)) in pre_order_with_depth_iter(root_element) 451 | .filter(|&(e, _)| e.name == "node" && has_attribute_with_value(e, "type", "JOINT")) 452 | .enumerate() 453 | { 454 | // If our depth decreases after visiting a leaf node, pop indices off the stack 455 | // until it matches our depth 456 | while depth < parent_index_stack.len() - 1 { 457 | parent_index_stack.pop(); 458 | } 459 | 460 | let joint_name = joint_element.get_attribute("id", None).unwrap().to_string(); 461 | 462 | let mut joint_names_with_bind_pose = bind_data 463 | .joint_names 464 | .iter() 465 | .zip(bind_data.inverse_bind_poses.iter()); 466 | let inverse_bind_pose = 467 | match joint_names_with_bind_pose.find(|&(name, _)| *name == joint_name) { 468 | Some((_, pose)) => *pose, 469 | _ => vecmath::mat4_id(), 470 | }; 471 | 472 | joints.push(Joint { 473 | inverse_bind_pose, 474 | name: joint_name, 475 | parent_index: *parent_index_stack.last().unwrap(), 476 | }); 477 | 478 | let pose_matrix_element = (joint_element.get_child("matrix", self.get_ns()))?; 479 | let pose_matrix_array = (get_array_content(pose_matrix_element))?; 480 | let mut pose_matrix = vecmath::mat4_id(); 481 | for (&array_value, matrix_value) in pose_matrix_array 482 | .iter() 483 | .zip(pose_matrix.iter_mut().flat_map(|n| n.iter_mut())) 484 | { 485 | *matrix_value = array_value; 486 | } 487 | 488 | bind_poses.push(pose_matrix); 489 | 490 | parent_index_stack.push(joint_index as JointIndex); 491 | } 492 | 493 | Some(Skeleton { joints, bind_poses }) 494 | } 495 | 496 | fn get_bind_data(&self, controller_element: &xml::Element) -> Option { 497 | let skeleton_name = controller_element.get_attribute("name", None); 498 | let skin_element = controller_element.get_child("skin", self.get_ns())?; 499 | let object_name = skin_element 500 | .get_attribute("source", None)? 501 | .trim_start_matches('#'); 502 | 503 | let vertex_weights_element = (skin_element.get_child("vertex_weights", self.get_ns()))?; 504 | let vertex_weights = (self.get_vertex_weights(vertex_weights_element))?; 505 | 506 | let joints_element = (skin_element.get_child("joints", self.get_ns()))?; 507 | 508 | let joint_input = (self.get_input(joints_element, "JOINT"))?; 509 | let joint_names = (self.get_array_for_input(skin_element, joint_input))?; 510 | 511 | let weights_input = (self.get_input(vertex_weights_element, "WEIGHT"))?; 512 | let weights = (self.get_array_for_input(skin_element, weights_input))?; 513 | 514 | let inv_bind_matrix_input = (self.get_input(joints_element, "INV_BIND_MATRIX"))?; 515 | 516 | let inverse_bind_poses = 517 | to_matrix_array((self.get_array_for_input(skin_element, inv_bind_matrix_input))?); 518 | 519 | Some(BindData { 520 | object_name: object_name.to_string(), 521 | skeleton_name: skeleton_name.map(|s| s.to_string()), 522 | joint_names, 523 | inverse_bind_poses, 524 | weights, 525 | vertex_weights, 526 | }) 527 | } 528 | 529 | fn get_vertex_weights( 530 | &self, 531 | vertex_weights_element: &xml::Element, 532 | ) -> Option> { 533 | let joint_index_offset = (self.get_input_offset(vertex_weights_element, "JOINT"))?; 534 | let weight_index_offset = (self.get_input_offset(vertex_weights_element, "WEIGHT"))?; 535 | 536 | let vcount_element = (vertex_weights_element.get_child("vcount", self.get_ns()))?; 537 | let weights_per_vertex: Vec = (get_array_content(vcount_element))?; 538 | let input_count = vertex_weights_element 539 | .get_children("input", self.get_ns()) 540 | .count(); 541 | 542 | let v_element = (vertex_weights_element.get_child("v", self.get_ns()))?; 543 | let joint_weight_indices: Vec = (get_array_content(v_element))?; 544 | let mut joint_weight_iter = joint_weight_indices.chunks(input_count); 545 | 546 | let mut vertex_indices: Vec = Vec::new(); 547 | for (index, n) in weights_per_vertex.iter().enumerate() { 548 | for _ in 0..*n { 549 | vertex_indices.push(index); 550 | } 551 | } 552 | 553 | let vertex_weights = vertex_indices 554 | .iter() 555 | .filter_map(|vertex_index| { 556 | joint_weight_iter.next().map(|joint_weight| VertexWeight { 557 | vertex: *vertex_index, 558 | joint: joint_weight[joint_index_offset] as JointIndex, 559 | weight: joint_weight[weight_index_offset], 560 | }) 561 | }) 562 | .collect(); 563 | 564 | Some(vertex_weights) 565 | } 566 | 567 | fn get_object(&self, geometry_element: &xml::Element) -> Option { 568 | let id = (geometry_element.get_attribute("id", None))?; 569 | let name = (geometry_element.get_attribute("name", None))?; 570 | let mesh_element = (geometry_element.get_child("mesh", self.get_ns()))?; 571 | let mesh = (self.get_mesh_elements(mesh_element))?; 572 | //let binding_type = (self.get_geometry_binding_type(mesh_element))?; 573 | 574 | let mut first_primitive_element = None; 575 | 'find_primitive: for t in [ 576 | GeometryBindingType::Polylist, 577 | GeometryBindingType::Triangles, 578 | ] 579 | .iter() 580 | { 581 | first_primitive_element = mesh_element.get_child(t.name(), self.get_ns()); 582 | if first_primitive_element.is_some() { 583 | break 'find_primitive; 584 | } 585 | } 586 | let first_primitive_element = first_primitive_element?; 587 | 588 | let positions_input = (self.get_input(first_primitive_element, "VERTEX"))?; 589 | let positions_array = (self.get_array_for_input(mesh_element, positions_input))?; 590 | let positions: Vec<_> = positions_array 591 | .chunks(3) 592 | .map(|coords| Vertex { 593 | x: coords[0], 594 | y: coords[1], 595 | z: coords[2], 596 | }) 597 | .collect(); 598 | 599 | let normals = { 600 | match self.get_input(first_primitive_element, "NORMAL") { 601 | Some(normals_input) => { 602 | let normals_array = (self.get_array_for_input(mesh_element, normals_input))?; 603 | normals_array 604 | .chunks(3) 605 | .map(|coords| Normal { 606 | x: coords[0], 607 | y: coords[1], 608 | z: coords[2], 609 | }) 610 | .collect() 611 | } 612 | None => Vec::new(), 613 | } 614 | }; 615 | 616 | let texcoords = { 617 | match self.get_input(first_primitive_element, "TEXCOORD") { 618 | Some(texcoords_input) => { 619 | let texcoords_array = 620 | (self.get_array_for_input(mesh_element, texcoords_input))?; 621 | texcoords_array 622 | .chunks(2) 623 | .map(|coords| TVertex { 624 | x: coords[0], 625 | y: coords[1], 626 | }) 627 | .collect() 628 | } 629 | None => Vec::new(), 630 | } 631 | }; 632 | 633 | // TODO cache! also only if any skeleton 634 | 635 | let joint_weights = match self.get_skeletons() { 636 | Some(skeletons) => { 637 | let skeleton = &skeletons[0]; 638 | // TODO cache bind_data_set 639 | let bind_data_set = (self.get_bind_data_set())?; 640 | let bind_data_opt = bind_data_set 641 | .bind_data 642 | .iter() 643 | .find(|bind_data| bind_data.object_name == id); 644 | 645 | if let Some(bind_data) = bind_data_opt { 646 | // Build an array of joint weights for each vertex 647 | // Initialize joint weights array with no weights for any vertex 648 | let mut joint_weights = vec![ 649 | JointWeights { 650 | joints: [0; 4], 651 | weights: [0.0; 4] 652 | }; 653 | positions.len() 654 | ]; 655 | 656 | for vertex_weight in bind_data.vertex_weights.iter() { 657 | let joint_name = &bind_data.joint_names[vertex_weight.joint as usize]; 658 | let vertex_joint_weights: &mut JointWeights = 659 | &mut joint_weights[vertex_weight.vertex]; 660 | 661 | if let Some((next_index, _)) = vertex_joint_weights 662 | .weights 663 | .iter() 664 | .enumerate() 665 | .find(|&(_, weight)| *weight == 0.0) 666 | { 667 | if let Some((joint_index, _)) = skeleton 668 | .joints 669 | .iter() 670 | .enumerate() 671 | .find(|&(_, j)| &j.name == joint_name) 672 | { 673 | vertex_joint_weights.joints[next_index] = joint_index; 674 | vertex_joint_weights.weights[next_index] = 675 | bind_data.weights[vertex_weight.weight]; 676 | } else { 677 | error!("Couldn't find joint: {}", joint_name); 678 | } 679 | } else { 680 | error!("Too many joint influences for vertex"); 681 | } 682 | } 683 | joint_weights 684 | } else { 685 | Vec::new() 686 | } 687 | } 688 | None => Vec::new(), 689 | }; 690 | 691 | Some(Object { 692 | id: id.to_string(), 693 | name: name.to_string(), 694 | vertices: positions, 695 | tex_vertices: texcoords, 696 | normals, 697 | joint_weights, 698 | geometry: vec![Geometry { 699 | smooth_shading_group: 0, 700 | mesh, 701 | }], 702 | }) 703 | } 704 | 705 | fn get_ns(&self) -> Option<&str> { 706 | match self.root_element.ns { 707 | Some(ref ns) => Some(&ns[..]), 708 | None => None, 709 | } 710 | } 711 | 712 | fn get_input_offset(&self, parent_element: &xml::Element, semantic: &str) -> Option { 713 | let mut inputs = parent_element.get_children("input", self.get_ns()); 714 | let input: &Element = inputs.find(|i| { 715 | if let Some(s) = i.get_attribute("semantic", None) { 716 | s == semantic 717 | } else { 718 | false 719 | } 720 | })?; 721 | input 722 | .get_attribute("offset", None) 723 | .expect("input is missing offest") 724 | .parse::() 725 | .ok() 726 | } 727 | 728 | /// 729 | fn get_input<'a>(&'a self, parent: &'a Element, semantic: &str) -> Option<&'a Element> { 730 | let mut inputs = parent.get_children("input", self.get_ns()); 731 | match inputs.find(|i| { 732 | if let Some(s) = i.get_attribute("semantic", None) { 733 | s == semantic 734 | } else { 735 | false 736 | } 737 | }) { 738 | Some(e) => Some(e), 739 | None => None, 740 | } 741 | } 742 | 743 | fn get_input_source<'a>( 744 | &'a self, 745 | parent_element: &'a xml::Element, 746 | input_element: &'a xml::Element, 747 | ) -> Option<&'a xml::Element> { 748 | let source_id = (input_element.get_attribute("source", None))?; 749 | 750 | if let Some(element) = parent_element 751 | .children 752 | .iter() 753 | .filter_map(|node| { 754 | if let xml::Xml::ElementNode(ref e) = node { 755 | Some(e) 756 | } else { 757 | None 758 | } 759 | }) 760 | .find(|e| { 761 | if let Some(id) = e.get_attribute("id", None) { 762 | let id = "#".to_string() + id; 763 | id == source_id 764 | } else { 765 | false 766 | } 767 | }) 768 | { 769 | if element.name == "source" { 770 | return Some(element); 771 | } else { 772 | let input = (element.get_child("input", self.get_ns()))?; 773 | return self.get_input_source(parent_element, input); 774 | } 775 | } 776 | None 777 | } 778 | 779 | fn get_array_for_input(&self, parent: &Element, input: &Element) -> Option> { 780 | let source = (self.get_input_source(parent, input))?; 781 | let array_element = 782 | (if let Some(float_array) = source.get_child("float_array", self.get_ns()) { 783 | Some(float_array) 784 | } else { 785 | source.get_child("Name_array", self.get_ns()) 786 | })?; 787 | get_array_content(array_element) 788 | } 789 | 790 | fn get_vertex_indices(&self, prim_element: &xml::Element) -> Option> { 791 | let p_element = (prim_element.get_child("p", self.get_ns()))?; 792 | let indices: Vec = (get_array_content(p_element))?; 793 | 794 | let input_count = prim_element 795 | .get_children("input", self.get_ns()) 796 | .filter(|c| { 797 | if let Some(set) = c.get_attribute("set", None) { 798 | set == "0" 799 | } else { 800 | true 801 | } 802 | }) 803 | .count(); 804 | 805 | let vertex_offset = (self.get_input_offset(prim_element, "VERTEX"))?; 806 | let vertex_indices = indices 807 | .chunks(input_count) 808 | .map(|indices| indices[vertex_offset]) 809 | .collect(); 810 | 811 | Some(vertex_indices) 812 | } 813 | 814 | fn get_normal_indices(&self, prim_element: &xml::Element) -> Option> { 815 | let p_element = (prim_element.get_child("p", self.get_ns()))?; 816 | let indices: Vec = (get_array_content(p_element))?; 817 | 818 | let input_count = prim_element 819 | .get_children("input", self.get_ns()) 820 | .filter(|c| { 821 | if let Some(set) = c.get_attribute("set", None) { 822 | set == "0" 823 | } else { 824 | true 825 | } 826 | }) 827 | .count(); 828 | 829 | self.get_input_offset(prim_element, "NORMAL") 830 | .map(|normal_offset| { 831 | indices 832 | .chunks(input_count) 833 | .map(|indices| indices[normal_offset]) 834 | .collect() 835 | }) 836 | } 837 | 838 | fn get_texcoord_indices(&self, prim_element: &xml::Element) -> Option> { 839 | let p_element = (prim_element.get_child("p", self.get_ns()))?; 840 | let indices: Vec = (get_array_content(p_element))?; 841 | 842 | let input_count = prim_element 843 | .get_children("input", self.get_ns()) 844 | .filter(|c| { 845 | if let Some(set) = c.get_attribute("set", None) { 846 | set == "0" 847 | } else { 848 | true 849 | } 850 | }) 851 | .count(); 852 | 853 | self.get_input_offset(prim_element, "TEXCOORD") 854 | .map(|texcoord_offset| { 855 | indices 856 | .chunks(input_count) 857 | .map(|indices| indices[texcoord_offset]) 858 | .collect() 859 | }) 860 | } 861 | 862 | fn get_vtn_indices(&self, prim_element: &xml::Element) -> Option> { 863 | let p_element = (prim_element.get_child("p", self.get_ns()))?; 864 | let indices: Vec = (get_array_content(p_element))?; 865 | 866 | let input_count = prim_element 867 | .get_children("input", self.get_ns()) 868 | .filter(|c| { 869 | if let Some(set) = c.get_attribute("set", None) { 870 | set == "0" 871 | } else { 872 | true 873 | } 874 | }) 875 | .count(); 876 | 877 | let position_offset = (self.get_input_offset(prim_element, "VERTEX"))?; 878 | 879 | let normal_offset_opt = self.get_input_offset(prim_element, "NORMAL"); 880 | let texcoord_offset_opt = self.get_input_offset(prim_element, "TEXCOORD"); 881 | 882 | let vtn_indices: Vec = indices 883 | .chunks(input_count) 884 | .map(|indices| { 885 | let position_index = indices[position_offset]; 886 | 887 | let normal_index_opt = 888 | normal_offset_opt.map(|normal_offset| indices[normal_offset]); 889 | 890 | let texcoord_index_opt = 891 | texcoord_offset_opt.map(|texcoord_offset| indices[texcoord_offset]); 892 | 893 | (position_index, texcoord_index_opt, normal_index_opt) 894 | }) 895 | .collect(); 896 | 897 | Some(vtn_indices) 898 | } 899 | 900 | fn get_material(&self, primitive_el: &xml::Element) -> Option { 901 | primitive_el 902 | .get_attribute("material", None) 903 | .map(|s| s.to_string()) 904 | } 905 | 906 | //fn get_geometry_binding_type(&self, mesh_element: &xml::Element) -> Option { 907 | // if mesh_element.get_child(GeometryBindingType::Polylist.name(), self.get_ns()).is_some() { 908 | // return Some(GeometryBindingType::Polylist) 909 | // } 910 | // if mesh_element.get_child(GeometryBindingType::Triangles.name(), self.get_ns()).is_some() { 911 | // return Some(GeometryBindingType::Triangles) 912 | // } 913 | // None 914 | //} 915 | 916 | fn get_mesh_elements(&self, mesh_element: &xml::Element) -> Option> { 917 | let mut prims = vec![]; 918 | for child in &mesh_element.children { 919 | match child { 920 | xml::Xml::ElementNode(el) => { 921 | if el.name == GeometryBindingType::Polylist.name() { 922 | let shapes = self 923 | .get_polylist_shape(el) 924 | .expect("Polylist had no shapes."); 925 | let material = self.get_material(el); 926 | let polylist = Polylist { shapes, material }; 927 | prims.push(PrimitiveElement::Polylist(polylist)) 928 | } else if el.name == GeometryBindingType::Triangles.name() { 929 | let material = self.get_material(el); 930 | let triangles = self 931 | .get_triangles(el, material) 932 | .expect("Triangles had no indices."); 933 | prims.push(PrimitiveElement::Triangles(triangles)) 934 | } 935 | } 936 | _ => {} 937 | } 938 | } 939 | 940 | if prims.is_empty() { 941 | None 942 | } else { 943 | Some(prims) 944 | } 945 | } 946 | 947 | fn get_triangles( 948 | &self, 949 | triangles: &xml::Element, 950 | material: Option, 951 | ) -> Option { 952 | let count_str: &str = triangles.get_attribute("count", None)?; 953 | let count = count_str.parse::().ok().unwrap(); 954 | 955 | let vertices = self.get_vertex_indices(triangles).map(|vertex_indices| { 956 | let mut vertex_iter = vertex_indices.iter(); 957 | (0..count) 958 | .map(|_| { 959 | ( 960 | *vertex_iter.next().unwrap(), 961 | *vertex_iter.next().unwrap(), 962 | *vertex_iter.next().unwrap(), 963 | ) 964 | }) 965 | .collect() 966 | })?; 967 | 968 | let tex_vertices = self 969 | .get_texcoord_indices(triangles) 970 | .map(|texcoord_indices| { 971 | let mut texcoord_iter = texcoord_indices.iter(); 972 | (0..count) 973 | .map(|_| { 974 | ( 975 | *texcoord_iter.next().unwrap(), 976 | *texcoord_iter.next().unwrap(), 977 | *texcoord_iter.next().unwrap(), 978 | ) 979 | }) 980 | .collect() 981 | }); 982 | 983 | let normals = self.get_normal_indices(triangles).map(|normal_indices| { 984 | let mut normal_iter = normal_indices.iter(); 985 | (0..count) 986 | .map(|_| { 987 | ( 988 | *normal_iter.next().unwrap(), 989 | *normal_iter.next().unwrap(), 990 | *normal_iter.next().unwrap(), 991 | ) 992 | }) 993 | .collect() 994 | }); 995 | 996 | Some(Triangles { 997 | vertices, 998 | tex_vertices, 999 | normals, 1000 | material, 1001 | }) 1002 | } 1003 | 1004 | fn get_polylist_shape(&self, polylist_element: &xml::Element) -> Option> { 1005 | let vtn_indices = (self.get_vtn_indices(polylist_element))?; 1006 | 1007 | let vcount_element = (polylist_element.get_child("vcount", self.get_ns()))?; 1008 | let vertex_counts: Vec = (get_array_content(vcount_element))?; 1009 | 1010 | let mut vtn_iter = vtn_indices.iter(); 1011 | let shapes = vertex_counts 1012 | .iter() 1013 | .map(|vertex_count| { 1014 | match *vertex_count { 1015 | 1 => Shape::Point(*vtn_iter.next().unwrap()), 1016 | 2 => Shape::Line(*vtn_iter.next().unwrap(), *vtn_iter.next().unwrap()), 1017 | 3 => Shape::Triangle( 1018 | *vtn_iter.next().unwrap(), 1019 | *vtn_iter.next().unwrap(), 1020 | *vtn_iter.next().unwrap(), 1021 | ), 1022 | n => { 1023 | // Polys with more than 3 vertices not supported - try to advance and continue 1024 | // TODO attempt to triangle-fy? (take a look at wavefront_obj) 1025 | for _ in 0..n { 1026 | vtn_iter.next(); 1027 | } 1028 | Shape::Point((0, None, None)) 1029 | } 1030 | } 1031 | }) 1032 | .collect(); 1033 | 1034 | Some(shapes) 1035 | } 1036 | } 1037 | 1038 | #[test] 1039 | fn test_get_obj_set() { 1040 | let collada_document = ColladaDocument::from_path(Path::new("test_assets/test.dae")).unwrap(); 1041 | let obj_set = collada_document.get_obj_set().unwrap(); 1042 | assert_eq!(obj_set.objects.len(), 1); 1043 | 1044 | let object = &obj_set.objects[0]; 1045 | assert_eq!(object.id, "BoxyWorm-mesh"); 1046 | assert_eq!(object.name, "BoxyWorm"); 1047 | assert_eq!(object.vertices.len(), 16); 1048 | assert_eq!(object.tex_vertices.len(), 84); 1049 | assert_eq!(object.normals.len(), 28); 1050 | assert_eq!(object.geometry.len(), 1); 1051 | 1052 | let geometry = &object.geometry[0]; 1053 | 1054 | let prim = &geometry.mesh[0]; 1055 | if let PrimitiveElement::Polylist(polylist) = prim { 1056 | assert_eq!(polylist.shapes.len(), 28); 1057 | let shape = polylist.shapes[1]; 1058 | if let Shape::Triangle((position_index, Some(texture_index), Some(normal_index)), _, _) = 1059 | shape 1060 | { 1061 | assert_eq!(position_index, 7); 1062 | assert_eq!(texture_index, 3); 1063 | assert_eq!(normal_index, 1); 1064 | } else { 1065 | assert!(false); 1066 | } 1067 | } 1068 | } 1069 | 1070 | #[test] 1071 | fn test_get_obj_set_triangles_geometry() { 1072 | let collada_document = 1073 | ColladaDocument::from_path(Path::new("test_assets/test_cube_triangles_geometry.dae")) 1074 | .unwrap(); 1075 | let obj_set = collada_document.get_obj_set().unwrap(); 1076 | assert_eq!(obj_set.objects.len(), 1); 1077 | 1078 | let object = &obj_set.objects[0]; 1079 | assert_eq!(object.id, "Cube-mesh"); 1080 | assert_eq!(object.name, "Cube"); 1081 | assert_eq!(object.vertices.len(), 8); 1082 | assert_eq!(object.normals.len(), 12); 1083 | assert_eq!(object.geometry.len(), 1); 1084 | 1085 | let geometry = &object.geometry[0]; 1086 | 1087 | let prim = &geometry.mesh[0]; 1088 | match prim { 1089 | PrimitiveElement::Triangles(triangles) => { 1090 | assert_eq!(triangles.vertices.len(), 12); 1091 | 1092 | let position_index = triangles.vertices[1].0; 1093 | assert_eq!(position_index, 7); 1094 | 1095 | if let Some(ref normals) = triangles.normals { 1096 | assert_eq!(normals.len(), 12); 1097 | 1098 | let normal_index = normals[1].0; 1099 | assert_eq!(normal_index, 1); 1100 | } else { 1101 | assert!(false, "Triangle is missing a normal."); 1102 | } 1103 | } 1104 | x => { 1105 | assert!(false, "Not a polylist: {:#?}", x); 1106 | } 1107 | } 1108 | } 1109 | 1110 | #[test] 1111 | fn test_get_bind_data_set() { 1112 | let collada_document = ColladaDocument::from_path(Path::new("test_assets/test.dae")).unwrap(); 1113 | let bind_data_set = collada_document.get_bind_data_set().unwrap(); 1114 | let bind_data = &bind_data_set.bind_data[0]; 1115 | 1116 | assert_eq!(bind_data.object_name, "BoxyWorm-mesh"); 1117 | assert!(bind_data.skeleton_name.is_some()); 1118 | assert_eq!(bind_data.skeleton_name.as_ref().unwrap(), "BoxWormRoot"); 1119 | assert_eq!(bind_data.joint_names, ["Root", "UpperArm", "LowerArm"]); 1120 | assert_eq!(bind_data.vertex_weights.len(), 29); 1121 | assert_eq!(bind_data.weights.len(), 29); 1122 | assert_eq!(bind_data.inverse_bind_poses.len(), 3); 1123 | } 1124 | 1125 | #[test] 1126 | fn test_get_skeletons() { 1127 | let collada_document = ColladaDocument::from_path(Path::new("test_assets/test.dae")).unwrap(); 1128 | let skeletons = collada_document.get_skeletons().unwrap(); 1129 | assert_eq!(skeletons.len(), 1); 1130 | 1131 | let skeleton = &skeletons[0]; 1132 | assert_eq!(skeleton.joints.len(), 4); 1133 | assert_eq!(skeleton.bind_poses.len(), 4); 1134 | 1135 | assert_eq!(skeleton.joints[0].name, "Root"); 1136 | assert_eq!(skeleton.joints[0].parent_index, ROOT_JOINT_PARENT_INDEX); 1137 | assert!(skeleton.joints[0].inverse_bind_pose != vecmath::mat4_id()); 1138 | 1139 | assert_eq!(skeleton.joints[1].name, "UpperArm"); 1140 | assert_eq!(skeleton.joints[1].parent_index, 0); 1141 | assert!(skeleton.joints[1].inverse_bind_pose != vecmath::mat4_id()); 1142 | 1143 | assert_eq!(skeleton.joints[2].name, "UpperArmDanglyBit"); 1144 | assert_eq!(skeleton.joints[2].parent_index, 1); 1145 | assert_eq!(skeleton.joints[2].inverse_bind_pose, vecmath::mat4_id()); 1146 | 1147 | assert_eq!(skeleton.joints[3].name, "LowerArm"); 1148 | assert_eq!(skeleton.joints[3].parent_index, 0); 1149 | assert!(skeleton.joints[3].inverse_bind_pose != vecmath::mat4_id()); 1150 | } 1151 | 1152 | #[test] 1153 | fn test_get_animations() { 1154 | let collada_document = ColladaDocument::from_path(Path::new("test_assets/test.dae")).unwrap(); 1155 | let animations = collada_document.get_animations().unwrap(); 1156 | assert_eq!(animations.len(), 4); 1157 | 1158 | let animation = &animations[1]; 1159 | assert_eq!(animation.target, "UpperArm/transform"); 1160 | assert_eq!(animation.sample_times.len(), 4); 1161 | assert_eq!(animation.sample_poses.len(), 4); 1162 | 1163 | let animation = &animations[3]; 1164 | assert_eq!(animation.target, "LowerArm/transform"); 1165 | assert_eq!(animation.sample_times.len(), 4); 1166 | assert_eq!(animation.sample_poses.len(), 4); 1167 | } 1168 | 1169 | #[test] 1170 | fn test_get_obj_set_noskeleton() { 1171 | let collada_document = 1172 | ColladaDocument::from_path(Path::new("test_assets/test_noskeleton.dae")).unwrap(); 1173 | collada_document.get_obj_set().unwrap(); 1174 | } 1175 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate vecmath; 2 | extern crate xml; 3 | 4 | #[macro_use] 5 | extern crate log; 6 | 7 | pub use obj::*; 8 | pub use vecmath::Matrix4; 9 | 10 | pub mod document; 11 | mod obj; 12 | mod utils; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct Skeleton { 16 | /// 17 | /// All joints in the skeleton 18 | /// 19 | pub joints: Vec, 20 | 21 | /// 22 | /// Default parent-relative transforms for each joint (at time of vertex binding) 23 | /// Column-major. 24 | /// 25 | pub bind_poses: Vec>, 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct Joint { 30 | /// 31 | /// Name of joint 32 | /// 33 | pub name: String, 34 | 35 | /// 36 | /// Index of parent joint in Skeleton's 'joints' vector 37 | /// 38 | pub parent_index: JointIndex, 39 | 40 | /// 41 | /// Matrix transforming vertex coordinates from model-space to joint-space 42 | /// Column-major. 43 | /// 44 | pub inverse_bind_pose: Matrix4, 45 | } 46 | 47 | impl Joint { 48 | pub fn is_root(&self) -> bool { 49 | self.parent_index == ROOT_JOINT_PARENT_INDEX 50 | } 51 | } 52 | 53 | /// 54 | /// A COLLADA animation consists of mapping of sample times to pose transforms 55 | /// for a single node in the scene (usually a skeleton joint) 56 | /// 57 | /// Note - COLLADA supports animating arbitrary 'outputs', not just pose transforms, 58 | /// (eg colors, texture offsets, etc), but we'll leave those unsupported for now. 59 | /// 60 | #[derive(Debug)] 61 | pub struct Animation { 62 | /// 63 | /// The node (joint) this animation is targeting 64 | /// 65 | pub target: String, 66 | 67 | /// 68 | /// Times for each sample (in seconds) 69 | /// 70 | pub sample_times: Vec, 71 | 72 | /// 73 | /// Node pose transforms for each sample. 74 | /// Column-major. 75 | /// 76 | pub sample_poses: Vec>, 77 | } 78 | 79 | /// 80 | /// Skeleton-Mesh Binding Data 81 | /// 82 | 83 | #[derive(Debug)] 84 | pub struct BindDataSet { 85 | pub bind_data: Vec, 86 | } 87 | 88 | #[derive(Debug)] 89 | pub struct BindData { 90 | pub object_name: String, 91 | pub skeleton_name: Option, 92 | pub joint_names: Vec, 93 | 94 | /// Vertex weights, for vertex by index in mesh and joint by index in 'joint_names' 95 | /// and weight by index in 'weights' 96 | pub vertex_weights: Vec, 97 | 98 | /// Weight values that are indexed by VertexWeights 99 | pub weights: Vec, 100 | 101 | /// Inverse bind pose matrices listed in order of joint_names 102 | /// Column-major 103 | pub inverse_bind_poses: Vec>, 104 | } 105 | 106 | #[derive(Debug, Copy, Clone)] 107 | pub struct VertexWeight { 108 | pub vertex: VertexIndex, 109 | pub joint: JointIndex, 110 | pub weight: WeightIndex, 111 | } 112 | 113 | pub type WeightIndex = usize; 114 | 115 | pub type JointIndex = u8; 116 | pub const ROOT_JOINT_PARENT_INDEX: JointIndex = 255u8; 117 | -------------------------------------------------------------------------------- /src/obj.rs: -------------------------------------------------------------------------------- 1 | // Note: this is mostly copied and adapted from https://github.com/PistonDevelopers/wavefront_obj, 2 | // could potentially be moved to a shared dependency 3 | 4 | /// A set of objects from the Collada document 5 | #[derive(Clone, Debug)] 6 | pub struct ObjSet { 7 | /// Which material library to use. 8 | pub material_library: Option, 9 | /// The set of objects. 10 | pub objects: Vec, 11 | } 12 | 13 | /// A mesh object. 14 | #[derive(Clone, Debug)] 15 | pub struct Object { 16 | pub id: String, 17 | /// A human-readable name for this object. This can be set in blender. 18 | pub name: String, 19 | /// The set of vertices this object is composed of. These are referenced 20 | /// by index in `faces`. 21 | pub vertices: Vec, 22 | /// The set of attached joints for each vertex. Should match 23 | /// length of 'vertices' if present 24 | pub joint_weights: Vec, 25 | /// The set of texture vertices referenced by this object. The actual 26 | /// vertices are indexed by a `PrimitiveElement`. 27 | pub tex_vertices: Vec, 28 | /// The set of normals referenced by this object. The actual normals are 29 | /// indexed by a `PrimitiveElement`. 30 | pub normals: Vec, 31 | /// A set of shapes (with materials applied to them) of which this object is 32 | /// composed. 33 | pub geometry: Vec, 34 | } 35 | 36 | /// A set of shapes, all using the given material. 37 | #[derive(Clone, Debug)] 38 | pub struct Geometry { 39 | /// Should we use smooth shading when rendering this? 40 | pub smooth_shading_group: usize, 41 | /// The shapes of which this geometry is composed. 42 | pub mesh: Vec, 43 | } 44 | 45 | #[derive(Clone, Debug)] 46 | pub struct Triangles { 47 | /// The vertex indices of the triangles. 48 | pub vertices: Vec<(VertexIndex, VertexIndex, VertexIndex)>, 49 | /// The texture vertex indices of the triangles. 50 | pub tex_vertices: Option>, 51 | /// The normal indices of the triangles. 52 | pub normals: Option>, 53 | /// The material of the polylist. Optional. 54 | pub material: Option, 55 | } 56 | 57 | /// The various shapes supported by this library that can be found in a Polylist. 58 | /// 59 | /// Convex polygons more complicated than a triangle are automatically 60 | /// converted into triangles. 61 | #[derive(Clone, Copy, Debug, Hash)] 62 | pub enum Shape { 63 | /// A point specified by its position. 64 | Point(VTNIndex), 65 | /// A line specified by its endpoints. 66 | Line(VTNIndex, VTNIndex), 67 | /// A triangle specified by its three vertices. 68 | Triangle(VTNIndex, VTNIndex, VTNIndex), 69 | } 70 | 71 | /// Provides the information needed for a mesh to bind vertex attributes 72 | /// together and then organize those vertices into individual polygons. 73 | #[derive(Clone, Debug)] 74 | pub struct Polylist { 75 | /// The shapes in this polylist. 76 | pub shapes: Vec, 77 | /// The material of the polylist. Optional. 78 | pub material: Option, 79 | } 80 | 81 | /// Geometric primitives, which assemble values from the inputs into vertex 82 | /// attribute data. 83 | #[derive(Clone, Debug)] 84 | pub enum PrimitiveElement { 85 | // Lines, 86 | // LineStrips, 87 | // Polygons, 88 | Polylist(Polylist), 89 | Triangles(Triangles), 90 | // Trifans, 91 | // Tristrips, 92 | } 93 | 94 | /// A single 3-dimensional point on the corner of an object. 95 | #[allow(missing_docs)] 96 | #[derive(Clone, Copy, Debug)] 97 | pub struct Vertex { 98 | pub x: f64, 99 | pub y: f64, 100 | pub z: f64, 101 | } 102 | 103 | /// Represents the weights of any joints that should 104 | /// control the vertex with skinned animation 105 | #[derive(Clone, Copy, Debug)] 106 | pub struct JointWeights { 107 | /// Indices of joints attached to this vertex. 108 | /// Maximum of 4 joints 109 | pub joints: [usize; 4], 110 | /// Weights for each joint attached to this vertex. 111 | /// Maximum of 4 joints 112 | pub weights: [f32; 4], 113 | } 114 | /// A single 3-dimensional normal 115 | pub type Normal = Vertex; 116 | 117 | /// A single 2-dimensional point on a texture. "Texure Vertex". 118 | #[allow(missing_docs)] 119 | #[derive(Clone, Copy, Debug)] 120 | pub struct TVertex { 121 | pub x: f64, 122 | pub y: f64, 123 | } 124 | 125 | /// An index into the `vertices` array of an object, representing a vertex in 126 | /// the mesh. After parsing, this is guaranteed to be a valid index into the 127 | /// array, so unchecked indexing may be used. 128 | pub type VertexIndex = usize; 129 | 130 | /// An index into the `texture vertex` array of an object. 131 | /// 132 | /// Unchecked indexing may be used, because the values are guaranteed to be in 133 | /// range by the parser. 134 | pub type TextureIndex = usize; 135 | 136 | /// An index into the `normals` array of an object. 137 | /// 138 | /// Unchecked indexing may be used, because the values are guaranteed to be in 139 | /// range by the parser. 140 | pub type NormalIndex = usize; 141 | 142 | /// An index into the vertex array, with an optional index into the texture 143 | /// array. This is used to define the corners of shapes which may or may not 144 | /// be textured. 145 | pub type VTNIndex = (VertexIndex, Option, Option); 146 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use xml::Element; 3 | use xml::Xml::{CharacterNode, ElementNode}; 4 | 5 | pub fn parse_string_to_vector(string: &str) -> Vec { 6 | string 7 | .trim() 8 | .split(&[' ', '\n'][..]) 9 | .map(|s| s.parse().ok().expect("Error parsing array in COLLADA file")) 10 | .collect() 11 | } 12 | 13 | pub fn get_array_content(element: &Element) -> Option> { 14 | match element.children[0] { 15 | CharacterNode(ref contents) => Some(parse_string_to_vector(contents)), 16 | _ => None, 17 | } 18 | } 19 | 20 | pub fn has_attribute_with_value(e: &Element, name: &str, value: &str) -> bool { 21 | if let Some(s) = e.get_attribute(name, None) { 22 | s == value 23 | } else { 24 | false 25 | } 26 | } 27 | 28 | pub fn to_matrix_array(float_array: Vec) -> Vec<[[f32; 4]; 4]> { 29 | float_array 30 | .chunks(16) 31 | .map(|chunk| { 32 | let mut matrix = [[0f32; 4]; 4]; 33 | for (&chunk_value, matrix_value) in chunk 34 | .iter() 35 | .zip(matrix.iter_mut().flat_map(|n| n.iter_mut())) 36 | { 37 | *matrix_value = chunk_value; 38 | } 39 | matrix 40 | }) 41 | .collect() 42 | } 43 | 44 | /// 45 | /// Returns an iterator over all ElementNodes in an XML Element subtree with the given root, 46 | /// using a pre-order tree traversal (root before children) 47 | /// 48 | pub fn pre_order_iter(root: &Element) -> PreOrderIterator { 49 | PreOrderIterator { stack: vec![root] } 50 | } 51 | 52 | /// 53 | /// Returns an iterator over all ElementNodes in an XML Element subtree with the given root, 54 | /// with their depth relative to the subtree root, 55 | /// using a pre-order tree traversal (root before children) 56 | /// 57 | pub fn pre_order_with_depth_iter(root: &Element) -> PreOrderWithDepthIterator { 58 | PreOrderWithDepthIterator { 59 | stack: vec![(root, 0)], 60 | } 61 | } 62 | 63 | pub struct PreOrderIterator<'a> { 64 | stack: Vec<&'a Element>, 65 | } 66 | 67 | impl<'a> Iterator for PreOrderIterator<'a> { 68 | type Item = &'a Element; 69 | fn next(&mut self) -> Option<&'a Element> { 70 | let current_element = self.stack.pop(); 71 | match current_element { 72 | Some(element) => { 73 | for child in element.children.iter().rev() { 74 | if let ElementNode(ref e) = *child { 75 | self.stack.push(e); 76 | } 77 | } 78 | } 79 | None => (), 80 | } 81 | current_element 82 | } 83 | } 84 | 85 | pub struct PreOrderWithDepthIterator<'a> { 86 | stack: Vec<(&'a Element, usize)>, 87 | } 88 | 89 | impl<'a> Iterator for PreOrderWithDepthIterator<'a> { 90 | type Item = (&'a Element, usize); 91 | fn next(&mut self) -> Option<(&'a Element, usize)> { 92 | match self.stack.pop() { 93 | Some((element, depth)) => { 94 | for child in element.children.iter().rev() { 95 | if let ElementNode(ref e) = *child { 96 | self.stack.push((e, depth + 1)); 97 | } 98 | } 99 | Some((element, depth)) 100 | } 101 | None => None, 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test_assets/test.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/piston_collada/baadada54d05a9aec0970b78969a4c4bf221fd39/test_assets/test.blend -------------------------------------------------------------------------------- /test_assets/test.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blender User 6 | Blender 2.72.0 commit date:2014-10-21, commit time:11:38, hash:9e963ae 7 | 8 | 2015-02-21T13:34:12 9 | 2015-02-21T13:34:12 10 | 11 | Z_UP 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 0 0 0 1 21 | 22 | 23 | 0 0 0 1 24 | 25 | 26 | 0.64 0.64 0.64 1 27 | 28 | 29 | 0.5 0.5 0.5 1 30 | 31 | 32 | 50 33 | 34 | 35 | 1 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 1 1 -1 1 -1 -1 -1 -0.9999998 -1 -0.9999997 1 -1 1 0.9999995 1 0.9999994 -1.000001 1 -1 -0.9999997 1 -1 1 1 1 0.9999995 3 0.9999994 -1.000001 3 -1 -0.9999997 3 -1 1 3 1 0.9999995 5 0.9999994 -1.000001 5 -1 -0.9999997 5 -1 1 5 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 0 -1 -1 2.08616e-7 0 1 -5.66244e-7 3.27825e-7 -4.76837e-7 -1 0 -1 2.08616e-7 -1.19209e-7 2.08616e-7 1 2.38419e-7 -1 2.08616e-7 0 -4.76837e-7 -1 0 2.68221e-7 1 0 1 -5.66244e-7 0 0 0 1 -4.76837e-7 -1 0 2.68221e-7 1 0 1 -5.66244e-7 0 0 0 -1 -1 2.08616e-7 0 1 0 -2.38419e-7 0 -1 -2.98023e-7 -1 2.38419e-7 -1.49012e-7 2.68221e-7 1 1.78814e-7 -1 2.08616e-7 0 -4.76837e-7 -1 0 2.68221e-7 1 0 1 -5.66244e-7 0 0 0 1 -4.76837e-7 -1 0 2.68221e-7 1 0 1 -5.66244e-7 0 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 0 0 1 0 1 1 0 0 1 0 1 1 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 0 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 0 0 1 0 1 1 0 0 1 0 1 1 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 0 1 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 0 1 0 0 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 108 |

0 0 0 0 1 0 1 1 2 0 2 2 7 1 3 3 6 1 4 4 10 1 5 5 4 2 6 6 5 2 7 7 1 2 8 8 5 3 9 9 6 3 10 10 2 3 11 11 2 4 12 12 6 4 13 13 7 4 14 14 4 5 15 15 0 5 16 16 3 5 17 17 11 6 18 18 10 6 19 19 14 6 20 20 6 7 21 21 5 7 22 22 9 7 23 23 4 8 24 24 7 8 25 25 11 8 26 26 5 9 27 27 4 9 28 28 8 9 29 29 15 10 30 30 14 10 31 31 13 10 32 32 10 11 33 33 9 11 34 34 13 11 35 35 8 12 36 36 11 12 37 37 15 12 38 38 9 13 39 39 8 13 40 40 12 13 41 41 3 14 42 42 0 14 43 43 2 14 44 44 11 15 45 45 7 15 46 46 10 15 47 47 0 16 48 48 4 16 49 49 1 16 50 50 1 17 51 51 5 17 52 52 2 17 53 53 3 18 54 54 2 18 55 55 7 18 56 56 7 19 57 57 4 19 58 58 3 19 59 59 15 20 60 60 11 20 61 61 14 20 62 62 10 21 63 63 6 21 64 64 9 21 65 65 8 22 66 66 4 22 67 67 11 22 68 68 9 23 69 69 5 23 70 70 8 23 71 71 12 24 72 72 15 24 73 73 13 24 74 74 14 25 75 75 10 25 76 76 13 25 77 77 12 26 78 78 8 26 79 79 15 26 80 80 13 27 81 81 9 27 82 82 12 27 83 83

109 |
110 |
111 |
112 |
113 | 114 | 115 | 116 | 0.04166662 0.8333333 1.666667 2.5 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -1 0 -4.76837e-7 0 0 -1 0 1 -4.76837e-7 0 1 3 0 0 0 1 -1 0 -4.76837e-7 0 0 -1 0 1 -4.76837e-7 0 1 3 0 0 0 1 -1 0 -4.76837e-7 0 0 -1 0 1 -4.76837e-7 0 1 3 0 0 0 1 -1 0 -4.76837e-7 0 0 -1 0 1 -4.76837e-7 0 1 3 0 0 0 1 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | LINEAR LINEAR LINEAR LINEAR 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 0.04166662 0.8333333 1.666667 2.5 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -1 -4.76837e-7 -8.74228e-8 0 -8.74228e-8 0 1 1 -4.76837e-7 1 -2.6906e-13 0 0 0 0 1 -0.9746631 -0.02624835 0.2221323 0 0.2012852 -0.5360308 0.8198507 1 0.09755003 0.8437902 0.5277329 0 0 0 0 1 -0.9589655 -0.1442006 -0.2441134 0 -0.2778772 0.648986 0.7082384 1 0.05629777 0.7470097 -0.6624252 0 0 0 0 1 -1 -4.69387e-7 -2.22302e-8 0 -2.22302e-8 -5.68107e-8 1 1 -4.69387e-7 1 5.68104e-8 0 0 0 0 1 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | LINEAR LINEAR LINEAR LINEAR 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 0.04166662 0.8333333 1.666667 2.5 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 1 -1.33227e-15 -2.21261e-9 1.77636e-15 1.56455e-9 0.7071068 0.7071068 3 1.56455e-9 -0.7071068 0.7071068 0 0 0 0 1 1 -1.33227e-15 -2.21261e-9 1.77636e-15 1.56455e-9 0.7071068 0.7071068 3 1.56455e-9 -0.7071068 0.7071068 0 0 0 0 1 1 -1.33227e-15 -2.21261e-9 1.77636e-15 1.56455e-9 0.7071068 0.7071068 3 1.56455e-9 -0.7071068 0.7071068 0 0 0 0 1 1 -1.33227e-15 -2.21261e-9 1.77636e-15 1.56455e-9 0.7071068 0.7071068 3 1.56455e-9 -0.7071068 0.7071068 0 0 0 0 1 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | LINEAR LINEAR LINEAR LINEAR 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 0.04166662 0.8333333 1.666667 2.5 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -1 4.76837e-7 -8.74228e-8 0 8.74228e-8 0 -1 1 -4.76837e-7 -1 -2.6906e-13 0 0 0 0 1 -1 4.76837e-7 -8.74228e-8 0 8.74228e-8 0 -1 1 -4.76837e-7 -1 -2.6906e-13 0 0 0 0 1 -1 4.76837e-7 -8.74228e-8 0 8.74228e-8 0 -1 1 -4.76837e-7 -1 -2.6906e-13 0 0 0 0 1 -1 4.76837e-7 -8.74228e-8 0 8.74228e-8 0 -1 1 -4.76837e-7 -1 -2.6906e-13 0 0 0 0 1 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | LINEAR LINEAR LINEAR LINEAR 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 247 | 248 | Root UpperArm LowerArm 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | -1 0 -4.76837e-7 9.53674e-7 0 -1 0 1 -4.76837e-7 0 1 -2 0 0 0 1 1 0 0 0 0 0 1 -2 0 -1 0 0 0 0 0 1 1 0 0 0 0 0 -1 2 0 1 0 0 0 0 0 1 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 4.85241e-4 4.85241e-4 0.9990295 0 0 1 7.05123e-5 7.05123e-5 0.999859 0 0 1 1.37985e-5 1.37985e-5 0.9999724 0.003690063 0.003690063 0.9926198 4.57287e-4 0.9995427 1 1 1 1 1 1 1 1 1 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 3 3 3 3 3 3 2 1 1 1 1 1 1 1 1 1 279 | 0 0 1 1 2 2 0 3 1 4 2 5 0 6 1 7 2 8 0 9 1 10 2 11 0 12 1 13 2 14 0 15 1 16 2 17 1 18 2 19 2 20 1 21 1 22 1 23 1 24 1 25 1 26 1 27 1 28 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 1 0 0 0 0 1 0 0 0 0 1 -1 0 0 0 1 288 | 0 0 -1 289 | 0 0 1 0 290 | 0 1 0 0 291 | 1 0 0 0 292 | 1 1 1 293 | 294 | -1 0 -4.76837e-7 0 0 -1 0 1 -4.76837e-7 0 1 3 0 0 0 1 295 | 296 | -1 -4.76837e-7 -8.74228e-8 0 -8.74228e-8 0 1 1 -4.76837e-7 1 -2.6906e-13 0 0 0 0 1 297 | 298 | 1 0 -2.21261e-9 0 1.56455e-9 0.7071068 0.7071068 3 1.56455e-9 -0.7071068 0.7071068 0 0 0 0 1 299 | 300 | 301 | 302 | -1 4.76837e-7 -8.74228e-8 0 8.74228e-8 0 -1 1 -4.76837e-7 -1 -2.6906e-13 0 0 0 0 1 303 | 304 | 305 | 306 | 307 | 0 0 0 308 | 0 0 1 0 309 | 0 1 0 0 310 | 1 0 0 0 311 | 1 1 1 312 | 313 | #Root 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 |
327 | -------------------------------------------------------------------------------- /test_assets/test_cube_triangles_geometry.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blender User 6 | Blender 2.79.0 commit date:2018-03-22, commit time:14:10, hash:f4dc9f9 7 | 8 | 2018-08-12T14:57:37 9 | 2018-08-12T14:57:37 10 | 11 | Z_UP 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 0 0 0 1 21 | 22 | 23 | 0 0 0 1 24 | 25 | 26 | 0.64 0.64 0.64 1 27 | 28 | 29 | 0.5 0.5 0.5 1 30 | 31 | 32 | 50 33 | 34 | 35 | 1 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 1 1 -1 1 -1 -1 -1 -0.9999998 -1 -0.9999997 1 -1 1 0.9999995 1 0.9999994 -1.000001 1 -1 -0.9999997 1 -1 1 1 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 0 -1 0 0 1 1 0 -2.38419e-7 0 -1 -4.76837e-7 -1 2.38419e-7 -1.49012e-7 2.68221e-7 1 2.38419e-7 0 0 -1 0 0 1 1 -5.96046e-7 3.27825e-7 -4.76837e-7 -1 0 -1 2.38419e-7 -1.19209e-7 2.08616e-7 1 0 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |

0 0 2 0 3 0 7 1 5 1 4 1 4 2 1 2 0 2 5 3 2 3 1 3 2 4 7 4 3 4 0 5 7 5 4 5 0 6 1 6 2 6 7 7 6 7 5 7 4 8 5 8 1 8 5 9 6 9 2 9 2 10 6 10 7 10 0 11 3 11 7 11

77 |
78 |
79 |
80 |
81 | 82 | 83 | 84 | 85 | 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | -------------------------------------------------------------------------------- /test_assets/test_noskeleton.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blender User 6 | Blender 2.76.0 commit date:2015-10-11, commit time:06:55, hash:48f7dd6 7 | 8 | 2016-08-05T12:46:16 9 | 2016-08-05T12:46:16 10 | 11 | Z_UP 12 | 13 | 14 | 15 | 16 | 17 | 18 | 49.13434 19 | 1.777778 20 | 0.1 21 | 100 22 | 23 | 24 | 25 | 26 | 27 | 0 28 | 0 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 49.13434 38 | 1.777778 39 | 0.1 40 | 100 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 0 48 | 0 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 1 1 1 58 | 1 59 | 0 60 | 0.00111109 61 | 62 | 63 | 64 | 65 | 0.000999987 66 | 1 67 | 0.1 68 | 0.1 69 | 1 70 | 1 71 | 1 72 | 2 73 | 0 74 | 1 75 | 1 76 | 1 77 | 1 78 | 1 79 | 0 80 | 2880 81 | 2 82 | 30.002 83 | 1.000799 84 | 0.04999995 85 | 29.99998 86 | 1 87 | 2 88 | 0 89 | 0 90 | 1 91 | 1 92 | 1 93 | 1 94 | 8192 95 | 1 96 | 1 97 | 0 98 | 1 99 | 1 100 | 1 101 | 3 102 | 0 103 | 0 104 | 0 105 | 0 106 | 0 107 | 1 108 | 1 109 | 1 110 | 3 111 | 0.15 112 | 75 113 | 1 114 | 1 115 | 0 116 | 1 117 | 1 118 | 0 119 | 120 | 121 | 122 | 123 | 124 | 125 | 1 1 1 126 | 1 127 | 0 128 | 0.00111109 129 | 130 | 131 | 132 | 133 | 9.99987e-4 134 | 1 135 | 0.1 136 | 0.1 137 | 1 138 | 1 139 | 1 140 | 2 141 | 0 142 | 1 143 | 1 144 | 1 145 | 1 146 | 1 147 | 0 148 | 2880 149 | 2 150 | 30.002 151 | 1.000799 152 | 0.04999995 153 | 29.99998 154 | 1 155 | 2 156 | 0 157 | 0 158 | 1 159 | 1 160 | 1 161 | 1 162 | 8192 163 | 1 164 | 1 165 | 0 166 | 1 167 | 1 168 | 1 169 | 3 170 | 0 171 | 0 172 | 0 173 | 0 174 | 0 175 | 1 176 | 1 177 | 1 178 | 3 179 | 0.15 180 | 75 181 | 1 182 | 1 183 | 0 184 | 1 185 | 1 186 | 0 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 0 1 -1 0 1 1 0.1950902 0.9807853 -1 0.1950902 0.9807853 1 0.3826835 0.9238795 -1 0.3826835 0.9238795 1 0.5555703 0.8314696 -1 0.5555703 0.8314696 1 0.7071068 0.7071068 -1 0.7071068 0.7071068 1 0.8314697 0.5555702 -1 0.8314697 0.5555702 1 0.9238795 0.3826834 -1 0.9238795 0.3826834 1 0.9807853 0.1950902 -1 0.9807853 0.1950902 1 1 0 -1 1 0 1 0.9807853 -0.1950902 -1 0.9807853 -0.1950902 1 0.9238796 -0.3826833 -1 0.9238796 -0.3826833 1 0.8314697 -0.5555702 -1 0.8314697 -0.5555702 1 0.7071068 -0.7071068 -1 0.7071068 -0.7071068 1 0.5555702 -0.8314697 -1 0.5555702 -0.8314697 1 0.3826833 -0.9238796 -1 0.3826833 -0.9238796 1 0.1950901 -0.9807853 -1 0.1950901 -0.9807853 1 -3.25841e-7 -1 -1 -3.25841e-7 -1 1 -0.1950907 -0.9807852 -1 -0.1950907 -0.9807852 1 -0.3826839 -0.9238793 -1 -0.3826839 -0.9238793 1 -0.5555707 -0.8314693 -1 -0.5555707 -0.8314693 1 -0.7071073 -0.7071064 -1 -0.7071073 -0.7071064 1 -0.83147 -0.5555697 -1 -0.83147 -0.5555697 1 -0.9238799 -0.3826827 -1 -0.9238799 -0.3826827 1 -0.9807854 -0.1950893 -1 -0.9807854 -0.1950893 1 -1 9.65599e-7 -1 -1 9.65599e-7 1 -0.9807851 0.1950913 -1 -0.9807851 0.1950913 1 -0.9238791 0.3826845 -1 -0.9238791 0.3826845 1 -0.8314689 0.5555713 -1 -0.8314689 0.5555713 1 -0.7071059 0.7071077 -1 -0.7071059 0.7071077 1 -0.5555691 0.8314704 -1 -0.5555691 0.8314704 1 -0.3826821 0.9238801 -1 -0.3826821 0.9238801 1 -0.1950888 0.9807856 -1 -0.1950888 0.9807856 1 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 0.09801697 0.9951848 0 0.2902849 0.9569404 0 0.4713966 0.8819214 0 0.6343933 0.7730104 0 0.7730107 0.634393 0 0.8819215 0.4713965 0 0.9569402 0.2902852 0 0.9951848 0.09801667 0 0.9951848 -0.09801733 0 0.9569404 -0.2902847 0 0.8819212 -0.4713968 0 0.7730104 -0.6343934 0 0.6343934 -0.7730104 0 0.4713965 -0.8819214 0 0.2902843 -0.9569405 0 0.09801691 -0.9951848 0 -0.09801757 -0.9951848 0 -0.2902851 -0.9569403 0 -0.4713971 -0.8819211 0 -0.6343934 -0.7730104 0 -0.7730111 -0.6343926 0 -0.8819215 -0.4713966 0 -0.9569408 -0.2902833 0 -0.9951848 -0.09801661 0 -0.9951847 0.09801846 0 -0.95694 0.2902857 0 -0.8819207 0.4713978 0 -0.77301 0.6343941 0 -0.634392 0.7730115 0 -0.4713955 0.881922 0 0 0 1 -0.0980162 0.9951849 0 -0.2902832 0.9569408 0 0 0 -1 0.2902848 0.9569404 0 0.4713967 0.8819213 0 0.634393 0.7730107 0 0.7730104 0.6343934 0 0.8819217 0.471396 0 0.9569404 0.2902847 0 0.9951848 0.09801727 0 0.9951848 -0.09801673 0 0.9569406 -0.2902842 0 0.8819216 -0.4713963 0 0.7730107 -0.634393 0 0.6343931 -0.7730107 0 0.4713966 -0.8819214 0 0.2902842 -0.9569405 0 -0.2902851 -0.9569402 0 -0.4713972 -0.8819211 0 -0.8819217 -0.471396 0 -0.9951849 -0.09801608 0 -0.9951847 0.09801787 0 -0.7730096 0.6343944 0 -0.6343923 0.7730113 0 -0.4713956 0.8819219 0 0 0 1 -3.97512e-6 0 1 1.36853e-6 0 1 1.36853e-6 0 1 -3.88857e-7 0 1 1.02308e-6 0 1 -1.94429e-7 0 1 -2.87796e-7 0 1 0 0 1 -0.2902833 0.9569408 0 0 0 -1 1.36853e-6 0 -1 3.88857e-7 0 -1 2.87797e-7 0 -1 0 0 -1 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 222 |

1 0 3 0 2 0 3 1 5 1 4 1 5 2 7 2 6 2 7 3 9 3 8 3 9 4 11 4 10 4 11 5 13 5 12 5 13 6 15 6 14 6 15 7 17 7 16 7 17 8 19 8 18 8 19 9 21 9 20 9 21 10 23 10 22 10 23 11 25 11 24 11 25 12 27 12 26 12 27 13 29 13 28 13 29 14 31 14 30 14 31 15 33 15 32 15 33 16 35 16 34 16 35 17 37 17 36 17 37 18 39 18 38 18 39 19 41 19 40 19 41 20 43 20 42 20 43 21 45 21 44 21 45 22 47 22 46 22 47 23 49 23 48 23 49 24 51 24 50 24 51 25 53 25 52 25 53 26 55 26 54 26 55 27 57 27 56 27 57 28 59 28 58 28 59 29 61 29 60 29 37 30 21 30 53 30 63 31 1 31 0 31 61 32 63 32 62 32 30 33 46 33 14 33 0 0 1 0 2 0 2 34 3 34 4 34 4 35 5 35 6 35 6 36 7 36 8 36 8 37 9 37 10 37 10 38 11 38 12 38 12 39 13 39 14 39 14 40 15 40 16 40 16 41 17 41 18 41 18 42 19 42 20 42 20 43 21 43 22 43 22 44 23 44 24 44 24 45 25 45 26 45 26 46 27 46 28 46 28 47 29 47 30 47 30 15 31 15 32 15 32 16 33 16 34 16 34 48 35 48 36 48 36 49 37 49 38 49 38 19 39 19 40 19 40 20 41 20 42 20 42 50 43 50 44 50 44 22 45 22 46 22 46 51 47 51 48 51 48 52 49 52 50 52 50 25 51 25 52 25 52 26 53 26 54 26 54 53 55 53 56 53 56 54 57 54 58 54 58 55 59 55 60 55 5 56 3 56 1 56 1 56 63 56 5 56 61 56 59 56 57 56 57 56 55 56 53 56 53 57 51 57 49 57 49 56 47 56 53 56 45 58 43 58 37 58 41 56 39 56 37 56 37 56 35 56 33 56 33 56 31 56 29 56 29 56 27 56 25 56 25 56 23 56 21 56 21 56 19 56 17 56 17 56 15 56 21 56 13 56 11 56 9 56 9 56 7 56 5 56 5 56 63 56 61 56 61 56 57 56 5 56 53 59 47 59 45 59 43 56 41 56 37 56 37 60 33 60 21 60 29 61 25 61 21 61 21 56 15 56 13 56 13 56 9 56 21 56 5 62 57 62 53 62 53 63 45 63 37 63 33 56 29 56 21 56 21 56 9 56 5 56 5 64 53 64 21 64 62 31 63 31 0 31 60 65 61 65 62 65 62 66 0 66 2 66 2 66 4 66 6 66 6 66 8 66 10 66 10 66 12 66 6 66 14 66 16 66 18 66 18 66 20 66 14 66 22 66 24 66 30 66 26 66 28 66 30 66 30 66 32 66 34 66 34 66 36 66 38 66 38 66 40 66 42 66 42 66 44 66 46 66 46 66 48 66 50 66 50 66 52 66 54 66 54 66 56 66 62 66 58 66 60 66 62 66 62 66 2 66 14 66 6 66 12 66 14 66 14 67 20 67 22 67 24 66 26 66 30 66 30 66 34 66 46 66 38 66 42 66 46 66 46 68 50 68 62 68 56 66 58 66 62 66 2 66 6 66 14 66 14 69 22 69 30 69 34 66 38 66 46 66 50 66 54 66 62 66 62 70 14 70 46 70

223 |
224 |
225 |
226 |
227 | 228 | 229 | 230 | 231 | 0.6858805 -0.3173701 0.6548619 7.481132 0.7276338 0.3124686 -0.6106656 -6.50764 -0.01081678 0.8953432 0.4452454 5.343665 0 0 0 1 232 | 233 | 234 | 235 | -0.2908646 -0.7711008 0.5663932 4.076245 0.9551712 -0.1998834 0.2183912 1.005454 -0.05518906 0.6045247 0.7946723 5.903862 0 0 0 1 236 | 237 | 238 | 239 | 0.6858804 -0.3173701 0.6548619 7.481132 0.7276337 0.3124686 -0.6106656 -6.50764 -0.01081678 0.8953431 0.4452454 5.343665 0 0 0 1 240 | 241 | 242 | 243 | -0.2908648 -0.7711008 0.5663931 4.076245 0.9551711 -0.1998834 0.2183913 1.005454 -0.05518911 0.6045247 0.7946723 5.903862 0 0 0 1 244 | 245 | 246 | 247 | 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 248 | 249 | 250 | 251 | 1 0 0 0.2240631 0 1 0 -0.1005818 0 0 1 -0.3674445 0 0 0 1 252 | 253 | 1 0 0 -0.2103731 0 0 -1 0.09128687 0 1 0 -0.1584563 0 0 0 1 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 |
--------------------------------------------------------------------------------