Mercurial > hg > forks > gldragon
view dmmodel.cpp @ 19:a329f0216491
Implement PLY file format parsing and extremely simplistic scene setup file format.
Not finished yet.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 07 Nov 2019 20:15:33 +0200 |
parents | |
children | 1404dfcee7b8 |
line wrap: on
line source
// // // #include "dmmodel.h" static bool dmError(DMTextFileInfo &info, const std::string &msg) { printf("ERROR: %s on line #%d: %s\n", msg.c_str(), info.nline, info.line.c_str()); return false; } static bool dmSyntaxError(DMTextFileInfo &info, const std::string &msg) { printf("ERROR: Syntax error on line #%d: %s\n", info.nline, msg.c_str()); return false; } static bool dmReadLine(DMTextFileInfo &info) { if (!std::getline(info.file, info.line)) return false; info.nline++; info.line = dmStrTrim(info.line); return true; } static DMPLYPropType dmPLYParsePropType(const std::string &name) { if (name == "list") return PLY_TYPE_LIST; else if (name == "float" || name == "float32") return PLY_TYPE_FLOAT; else if (name == "double" || name == "float64") return PLY_TYPE_DOUBLE; else if (name == "uchar" || name == "uint8") return PLY_TYPE_UINT8; else if (name == "int16") return PLY_TYPE_INT16; else if (name == "uint16") return PLY_TYPE_UINT16; else if (name == "int" || name == "int32") return PLY_TYPE_INT32; else if (name == "uint" || name == "uint32") return PLY_TYPE_UINT32; else return PLY_TYPE_NONE; } static bool dmPLYParsePropertyValueASCII(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval, size_t &pos) { size_t len = 0; std::string val = info.line.substr(pos); switch (ptype) { case PLY_TYPE_INT8: case PLY_TYPE_INT16: case PLY_TYPE_INT32: pval.v_int = std::stoi(val, &len); break; case PLY_TYPE_UINT8: case PLY_TYPE_UINT16: case PLY_TYPE_UINT32: pval.v_uint = std::stoi(val, &len); break; case PLY_TYPE_FLOAT: pval.v_float = std::stof(val, &len); break; case PLY_TYPE_DOUBLE: pval.v_double = std::stod(val, &len); break; default: return dmError(info, "Internal error, unimplemented PLY property type"); } pos += len; return true; } static bool dmPLYParsePropertyASCII(DMPLYFileInfo &info, DMPLYFileProperty &prop, size_t &pos) { switch (prop.type) { case PLY_TYPE_LIST: prop.list_values.clear(); if (!dmPLYParsePropertyValueASCII(info, prop.list_num_type, prop.list_num_value, pos)) return false; for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++) { DMPLYPropValue pval; if (!dmPLYParsePropertyValueASCII(info, prop.list_values_type, pval, pos)) return false; prop.list_values.push_back(pval); } if (prop.list_values.size() != prop.list_num_value.v_uint) { return dmSyntaxError(info, "Number of property list '"+ prop.name +" values not equal to number specified"); } return true; default: return dmPLYParsePropertyValueASCII(info, prop.type, prop.value, pos); } } static bool dmPLYParseElementASCII(DMPLYFileInfo &info, DMPLYFileElement &element) { size_t nprop = 0, pos = 0; // Read one line if (!dmReadLine(info)) return false; // Skip empty lines if (info.line.empty()) return true; // Parse the properties for (auto const prop : element.properties) { if (!dmPLYParsePropertyASCII(info, *prop, pos)) return false; nprop++; } if (nprop != element.properties.size()) { return dmSyntaxError(info, "Expected N properties, got different number"); } return true; } bool dmPLYReadPropertyValueBIN(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval) { uint8_t tmp[8]; uint16_t tmpU16; int16_t tmpS16; uint32_t tmpU32; int32_t tmpS32; switch (ptype) { case PLY_TYPE_INT8: info.file.read(reinterpret_cast<char *>(&tmp), 1); pval.v_int = (int8_t) tmp[0]; break; case PLY_TYPE_UINT8: info.file.read(reinterpret_cast<char *>(&tmp), 1); pval.v_uint = (uint8_t) tmp[0]; break; case PLY_TYPE_INT16: info.file.read(reinterpret_cast<char *>(&tmp), 2); tmpS16 = (tmp[0] << 8) | (tmp[1]); pval.v_int = tmpS16; break; case PLY_TYPE_UINT16: info.file.read(reinterpret_cast<char *>(&tmp), 2); tmpU16 = (tmp[0] << 8) | (tmp[1]); pval.v_uint = tmpU16; break; case PLY_TYPE_INT32: info.file.read(reinterpret_cast<char *>(&tmp), 4); tmpS32 = (tmp[0] << 24) | (tmp[1] << 16) | (tmp[2] << 8) | (tmp[3]); pval.v_int = tmpS32; break; case PLY_TYPE_UINT32: info.file.read(reinterpret_cast<char *>(&tmp), 4); tmpU32 = (tmp[3] << 24) | (tmp[2] << 16) | (tmp[1] << 8) | (tmp[0]); pval.v_uint = tmpU32; break; case PLY_TYPE_FLOAT: info.file.read(reinterpret_cast<char *>(&tmp), 4); tmpU32 = (tmp[0] << 24) | (tmp[1] << 16) | (tmp[2] << 8) | (tmp[3]); pval.v_uint = tmpU32; break; default: return dmError(info, "Internal error, unimplemented PLY property type"); } return true; } bool dmPLYReadPropertyBIN(DMPLYFileInfo &info, DMPLYFileProperty &prop) { switch (prop.type) { case PLY_TYPE_LIST: prop.list_values.clear(); if (!dmPLYReadPropertyValueBIN(info, prop.list_num_type, prop.list_num_value)) return false; for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++) { DMPLYPropValue pval; if (!dmPLYReadPropertyValueBIN(info, prop.list_values_type, pval)) return false; prop.list_values.push_back(pval); } if (prop.list_values.size() != prop.list_num_value.v_uint) { return dmSyntaxError(info, "Number of property list '"+ prop.name +" values not equal to number specified"); } return true; default: return dmPLYReadPropertyValueBIN(info, prop.type, prop.value); } } bool dmPLYReadElementBIN(DMPLYFileInfo &info, DMPLYFileElement &element) { size_t nprop = 0; for (auto const prop : element.properties) { if (!dmPLYReadPropertyBIN(info, *prop)) return false; nprop++; } if (nprop != element.properties.size()) { return dmSyntaxError(info, "Expected N properties, got different number"); } return true; } bool DMModel::loadFromPLY(const std::string &filename) { DMPLYFileInfo info; return loadFromPLY(filename, info); } bool DMModel::loadFromPLY(const std::string &filename, DMPLYFileInfo &info) { info.filename = filename; info.nline = info.state = 0; info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary); printf("INFO: Trying to read mesh from '%s'.\n", info.filename.c_str()); if (!info.file.is_open()) { printf("ERROR: Unable to open file '%s'.\n", info.filename.c_str()); return false; } // Parse the PLY header while (info.state >= 0) { // Read one line if (!dmReadLine(info)) return false; // Skip empty lines if (info.line.empty()) continue; // Split key and value std::vector<std::string> tokens = dmStrSplit(info.line); std::string &key = tokens[0]; if (info.state == 0 && key == "ply") { info.state = 1; } else if (info.state > 0 && key == "end_header") { info.state = -1; } else if (info.state == 1 && key == "format") { if (tokens.size() < 3) { return dmSyntaxError(info, "Expected value for format"); } info.format = PLY_FMT_UNKNOWN; if (tokens[1] == "ascii") info.format = PLY_FMT_ASCII; else if (tokens[1] == "binary_little_endian") info.format = PLY_FMT_BIN_LE; else if (tokens[1] == "binary_big_endian") info.format = PLY_FMT_BIN_BE; if (info.format == PLY_FMT_UNKNOWN || tokens[2] != "1.0") { printf("ERROR: Unknown or unsupported PLY file format '%s'.\n", (tokens[1] +" "+ tokens[2]).c_str()); return false; } info.state = 2; } else if ((info.state == 2 || info.state == 3) && key == "element") { if (tokens.size() < 3) { return dmSyntaxError(info, "Expected a value for element key"); } std::string &el_name = tokens[1], &el_value = tokens[2]; if (info.elem_map.count(el_name)) { return dmSyntaxError(info, "Element '"+ el_name +"' has already been defined"); } DMPLYFileElement &elem = info.elem_map[el_name]; info.element = &elem; info.elements.push_back(&elem); elem.name = el_name; elem.value = std::stoi(el_value); info.state = 3; } else if (info.state == 3 && key == "property") { if (tokens.size() < 3) { return dmSyntaxError(info, "Expected value for property"); } const std::string &pr_name = tokens.back(), &pr_typename = tokens.at(1); if (!info.element) { // Should not happen return dmSyntaxError(info, "No element defined for property '"+ pr_name +"'?"); } // Check if this property has been already defined if (info.element->prop_map.count(pr_name)) { return dmSyntaxError(info, "Element '"+ info.element->name + "' already has property '"+ pr_name +"' defined?"); } // Parse property information DMPLYPropType pr_type = dmPLYParsePropType(pr_typename); if (pr_type == PLY_TYPE_NONE) { return dmSyntaxError(info, "Invalid or unsupported property type '"+ pr_typename +"'"); } DMPLYFileProperty &prop = info.element->prop_map[pr_name]; info.element->properties.push_back(&prop); prop.name = pr_name; prop.type = pr_type; if (pr_type == PLY_TYPE_LIST) { // List is a special case if (tokens.size() < 5) { return dmSyntaxError(info, "Expected more values for a list property (num_type, val_type, name)"); } prop.list_num_type = dmPLYParsePropType(tokens.at(2)); prop.list_values_type = dmPLYParsePropType(tokens.at(3)); if (prop.list_num_type == PLY_TYPE_NONE || prop.list_values_type == PLY_TYPE_NONE) { return dmSyntaxError(info, "Invalid or unsupported property type(s)"); } } } else if (info.state > 0 // && (key == "comment" || key == "obj_info") ) { // Ignore comments // .. and unknown keys } else { return dmSyntaxError(info, "Unexpected key '"+ key +"'"); } } // Check header data DMPLYFileElement *elem; DMPLYFileProperty *prop; if (info.state != -1 || (elem = info.checkElem(PLY_ELEM_FACE)) == 0 || (prop = elem->checkProp(PLY_PROP_VERTEX_INDICES)) == 0 || prop->type != PLY_TYPE_LIST || (elem = info.checkElem(PLY_ELEM_VERTEX)) == 0 || (prop = elem->checkProp("x")) == 0 || prop->type != PLY_TYPE_FLOAT || (prop = elem->checkProp("y")) == 0 || prop->type != PLY_TYPE_FLOAT || (prop = elem->checkProp("z")) == 0 || prop->type != PLY_TYPE_FLOAT ) { printf("ERROR: PLY file did not contain expected information.\n"); return false; } nvertices = info.elem_map[PLY_ELEM_VERTEX].value; nfaces = info.elem_map[PLY_ELEM_FACE].value; if (nvertices < 3 || nfaces < 1) { printf("ERROR: Invalid nvertices (%d) and/or nfaces (%d).\n", nvertices, nfaces); return false; } printf("INFO: Should have %d vertices, %d faces\n", nvertices, nfaces); // Pre-allocate space vertices.reserve(nvertices); normals.reserve(nvertices); faces.reserve(nfaces * 3); // Read the actual data (in order of the elements) for (auto *element : info.elements) for (int n = 0; n < element->value; n++) { switch (info.format) { case PLY_FMT_ASCII: if (!dmPLYParseElementASCII(info, *element)) return false; break; default: if (!dmPLYReadElementBIN(info, *element)) return false; break; } // Check for specific elements if (element->name == PLY_ELEM_FACE) { DMPLYFileProperty &prop = element->prop_map[PLY_PROP_VERTEX_INDICES]; if (prop.list_num_value.v_uint != 3) { return dmSyntaxError(info, "Expected 3 vertices per face"); } for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++) { faces.push_back(prop.list_values[n].v_uint); } } else if (element->name == PLY_ELEM_VERTEX) { DMVertex vert; vert.x = element->prop_map["x"].value.v_float; vert.y = element->prop_map["y"].value.v_float; vert.z = element->prop_map["z"].value.v_float; vertices.push_back(vert); if (element->checkProp("nx") && element->checkProp("ny") && element->checkProp("nz")) { DMVertex normal; normal.x = element->prop_map["nx"].value.v_float; normal.y = element->prop_map["ny"].value.v_float; normal.z = element->prop_map["nz"].value.v_float; normals.push_back(normal); } } } printf("INFO: Found %ld vertices, %ld normals, %ld faces\n", vertices.size(), normals.size(), faces.size() / 3); return true; } bool DMSimpleScene::loadInfo(const std::string &filename) { DMTextFileInfo info; info.filename = filename; info.nline = info.state = 0; info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary); printf("INFO: Trying to read scene data from '%s'.\n", info.filename.c_str()); if (!info.file.is_open()) { printf("ERROR: Unable to open file '%s'.\n", info.filename.c_str()); return false; } while (info.state >= 0) { // Read one line if (!dmReadLine(info)) { info.state = -1; break; } // Skip empty lines and comments if (info.line.empty() || info.line[0] == '#' || info.line[0] == ';') continue; // Split key and values std::vector<std::string> tokens = dmStrSplit(info.line); std::string &key = tokens[0]; if (key == "color") { DMColor color; color.alpha = 0xff; if (tokens.size() == 2) { color.r = color.g = color.b = std::stoi(tokens[1], 0, 0); } else if (tokens.size() == 4 || tokens.size() == 5) { color.r = std::stoi(tokens[1], 0, 0); color.g = std::stoi(tokens[2], 0, 0); color.b = std::stoi(tokens[3], 0, 0); if (tokens.size() == 5) color.alpha = std::stoi(tokens[4], 0, 0); } else { return dmSyntaxError(info, "Expected color values <value> or <red> <green> <blue> [<alpha>]"); } model.color = color; } else if (key == "translate" || key == "rotate" || key == "scale" || key == "camera_pos" || key == "camera_at") { DMVertex vec; if (tokens.size() == 2) { vec.x = vec.y = vec.z = std::stof(tokens[1]); } else if (tokens.size() == 4) { vec.x = std::stof(tokens[1]); vec.y = std::stof(tokens[2]); vec.z = std::stof(tokens[3]); } else { return dmSyntaxError(info, "Expected vector value for '"+ key +"'"); } if (key == "translate") model.translate = vec; else if (key == "rotate") model.rotate = vec; else if (key == "scale") model.scale = vec; else if (key == "camera_pos") camera.pos = vec; else if (key == "camera_at") camera.lookAt = vec; } else if (key == "light") { if (tokens.size() == 4) { DMVertex pos; pos.x = std::stof(tokens[1]); pos.y = std::stof(tokens[2]); pos.z = std::stof(tokens[3]); } else { return dmSyntaxError(info, "Expected light definition as <x> <y> <z> "); } } else { return dmSyntaxError(info, "Unexpected key '"+ key +"'"); } } model.printInfo(); return true; }