diff --git a/scene/scene.cpp b/scene/scene.cpp index 4c8fe86..712948c 100644 --- a/scene/scene.cpp +++ b/scene/scene.cpp @@ -11,13 +11,13 @@ const std::string WHITESPACE = " \n\r\t\f\v"; std::string ltrim(const std::string &s) { - size_t start = s.find_first_not_of(WHITESPACE); - return (start == std::string::npos) ? "" : s.substr(start); + size_t start = s.find_first_not_of(WHITESPACE); + return (start == std::string::npos) ? "" : s.substr(start); } std::string rtrim(const std::string &s) { - size_t end = s.find_last_not_of(WHITESPACE); - return (end == std::string::npos) ? "" : s.substr(0, end + 1); + size_t end = s.find_last_not_of(WHITESPACE); + return (end == std::string::npos) ? "" : s.substr(0, end + 1); } std::string trim(const std::string &s) { return rtrim(ltrim(s)); } @@ -25,213 +25,199 @@ std::string trim(const std::string &s) { return rtrim(ltrim(s)); } void Scene::add(const std::shared_ptr &light) { this->lights_.push_back(light); } void Scene::add(const std::shared_ptr &primitive) { - assert(primitive->shader() != nullptr); - this->primitives_.push_back(primitive); + assert(primitive->shader() != nullptr); + this->primitives_.push_back(primitive); } void Scene::addObj(char const *fileName, Vector3d const &scale, Vector3d const &translation, const std::shared_ptr &shader, bool flipU, bool flipV) { - std::vector> triangles = loadObj(fileName, scale, translation, shader, flipU, flipV); - this->primitives_.insert(this->primitives_.end(), std::make_move_iterator(triangles.begin()), - std::make_move_iterator(triangles.end())); + std::vector> triangles = loadObj(fileName, scale, translation, shader, flipU, flipV); + this->primitives_.insert(this->primitives_.end(), std::make_move_iterator(triangles.begin()), + std::make_move_iterator(triangles.end())); } std::vector> Scene::loadObj(char const *fileName, Vector3d const &scale, Vector3d const &translation, const std::shared_ptr &shader, bool flipU, bool flipV) { - std::vector> faces; - std::vector> indices; + std::vector> faces; + std::vector> indices; - // Open file from disk - std::ifstream file; - file.open(fileName); - if (!file.is_open()) { - std::cout << "(Scene): Could not open .obj file: " << fileName << std::endl; - return std::vector>(); - } - - // Print the file name - std::cout << "(Scene): Loading \"" << fileName << "\"" << std::endl; - - // Actual model data - std::vector vData; - std::vector tangentData; - std::vector bitangentData; - std::vector normalData; - std::vector vnData; - std::vector vtData; - - // Read vertices, normals, textures, and faces from the file - std::string line; - while (getline(file, line)) { - std::stringstream lineStream(trim(line)); - std::string type; - lineStream >> type; - - // Vertices - if (type == "v") { - float x, y, z; - lineStream >> x >> y >> z; - vData.emplace_back(componentProduct(Vector3d(x, y, z), scale) + translation); - tangentData.emplace_back(); - bitangentData.emplace_back(); - normalData.emplace_back(); + // Open file from disk + std::ifstream file; + file.open(fileName); + if (!file.is_open()) { + std::cout << "(Scene): Could not open .obj file: " << fileName << std::endl; + return std::vector>(); } - // Texture coordinates - if (type == "vt") { - float u, v; - lineStream >> u >> v; - vtData.emplace_back(flipU ? 1.0f - u : u, flipV ? 1.0f - v : v); - } + // Print the file name + std::cout << "(Scene): Loading \"" << fileName << "\"" << std::endl; - // Normals - if (type == "vn") { - float a, b, c; - lineStream >> a >> b >> c; - vnData.emplace_back(normalized(componentQuotient( - Vector3d(a, b, c), - scale))); // Division needed for preventing stretched normals, normals' = (transform^-1)^T * normals - } + // Actual model data + std::vector vData; + std::vector tangentData; + std::vector bitangentData; + std::vector normalData; + std::vector vnData; + std::vector vtData; - // Faces - if (type == "f") { - std::string vertex[3]; - std::array vertInd = {-1, -1, -1}; - std::array texInd = {-1, -1, -1}; - std::array normInd = {-1, -1, -1}; - lineStream >> vertex[0] >> vertex[1] >> vertex[2]; + // Read vertices, normals, textures, and faces from the file + std::string line; + while (getline(file, line)) { + std::stringstream lineStream(trim(line)); + std::string type; + lineStream >> type; - // triangulate polygons, like quads (which must be given in triangle fan notation) - while (!vertex[2].empty()) { - auto triangle = std::make_shared(shader); - - for (int i = 0; i < 3; ++i) { - std::stringstream vertexSteam(vertex[i]); - std::string reference; - - // vertex index - getline(vertexSteam, reference, '/'); - try { - vertInd[i] = stoi(reference) - 1; - triangle->setVertex(i, vData.at(vertInd[i])); - } catch (...) { - std::cout << "Error: vertex index invalid on line \"" << line << "\"" << std::endl; - } - - // texture index - if (getline(vertexSteam, reference, '/')) { - if (!reference.empty()) { - try { - texInd[i] = stoi(reference) - 1; - triangle->setSurface(i, vtData.at(texInd[i])); - } catch (...) { - std::cout << "Error: texture coordinate index invalid on line \"" << line << "\"" << std::endl; - } - } - - // normal index - if (getline(vertexSteam, reference, '/')) { - try { - normInd[i] = stoi(reference) - 1; - triangle->setNormal(i, vnData.at(normInd[i])); - } catch (...) { - std::cout << "Error: normal index invalid on line \"" << line << "\"" << std::endl; - } - } - } + // Vertices + if (type == "v") { + float x, y, z; + lineStream >> x >> y >> z; + vData.emplace_back(componentProduct(Vector3d(x, y, z), scale) + translation); + tangentData.emplace_back(); + bitangentData.emplace_back(); + normalData.emplace_back(); } - // calculate and accumulate tangent and bitangent vectors - if (std::all_of(vertInd.begin(), vertInd.end(), [](int i) { return i > -1; }) && - std::all_of(texInd.begin(), texInd.end(), [](int i) { return i > -1; })) { - for (int i = 0; i < 3; i++) { - const Vector3d deltaPos1 = vData.at(vertInd[(i + 1) % 3]) - vData.at(vertInd[i]); - const Vector3d deltaPos2 = vData.at(vertInd[(i + 2) % 3]) - vData.at(vertInd[i]); - - const Vector2d deltaUV1 = vtData.at(texInd[(i + 1) % 3]) - vtData.at(texInd[i]); - const Vector2d deltaUV2 = vtData.at(texInd[(i + 2) % 3]) - vtData.at(texInd[i]); - - const float r = 1.0f / (deltaUV1.u * deltaUV2.v - deltaUV1.v * deltaUV2.u); - tangentData[vertInd[i]] += (deltaPos1 * deltaUV2.v - deltaPos2 * deltaUV1.v) * r; - bitangentData[vertInd[i]] += (deltaPos2 * deltaUV1.u - deltaPos1 * deltaUV2.u) * r; - - normalData[vertInd[i]] += crossProduct(tangentData[vertInd[i]], bitangentData[vertInd[i]]); - } + // Texture coordinates + if (type == "vt") { + float u, v; + lineStream >> u >> v; + vtData.emplace_back(flipU ? 1.0f - u : u, flipV ? 1.0f - v : v); } - faces.push_back(triangle); - indices.push_back(vertInd); + // Normals + if (type == "vn") { + float a, b, c; + lineStream >> a >> b >> c; + vnData.emplace_back(normalized(componentQuotient( + Vector3d(a, b, c), + scale))); // Division needed for preventing stretched normals, normals' = (transform^-1)^T * normals + } - // get the next triangle - if (lineStream.eof()) - break; + // Faces + if (type == "f") { + std::string vertex[3]; + std::array vertInd = {-1, -1, -1}; + std::array texInd = {-1, -1, -1}; + std::array normInd = {-1, -1, -1}; + lineStream >> vertex[0] >> vertex[1] >> vertex[2]; - vertex[1] = vertex[2]; - lineStream >> vertex[2]; - } - } - } + // triangulate polygons, like quads (which must be given in triangle fan notation) + while (!vertex[2].empty()) { + auto triangle = std::make_shared(shader); - // Close the file - file.close(); + for (int i = 0; i < 3; ++i) { + std::stringstream vertexSteam(vertex[i]); + std::string reference; - // set the normalized tangents and bitangents - for (int i = 0; i < faces.size(); i++) { - for (int j = 0; j < 3; j++) { - Vector3d tangent = normalized(tangentData[indices[i][j]]); - const Vector3d bitangent = normalized(bitangentData[indices[i][j]]); - // try to use the normal from the obj file, if it doesn't exist, use the computed normal - Vector3d normal = normalized(normalData[indices[i][j]]); - if (vnData.size() > 0) - normal = dynamic_cast(faces[i].get())->getNormal(j); + // vertex index + getline(vertexSteam, reference, '/'); + try { + vertInd[i] = stoi(reference) - 1; + triangle->setVertex(i, vData.at(vertInd[i])); + } catch (...) { + std::cout << "Error: vertex index invalid on line \"" << line << "\"" << std::endl; + } - // gram-schmidt orthogonalization - tangent = normalized(tangent - normal * dotProduct(normal, tangent)); - // check handedness of coordinate system - if (dotProduct(crossProduct(normal, tangent), bitangent) < 0.0f) - tangent *= -1.0f; + // texture index + if (getline(vertexSteam, reference, '/')) { + if (!reference.empty()) { + try { + texInd[i] = stoi(reference) - 1; + triangle->setSurface(i, vtData.at(texInd[i])); + } catch (...) { + std::cout << "Error: texture coordinate index invalid on line \"" << line << "\"" << std::endl; + } + } - dynamic_cast(faces[i].get())->setTangent(j, tangent); - dynamic_cast(faces[i].get())->setBitangent(j, bitangent); - dynamic_cast(faces[i].get())->setNormal(j, normal); - } - } + // normal index + if (getline(vertexSteam, reference, '/')) { + try { + normInd[i] = stoi(reference) - 1; + triangle->setNormal(i, vnData.at(normInd[i])); + } catch (...) { + std::cout << "Error: normal index invalid on line \"" << line << "\"" << std::endl; + } + } + } + } - // Debug output - std::cout << " -> " << vData.size() << " vertices parsed" << std::endl; - std::cout << " -> " << vnData.size() << " normals parsed" << std::endl; - std::cout << " -> " << vtData.size() << " uv-positions parsed" << std::endl; - std::cout << " -> " << faces.size() << " primitives parsed" << std::endl; + // calculate and accumulate tangent and bitangent vectors + if (std::all_of(vertInd.begin(), vertInd.end(), [](int i) { return i > -1; }) && + std::all_of(texInd.begin(), texInd.end(), [](int i) { return i > -1; })) { + for (int i = 0; i < 3; i++) { + const Vector3d deltaPos1 = vData.at(vertInd[(i + 1) % 3]) - vData.at(vertInd[i]); + const Vector3d deltaPos2 = vData.at(vertInd[(i + 2) % 3]) - vData.at(vertInd[i]); - faces.push_back(std::make_shared(vertices[x - 1] * scale + translation, - vertices[y - 1] * scale + translation, - vertices[z - 1] * scale + translation, - vertex_normals[xn - 1], vertex_normals[yn - 1], - vertex_normals[zn - 1], - shader)); - } else if (key == "#") { - file.ignore(std::numeric_limits::max(), '\n'); - } else { - file.ignore(std::numeric_limits::max(), '\n'); + const Vector2d deltaUV1 = vtData.at(texInd[(i + 1) % 3]) - vtData.at(texInd[i]); + const Vector2d deltaUV2 = vtData.at(texInd[(i + 2) % 3]) - vtData.at(texInd[i]); + + const float r = 1.0f / (deltaUV1.u * deltaUV2.v - deltaUV1.v * deltaUV2.u); + tangentData[vertInd[i]] += (deltaPos1 * deltaUV2.v - deltaPos2 * deltaUV1.v) * r; + bitangentData[vertInd[i]] += (deltaPos2 * deltaUV1.u - deltaPos1 * deltaUV2.u) * r; + + normalData[vertInd[i]] += crossProduct(tangentData[vertInd[i]], bitangentData[vertInd[i]]); + } + } + + faces.push_back(triangle); + indices.push_back(vertInd); + + // get the next triangle + if (lineStream.eof()) + break; + + vertex[1] = vertex[2]; + lineStream >> vertex[2]; } } - file.close(); } + + // Close the file + file.close(); + + // set the normalized tangents and bitangents + for (int i = 0; i < faces.size(); i++) { + for (int j = 0; j < 3; j++) { + Vector3d tangent = normalized(tangentData[indices[i][j]]); + const Vector3d bitangent = normalized(bitangentData[indices[i][j]]); + // try to use the normal from the obj file, if it doesn't exist, use the computed normal + Vector3d normal = normalized(normalData[indices[i][j]]); + if (vnData.size() > 0) + normal = dynamic_cast(faces[i].get())->getNormal(j); + + // gram-schmidt orthogonalization + tangent = normalized(tangent - normal * dotProduct(normal, tangent)); + // check handedness of coordinate system + if (dotProduct(crossProduct(normal, tangent), bitangent) < 0.0f) + tangent *= -1.0f; + + dynamic_cast(faces[i].get())->setTangent(j, tangent); + dynamic_cast(faces[i].get())->setBitangent(j, bitangent); + dynamic_cast(faces[i].get())->setNormal(j, normal); + } + } + + // Debug output + std::cout << " -> " << vData.size() << " vertices parsed" << std::endl; + std::cout << " -> " << vnData.size() << " normals parsed" << std::endl; + std::cout << " -> " << vtData.size() << " uv-positions parsed" << std::endl; + std::cout << " -> " << faces.size() << " primitives parsed" << std::endl; + return faces; } Color Scene::traceRay(Ray &ray) const { - if (this->findIntersection(ray) && ray.remainingBounces-- > 0) { - // If the ray has hit an object, call the shader ... - return ray.primitive->shader()->shade(*this, ray); - } else if (this->environmentMap) { - // ... otherwise look up the environment map ... - float const phi = std::acos(ray.direction.y); - float const rho = std::atan2(ray.direction.z, ray.direction.x) + float(PI); - return this->environmentMap->color(rho / (2.0f * float(PI)), phi / float(PI)); - } else { - // ... if all else fails, just return the background color - return this->backgroundColor; - } + if (this->findIntersection(ray) && ray.remainingBounces-- > 0) { + // If the ray has hit an object, call the shader ... + return ray.primitive->shader()->shade(*this, ray); + } else if (this->environmentMap) { + // ... otherwise look up the environment map ... + float const phi = std::acos(ray.direction.y); + float const rho = std::atan2(ray.direction.z, ray.direction.x) + float(PI); + return this->environmentMap->color(rho / (2.0f * float(PI)), phi / float(PI)); + } else { + // ... if all else fails, just return the background color + return this->backgroundColor; + } }