Mercurial > hg > forks > gldragon
view dmmodel.cpp @ 37:73a785323e8a
Rendering cleanups.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 01 Dec 2019 23:52:19 +0200 |
parents | d640f2a34031 |
children | 9909014498f0 |
line wrap: on
line source
// // GLDragon - OpenGL PLY model viewer / simple benchmark // -- Scene and model handling + PLY file parsing // Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> // (C) Copyright 2019 Tecnic Software productions (TNSP) // // See file "COPYING" for license information. // #include "dmmodel.h" #include <SDL_endian.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 dmError(info, "Unexpected end of file"); } // 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 tmpU8; int8_t tmpS8; uint16_t tmpU16; int16_t tmpS16; uint32_t tmpU32; int32_t tmpS32; float tmpFloat; switch (ptype) { case PLY_TYPE_INT8: info.file.read(reinterpret_cast<char *>(&tmpS8), 1); pval.v_int = tmpS8; break; case PLY_TYPE_UINT8: info.file.read(reinterpret_cast<char *>(&tmpU8), 1); pval.v_uint = tmpU8; break; case PLY_TYPE_INT16: info.file.read(reinterpret_cast<char *>(&tmpS16), 2); tmpS16 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE16(tmpS16) : SDL_SwapBE16(tmpS16); pval.v_int = tmpS16; break; case PLY_TYPE_UINT16: info.file.read(reinterpret_cast<char *>(&tmpU16), 2); tmpU16 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE16(tmpU16) : SDL_SwapBE16(tmpU16); pval.v_uint = tmpU16; break; case PLY_TYPE_INT32: info.file.read(reinterpret_cast<char *>(&tmpS32), 4); tmpS32 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE32(tmpS32) : SDL_SwapBE32(tmpS32); pval.v_int = tmpS32; break; case PLY_TYPE_UINT32: info.file.read(reinterpret_cast<char *>(&tmpU32), 4); tmpU32 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE32(tmpU32) : SDL_SwapBE32(tmpU32); pval.v_uint = tmpU32; break; case PLY_TYPE_FLOAT: info.file.read(reinterpret_cast<char *>(&tmpFloat), 4); pval.v_float = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapFloatLE(tmpFloat) : SDL_SwapFloatBE(tmpFloat); 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) { DMVector3 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")) { DMVector3 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 dmParseVector(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMVector3 &vec) { if (tokens.size() == offs + 1) { vec.x = vec.y = vec.z = std::stof(tokens[offs]); } else if (tokens.size() == offs + 3) { vec.x = std::stof(tokens[offs]); vec.y = std::stof(tokens[offs + 1]); vec.z = std::stof(tokens[offs + 2]); } else { return dmSyntaxError(info, "Expected 1/3 value vector for '"+ *info.key +"'"); } return true; } bool dmParseVector(DMTextFileInfo &info, const std::vector<std::string> tokens, const size_t offs, DMVector4 &vec) { if (tokens.size() == offs + 1) { vec.p.x = vec.p.y = vec.p.z = std::stof(tokens[offs]); } else if (tokens.size() == offs + 3 || tokens.size() == offs + 4) { vec.p.x = std::stof(tokens[offs]); vec.p.y = std::stof(tokens[offs + 1]); vec.p.z = std::stof(tokens[offs + 2]); if (tokens.size() == offs + 4) vec.p.w = std::stof(tokens[offs + 3]); } else { return dmSyntaxError(info, "Expected 1/3/4 value vector for '"+ *info.key +"'"); } return true; } bool DMSimpleScene::loadInfo(const std::string &filename) { DMTextFileInfo info; DMModel *model = 0; DMLight *light = 0; DMVector4 *ppos = 0, *ppointAt = 0; 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]; info.key = &key; if (key == "model") { DMModel newmodel; if (tokens.size() != 2) { return dmSyntaxError(info, "Keyword model expects a filename argument"); } models.push_back(newmodel); model = &models.back(); model->modelFile = tokens[1]; info.state = 1; } else if (info.state == 1 && (key == "shaderfile")) { if (tokens.size() != 3) { return dmSyntaxError(info, "Keyword shaderfile expects shader type (fs, vs) and filename arguments"); } std::string &shtype = tokens[1], &shfile = tokens[2]; if (shtype == "fs") model->fragShaderFile = shfile; else if (shtype == "vs") model->vertShaderFile = shfile; else { return dmSyntaxError(info, "Invalid shaderfile type '"+ shtype +"'"); } } else if (info.state == 1 && (key == "translate" || key == "rotate" || key == "scale")) { DMVector3 vec; if (!dmParseVector(info, tokens, 1, vec)) return false; if (!model) return false; if (key == "translate") { model->translate = vec; model->translateSet = true; } else if (key == "rotate") { model->rotate = vec; model->rotateSet = true; } else if (key == "scale") { model->scale = vec; model->scaleSet = true; } } else if (info.state == 1 && key == "specular") { DMVector4 val; val.p.w = 1.0f; if (!dmParseVector(info, tokens, 1, val)) return false; model->specular = val; } else if (info.state == 1 && key == "shininess") { if (tokens.size() != 2) { return dmSyntaxError(info, "Expected argument for shininess"); } model->shininess = std::stoi(tokens[1], 0, 0); } else if (key == "light") { DMLight newlight; if (lights.size() >= 4) { return dmError(info, "Too many lights defined (max 4)"); } lights.push_back(newlight); light = &lights.back(); ppos = &light->position; ppointAt = &light->pointAt; info.state = 2; } else if (info.state == 2 && (key == "ambient" || key == "diffuse" || key == "specular")) { DMVector4 val; val.p.w = 1.0f; if (!dmParseVector(info, tokens, 1, val)) return false; if (key == "ambient") light->ambient = val; else if (key == "diffuse") light->diffuse = val; else if (key == "specular") light->specular = val; } else if (key == "camera") { info.state = 3; ppos = &camera.position; ppointAt = &camera.pointAt; } else if ((info.state == 3 || info.state == 2) && (key == "position" || key == "pos" || key == "point_at")) { DMVector4 vec; vec.p.w = 0; if (!dmParseVector(info, tokens, 1, vec)) return false; if (key == "position" || key == "pos") *ppos = vec; else if (key == "point_at") *ppointAt = vec; } else { return dmSyntaxError(info, "Unexpected key '"+ key +"'"); } } return true; }