/*] Copyright (c) 2009-2011, Charles McGarvey [***************************** **] All rights reserved. * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * *****************************************************************************/ #include #include #include #include #include #include #include #include "compression.hh" #include "debug.hh" #include "log.hh" #include "mesh.hh" #include "opengl.hh" #define AC3D_FORMAT_VERSION 0x0b namespace moof { MOOF_REGISTER_RESOURCE(mesh, ac, models); MOOF_REGISTER_RESOURCE(mesh, acz, models); static std::string read_string(std::istream& stream) { std::string str; char atom; do stream.get(atom); while (stream && std::isspace(atom)); if (atom == '"') { do { stream.get(atom); if (atom == '"') break; str += atom; } while (stream); } else { do { stream.get(atom); if (std::isspace(atom)) break; str += atom; } while (stream); } return str; } static int read_hex(std::istream& stream) { int hex; std::ios::fmtflags flags = stream.flags(); stream.setf(std::ios::hex, std::ios::basefield); stream >> hex; stream.flags(flags); return hex; } static vector2 read_pair(std::istream& stream) { vector2 pair; stream >> pair[0] >> pair[1]; return pair; } static vector3 read_triplet(std::istream& stream) { vector3 triplet; stream >> triplet[0] >> triplet[1] >> triplet[2]; return triplet; } static vector4 read_color(std::istream& stream) { vector4 color; stream >> color[0] >> color[1] >> color[2]; color[3] = SCALAR(1.0); return color; } static inline void throw_invalid_atom(const std::string& atom) { throw std::runtime_error("unexpected atom: " + atom); } void mesh::load(std::istream& stream) { // read and verify the AC3D header char magic[5]; unsigned version = 0; stream.get(magic, sizeof(magic)); if (!stream || strncmp(magic, "AC3D", 4) != 0) { throw std::runtime_error("invalid mesh header"); } version = read_hex(stream); if (version > AC3D_FORMAT_VERSION) { throw std::runtime_error("wrong mesh file format version"); } std::string atom; object_ptr obj; std::stack kids; while (stream) { stream >> atom; if (!stream) break; if (atom == "OBJECT") { obj = load_object(stream, obj); } else if (atom == "MATERIAL") { load_material(stream); } else if (atom == "name") { if (obj) { obj->name = read_string(stream); object_ptr parent = obj->parent.lock(); if (parent) parent->kids_byname.insert(std::make_pair(obj->name, obj)); } else { throw_invalid_atom(atom); } } else if (atom == "data") { std::getline(stream, atom); std::getline(stream, obj ? obj->data : atom); } else if (atom == "texture") { if (obj) obj->texture = resource::load(read_string(stream)); } else if (atom == "texrep") { if (obj) obj->texrep = read_pair(stream); else throw_invalid_atom(atom); } else if (atom == "rot") { // TODO std::getline(stream, atom); } else if (atom == "loc") { // TODO std::getline(stream, atom); } else if (atom == "url") { if (obj) std::getline(stream, obj->url); else throw_invalid_atom(atom); } else if (atom == "numvert") { if (!obj) throw_invalid_atom(atom); int numvert = 0; stream >> numvert; for (int i = 0; i < numvert; ++i) { obj->verts.push_back(read_triplet(stream)); } } else if (atom == "numsurf") { if (!obj) throw_invalid_atom(atom); int numsurf = 0; stream >> numsurf; for (int i = 0; i < numsurf; ++i) { stream >> atom; if (atom != "SURF") throw_invalid_atom(atom); load_surface(stream, obj); } } else if (atom == "kids") { int numkids = 0; stream >> numkids; if (0 < numkids) { kids.push(numkids); } else { if (0 < kids.size() && 0 < --kids.top()) kids.pop(); obj = obj->parent.lock(); } } } while (stream); } void mesh::load_material(std::istream& stream) { materials_.push_back(material(read_string(stream))); std::string atom; stream >> atom; materials_.back().diffuse = read_color(stream); stream >> atom; materials_.back().ambient = read_color(stream); stream >> atom; materials_.back().emissive = read_color(stream); stream >> atom; materials_.back().specular = read_color(stream); stream >> atom >> materials_.back().shininess; stream >> atom >> materials_.back().diffuse[3]; materials_.back().diffuse[3] = SCALAR(1.0) - materials_.back().diffuse[3]; } mesh::object_ptr mesh::load_object(std::istream& stream, object_ptr parent) { std::string atom; stream >> atom; if (atom != "world" && atom != "group" && atom != "poly") { throw_invalid_atom(atom); } object_ptr obj = object::alloc(*this); if (parent) { parent->kids.push_back(obj); obj->parent = parent; } else { objects_.push_back(obj); } return obj; } void mesh::load_surface(std::istream& stream, object_ptr obj) { std::string atom; read_hex(stream); int material = 0; stream >> atom; if (atom == "mat") stream >> material >> atom; if (atom != "refs") throw_invalid_atom(atom); int numrefs = 0; stream >> numrefs; ASSERT(numrefs >= 3); if ((int)obj->faces.size() <= material) obj->faces.resize(material + 1); material_group& face = obj->faces[material]; std::vector verts(numrefs); std::vector uv(numrefs); for (int i = 0; i < numrefs; ++i) { stream >> verts[i]; uv[i] = read_pair(stream); } // translate texture coordinates if (obj->texture) obj->texture->fix_uv(uv); // TODO why isn't texture always defined at this point? for (int i = 0; i < numrefs; ++i) { scalar vert = verts[i]; vector2 texcoord = uv[i]; if (vert < face.triangles_uv.size()) { if (texcoord != face.triangles_uv[vert]) { obj->verts.push_back(obj->verts[vert]); face.triangles_uv.resize(obj->verts.size()); vert = obj->verts.size() - 1; } } else face.triangles_uv.resize(vert + 1); face.triangles_uv[vert] = texcoord; verts[i] = vert; } face.triangles.push_back(verts[0]); face.triangles.push_back(verts[1]); face.triangles.push_back(verts[2]); for (int i = 3; i < numrefs; ++i) { face.triangles.push_back(verts[0]); face.triangles.push_back(verts[i-1]); face.triangles.push_back(verts[i]); } #if 0 unsigned vert; stream >> vert; vector2 uv = read_pair(stream); if (vert < face.triangles_uv.size()) { if (uv != face.triangles_uv[vert]) { obj->verts.push_back(obj->verts[vert]); face.triangles_uv.resize(obj->verts.size()); vert = obj->verts.size() - 1; } } else face.triangles_uv.resize(vert + 1); face.triangles_uv[vert] = uv; face.triangles.push_back(vert); unsigned first = vert; stream >> vert; uv = read_pair(stream); if (vert < face.triangles_uv.size()) { if (uv != face.triangles_uv[vert]) { obj->verts.push_back(obj->verts[vert]); face.triangles_uv.resize(obj->verts.size()); vert = obj->verts.size() - 1; } } else face.triangles_uv.resize(vert + 1); face.triangles_uv[vert] = uv; face.triangles.push_back(vert); stream >> vert; uv = read_pair(stream); if (vert < face.triangles_uv.size()) { if (uv != face.triangles_uv[vert]) { obj->verts.push_back(obj->verts[vert]); face.triangles_uv.resize(obj->verts.size()); vert = obj->verts.size() - 1; } } else face.triangles_uv.resize(vert + 1); face.triangles_uv[vert] = uv; face.triangles.push_back(vert); unsigned last = vert; for (int j = 3; j < numrefs; ++j) { face.triangles.push_back(first); face.triangles.push_back(last); stream >> vert; uv = read_pair(stream); if (vert < face.triangles_uv.size()) { if (uv != face.triangles_uv[vert]) { obj->verts.push_back(obj->verts[vert]); face.triangles_uv.resize(obj->verts.size()); vert = obj->verts.size() - 1; } } else face.triangles_uv.resize(vert + 1); face.triangles_uv[vert] = uv; face.triangles.push_back(vert); last = face.triangles.back(); } #endif } mesh::mesh(const std::string& path) { std::ifstream file(path.c_str(), std::ios::binary); if (!file) throw std::runtime_error("cannot find mesh file"); // if we can read the header, the file isn't compressed char magic[5]; file.get(magic, sizeof(magic)); if (strncmp(magic, "AC3D", 4) == 0) { log_info("text mesh detected"); file.seekg(std::ios::beg); load(file); } else { log_info("decompressing mesh..."); file.seekg(std::ios::beg); std::stringstream stream; inflate(file, stream); load(stream); } } void mesh::draw(scalar alpha) const { glEnableClientState(GL_VERTEX_ARRAY); std::vector::const_iterator it; for (it = objects_.begin(); it != objects_.end(); ++it) { (*it)->draw(alpha); } // TODO: disable vertex array? } void mesh::set_material(int index) const { set_material(materials_[index]); } void mesh::set_material(const material& material) const { glColor(material.diffuse); glMaterial(GL_FRONT, GL_DIFFUSE, material.diffuse); glMaterial(GL_FRONT, GL_AMBIENT, material.ambient); glMaterial(GL_FRONT, GL_SPECULAR, material.specular); glMaterial(GL_FRONT, GL_EMISSION, material.emissive); glMaterial(GL_FRONT, GL_SHININESS, material.shininess); } void mesh::object::draw(scalar alpha, bool recurse) const { glVertexPointer(verts); if (texture) { texture->bind(); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } else { image::reset_binding(); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } for (size_t i = 0; i < faces.size(); ++i) { const material_group& face = faces[i]; if (face.triangles.size() == 0) continue; mesh.set_material(i); if (texture) glTexCoordPointer(face.triangles_uv); glDrawElements(GL_TRIANGLES, face.triangles); } if (recurse) { std::vector::const_iterator jt; for (jt = kids.begin(); jt != kids.end(); ++jt) (*jt)->draw(alpha); } } } // namespace moof