From fbb7980ce724d7fde8a07864eda8d62f8a99d134 Mon Sep 17 00:00:00 2001 From: MrLetsplay2003 Date: Sun, 9 Oct 2022 23:12:37 +0200 Subject: [PATCH] MTL loading, Lots of changes --- DEPENDENCIES.md | 4 + src/kekengine/cpp/engine.cpp | 18 ++- src/kekengine/cpp/gameobject.cpp | 9 ++ src/kekengine/cpp/mesh.cpp | 30 +++-- src/kekengine/cpp/objparser.cpp | 110 +++++++++++++++++- src/kekengine/cpp/shader.cpp | 1 + src/kekengine/cpp/texture.cpp | 83 +++++++++++++ src/kekengine/include/input.h | 1 + src/kekengine/include/internal.h | 9 +- src/kekengine/include/mesh.h | 21 +++- src/kekengine/include/objparser.h | 10 +- src/kekengine/include/shader.h | 2 +- src/kekengine/include/texture.h | 33 ++++++ .../res/shader/include/lightcalc.glsl | 25 ++++ src/kekengine/res/shader/include/types.glsl | 1 + src/kekengine/res/shader/mesh/fragment.glsl | 19 ++- src/kekgame/cpp/kekgame.cpp | 16 ++- src/kekgame/res/object/cube_colored/Cube.mtl | 12 ++ src/kekgame/res/object/cube_colored/Cube.obj | 46 ++++++++ 19 files changed, 423 insertions(+), 27 deletions(-) create mode 100644 DEPENDENCIES.md create mode 100644 src/kekengine/cpp/texture.cpp create mode 100644 src/kekengine/include/texture.h create mode 100644 src/kekengine/res/shader/include/lightcalc.glsl create mode 100644 src/kekgame/res/object/cube_colored/Cube.mtl create mode 100644 src/kekgame/res/object/cube_colored/Cube.obj diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md new file mode 100644 index 0000000..a9b2f82 --- /dev/null +++ b/DEPENDENCIES.md @@ -0,0 +1,4 @@ +# Dependencies +- OpenGL, GLEW, GLFW +- stb_image, stb_image_write +- microtar (included) diff --git a/src/kekengine/cpp/engine.cpp b/src/kekengine/cpp/engine.cpp index ca23d75..042658d 100644 --- a/src/kekengine/cpp/engine.cpp +++ b/src/kekengine/cpp/engine.cpp @@ -6,6 +6,12 @@ #include #include #include +#include + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include +#include #include "internal.h" #include "errordialog.h" @@ -145,6 +151,9 @@ int init() { glfwSetInputMode(kekData.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glClearColor(0.1f, 0.3f, 0.1f, 0.0f); + //glfwSwapInterval(0); + + stbi_set_flip_vertically_on_load(true); kekData.activeCamera = new Camera(); kekData.shader = new Shader("shader/mesh/vertex.glsl", "shader/mesh/fragment.glsl"); @@ -154,6 +163,8 @@ int init() { int start() { while(!glfwWindowShouldClose(kekData.window)) { + auto start = std::chrono::high_resolution_clock::now(); + // Clear the screen glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, kekData.screenWidth, kekData.screenHeight); @@ -204,14 +215,17 @@ int start() { glUniformMatrix4fv(glGetUniformLocation(kekData.shader->id, "view"), 1, GL_FALSE, glm::value_ptr(view)); glUniformMatrix4fv(glGetUniformLocation(kekData.shader->id, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniform3fv(glGetUniformLocation(kekData.shader->id, "cameraPos"), 1, glm::value_ptr(position)); - glm::mat4 model = glm::mat4(1.0f); - glUniformMatrix4fv(glGetUniformLocation(kekData.shader->id, "model"), 1, GL_FALSE, glm::value_ptr(model)); if(kekData.activeScene) kekData.activeScene->draw(kekData.shader); // Swap buffers and poll window events glfwSwapBuffers(kekData.window); glfwPollEvents(); + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration secsTaken = end - start; + //std::cout << "FT: " << secsTaken.count() * 1000 << std::endl; + //std::cout << "FR: " << (1.0f / secsTaken.count()) << std::endl; } return KEK_SUCCESS; diff --git a/src/kekengine/cpp/gameobject.cpp b/src/kekengine/cpp/gameobject.cpp index 4c81e44..cee0603 100644 --- a/src/kekengine/cpp/gameobject.cpp +++ b/src/kekengine/cpp/gameobject.cpp @@ -1,5 +1,10 @@ #include "gameobject.h" +#include +#include + +#include "internal.h" + namespace kek { GameObject::GameObject() { @@ -17,6 +22,10 @@ void GameObject::addMesh(Mesh *mesh) { } void GameObject::draw(Shader *shader) { + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, position) * glm::mat4_cast(rotation); + glUniformMatrix4fv(glGetUniformLocation(kekData.shader->id, "model"), 1, GL_FALSE, glm::value_ptr(model)); + for(Mesh *mesh : meshes) { mesh->draw(shader); } diff --git a/src/kekengine/cpp/mesh.cpp b/src/kekengine/cpp/mesh.cpp index 3fa7758..e338c49 100644 --- a/src/kekengine/cpp/mesh.cpp +++ b/src/kekengine/cpp/mesh.cpp @@ -12,9 +12,17 @@ Vertex::Vertex(glm::vec3 pos, glm::vec3 normal, glm::vec2 texCoords) { this->texCoords = texCoords; } -Mesh::Mesh(std::vector vertices, std::vector indices) { +Material::Material(std::shared_ptr ambient, std::shared_ptr diffuse, std::shared_ptr specular, float shininess) { + this->ambient = ambient; + this->diffuse = diffuse; + this->specular = specular; + this->shininess = shininess; +} + +Mesh::Mesh(std::vector vertices, std::vector indices, Material *material) { this->vertices = vertices; this->indices = indices; + this->material = material; glGenVertexArrays(1, &vao); glGenBuffers(1, &vbo); @@ -52,6 +60,7 @@ Mesh::~Mesh() { glDeleteBuffers(1, &ebo); glDeleteBuffers(1, &vbo); glDeleteVertexArrays(1, &vao); + delete material; } void Mesh::draw(Shader *shader) { @@ -59,14 +68,19 @@ void Mesh::draw(Shader *shader) { glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - /*if(!light) { // TODO: check if props exist - material->diffuse->use(GL_TEXTURE0); - glUniform1i(glGetUniformLocation(shader->id, "material.diffuse"), 0); - - material->specular->use(GL_TEXTURE1); - glUniform1i(glGetUniformLocation(shader->id, "material.specular"), 1); + GLint ambient = glGetUniformLocation(shader->id, "material.ambient"); + // FIXME: is -1 + if(ambient != -1) { // Material can be passed to shader + material->ambient->use(GL_TEXTURE0); + glUniform1i(ambient, 0); + material->diffuse->use(GL_TEXTURE1); + glUniform1i(glGetUniformLocation(shader->id, "material.diffuse"), 1); + material->specular->use(GL_TEXTURE2); + glUniform1i(glGetUniformLocation(shader->id, "material.specular"), 2); glUniform1f(glGetUniformLocation(shader->id, "material.shininess"), material->shininess); - }*/ + }else { + std::cerr << "oof" << std::endl; + } // Bind vertices + texture coordinates glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); diff --git a/src/kekengine/cpp/objparser.cpp b/src/kekengine/cpp/objparser.cpp index 7d30b69..84e9009 100644 --- a/src/kekengine/cpp/objparser.cpp +++ b/src/kekengine/cpp/objparser.cpp @@ -6,6 +6,7 @@ #include #include "types.h" +#include "resource.h" namespace kek::ObjParser { @@ -24,7 +25,9 @@ static bool readFaceVertex(std::istringstream &stream, uint32_t *outVertex, uint return true; } -Mesh *parse(MemoryBuffer *buf) { +Mesh *parseMesh(MemoryBuffer *buf, std::string folderPath) { + std::map materials; + std::vector vertexPositions; std::vector vertexNormals; std::vector vertexTexCoords; @@ -32,6 +35,8 @@ Mesh *parse(MemoryBuffer *buf) { std::vector vertices; std::vector indices; + Material *material = nullptr; + std::istream stream(buf); std::string line; @@ -68,12 +73,111 @@ Mesh *parse(MemoryBuffer *buf) { indices.push_back(indices.size()); vertices.push_back(Vertex(vertexPositions[v3 - 1], vertexNormals[v3N - 1], vertexTexCoords[v3T - 1])); indices.push_back(indices.size()); + }else if(cmd == "mtllib") { + std::string name; + str >> name; + std::string mtllibPath = folderPath + name; + + std::map mats = loadMaterials(mtllibPath); + materials.insert(mats.begin(), mats.end()); + }else if(cmd == "usemtl") { + std::string mtlName; + str >> mtlName; + auto it = materials.find(mtlName); + if(it == materials.end()) { + std::cerr << "Invalid material specified: " << mtlName << std::endl; + throw std::exception(); + } + + material = it->second; }else { - std::cout << "Ignoring unknown OBJ command: " << cmd << std::endl; + std::cout << "Ignoring unknown/unsupported OBJ command: " << cmd << std::endl; } } - return new Mesh(vertices, indices); + for(auto it = materials.begin(); it != materials.end(); it++) { + if(it->second != material) delete it->second; + } + + if(!material) { + std::shared_ptr colorTex = Texture::generateColor(glm::vec3(1.0f, 0.0f, 0.0f)); + material = new Material(colorTex, colorTex, colorTex, 1.0f); + } + + return new Mesh(vertices, indices, material); +} + +Mesh *loadMesh(std::string path) { + std::size_t n = path.find('/'); + std::string folderPath = n == std::string::npos ? "" : path.substr(n + 1); + MemoryBuffer *buf = Resource::loadResource(path); + Mesh *mesh = parseMesh(buf, folderPath); + delete buf; + return mesh; +} + +std::map parseMaterials(MemoryBuffer *buf, std::string folderPath) { + std::map mats; + + std::string name; + std::shared_ptr ambient; + std::shared_ptr diffuse; + std::shared_ptr specular; + float shininess = 0.0f; + + std::istream stream(buf); + + std::string line; + while(std::getline(stream, line)) { + std::istringstream str(line); + std::string cmd; + str >> cmd; + + if(cmd == "#") continue; + + if(cmd == "newmtl") { + if(name.length() > 0) { + Material *mat = new Material(ambient, diffuse, specular, shininess); + mats.emplace(name, mat); + } + + str >> name; + }else if(cmd == "Ka") { + float r, g, b; + str >> r >> g >> b; + ambient = Texture::generateColor(glm::vec3(r, g, b)); + }else if(cmd == "Kd") { + float r, g, b; + str >> r >> g >> b; + diffuse = Texture::generateColor(glm::vec3(r, g, b)); + }else if(cmd == "Ks") { + float r, g, b; + str >> r >> g >> b; + specular = Texture::generateColor(glm::vec3(r, g, b)); + }else if(cmd == "Ns") { + float s; + str >> s; + shininess = s; + }else { + std::cout << "Ignoring unknown/unsupported MTL command: " << cmd << std::endl; + } + } + + if(name.length() > 0) { + Material *mat = new Material(ambient, diffuse, specular, shininess); + mats.emplace(name, mat); + } + + return mats; +} + +std::map loadMaterials(std::string path) { + std::size_t n = path.find('/'); + std::string folderPath = n == std::string::npos ? "" : path.substr(n + 1); + MemoryBuffer *buf = Resource::loadResource(path); + std::map mats = parseMaterials(buf, folderPath); + delete buf; + return mats; } } diff --git a/src/kekengine/cpp/shader.cpp b/src/kekengine/cpp/shader.cpp index a97b300..c5b21ae 100644 --- a/src/kekengine/cpp/shader.cpp +++ b/src/kekengine/cpp/shader.cpp @@ -32,6 +32,7 @@ static GLuint compileShader(GLenum type, std::string path) { } std::string code = stream.str(); + std::cout << code << std::endl; delete buf; const char *src = code.c_str(); diff --git a/src/kekengine/cpp/texture.cpp b/src/kekengine/cpp/texture.cpp new file mode 100644 index 0000000..b05ae5e --- /dev/null +++ b/src/kekengine/cpp/texture.cpp @@ -0,0 +1,83 @@ +#include "texture.h" + +#include +#include + +#include "resource.h" +#include "internal.h" + +namespace kek { + +static unsigned int allocTexture(unsigned char *data, int width, int height, float borderColor[4]) { + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); + //glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + return id; +} + +Texture::Texture(std::string texturePath) { + this->path = texturePath; + + int width, height, nrChannels; + MemoryBuffer *buf = Resource::loadResource(texturePath); + if(!buf) { + std::cout << "Failed to load texture: " << texturePath << std::endl; + throw std::exception(); + } + + unsigned char *data = stbi_load_from_memory((stbi_uc *) buf->buffer, buf->length, &width, &height, &nrChannels, 0); + delete buf; + + float borderColor[] = { 1.0f, 0.0f, 1.0f, 1.0f }; + allocTexture(data, width, height, borderColor); + + stbi_image_free(data); +} + +Texture::Texture(GLuint id) { + this->id = id; +} + +Texture::~Texture() { + glDeleteTextures(1, &id); +} + +void Texture::use(GLenum texture) { + glActiveTexture(texture); + glBindTexture(GL_TEXTURE_2D, id); +} + +std::shared_ptr Texture::load(std::string texturePath) { + std::shared_ptr ret; + + auto iter = kekData.loadedTextures.find(texturePath); + if(iter != kekData.loadedTextures.end()) ret = iter->second.lock(); + + if(!ret) { + ret = std::make_shared(texturePath); + kekData.loadedTextures.emplace(texturePath, ret); + } + + return ret; +} + +std::shared_ptr Texture::generateColor(glm::vec3 color) { + unsigned char *data = (unsigned char *) malloc(3 * sizeof(unsigned char)); + data[0] = (unsigned char) (color.x * 255); + data[1] = (unsigned char) (color.y * 255); + data[2] = (unsigned char) (color.z * 255); + float borderColor[4] = { color.x, color.y, color.z, 1.0f }; + GLuint tex = allocTexture(data, 1, 1, borderColor); + free(data); + return std::make_shared(tex); +} + +} diff --git a/src/kekengine/include/input.h b/src/kekengine/include/input.h index d22afa7..71443e7 100644 --- a/src/kekengine/include/input.h +++ b/src/kekengine/include/input.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "utils.h" diff --git a/src/kekengine/include/internal.h b/src/kekengine/include/internal.h index 84fff74..0cdd3a0 100644 --- a/src/kekengine/include/internal.h +++ b/src/kekengine/include/internal.h @@ -1,10 +1,13 @@ #pragma once +#include +#include +#include + #include "input.h" #include "camera.h" #include "scene.h" - -#include +#include "texture.h" namespace kek { @@ -21,6 +24,8 @@ struct KekData { int screenWidth; int screenHeight; + + std::map> loadedTextures; }; extern KekData kekData; diff --git a/src/kekengine/include/mesh.h b/src/kekengine/include/mesh.h index 0291787..2f89ad3 100644 --- a/src/kekengine/include/mesh.h +++ b/src/kekengine/include/mesh.h @@ -1,11 +1,13 @@ #pragma once -#include "shader.h" - #include #include #include #include +#include + +#include "shader.h" +#include "texture.h" namespace kek { @@ -18,15 +20,28 @@ struct Vertex { }; +struct Material { + +public: + std::shared_ptr ambient; + std::shared_ptr diffuse; + std::shared_ptr specular; + float shininess; + + Material(std::shared_ptr ambient, std::shared_ptr diffuse, std::shared_ptr specular, float shininess); + +}; + class Mesh { public: std::vector vertices; std::vector indices; + Material *material; unsigned int vao, vbo, ebo; - Mesh(std::vector vertices, std::vector indices); + Mesh(std::vector vertices, std::vector indices, Material *material); ~Mesh(); diff --git a/src/kekengine/include/objparser.h b/src/kekengine/include/objparser.h index ea69523..9e57042 100644 --- a/src/kekengine/include/objparser.h +++ b/src/kekengine/include/objparser.h @@ -1,12 +1,18 @@ #pragma once -#include +#include #include "types.h" #include "mesh.h" namespace kek::ObjParser { -Mesh *parse(MemoryBuffer *buf); +Mesh *parseMesh(MemoryBuffer *buf, std::string folderPath); + +Mesh *loadMesh(std::string path); + +std::map parseMaterials(MemoryBuffer *buf, std::string folderPath); + +std::map loadMaterials(std::string path); } diff --git a/src/kekengine/include/shader.h b/src/kekengine/include/shader.h index 3360bf9..59a14fa 100644 --- a/src/kekengine/include/shader.h +++ b/src/kekengine/include/shader.h @@ -19,4 +19,4 @@ public: }; -} \ No newline at end of file +} diff --git a/src/kekengine/include/texture.h b/src/kekengine/include/texture.h new file mode 100644 index 0000000..4b0b40b --- /dev/null +++ b/src/kekengine/include/texture.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace kek { + +class Texture { + +public: + GLuint id; + std::string path; + + Texture(std::string texturePath); + + Texture(GLuint id); + + ~Texture(); + + void use(GLenum texture); + + static std::shared_ptr load(std::string texturePath); + + static std::shared_ptr generateColor(glm::vec3 color); + +}; + +} diff --git a/src/kekengine/res/shader/include/lightcalc.glsl b/src/kekengine/res/shader/include/lightcalc.glsl new file mode 100644 index 0000000..66e5777 --- /dev/null +++ b/src/kekengine/res/shader/include/lightcalc.glsl @@ -0,0 +1,25 @@ +vec3 calcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir, vec2 texCoord, float shadow) { + vec3 lightDir = normalize(light.position - fragPos); + vec3 halfwayDir = normalize(lightDir + viewDir); + + // Ambient + vec3 ambient = light.ambient * vec3(texture(material.diffuse, texCoord)); + + // Diffuse + float diff = max(dot(normal, lightDir), 0.0); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, texCoord)); + + // Specular + float spec = pow(max(dot(normal, halfwayDir), 0.0), material.shininess); + vec3 specular = light.specular * spec * vec3(texture(material.specular, texCoord)); + + // Attenuation + float dist = length(light.position - fragPos); + float attenuation = 1.0 / (light.constant + light.linear * dist + light.quadratic * (dist * dist)); + + ambient *= attenuation; + diffuse *= attenuation; + specular *= attenuation; + + return ambient + (1.0 - shadow) * (diffuse + specular); +} diff --git a/src/kekengine/res/shader/include/types.glsl b/src/kekengine/res/shader/include/types.glsl index fdf9831..4293fab 100644 --- a/src/kekengine/res/shader/include/types.glsl +++ b/src/kekengine/res/shader/include/types.glsl @@ -1,4 +1,5 @@ struct Material { + sampler2D ambient; sampler2D diffuse; sampler2D specular; float shininess; diff --git a/src/kekengine/res/shader/mesh/fragment.glsl b/src/kekengine/res/shader/mesh/fragment.glsl index eba990c..f31ac2e 100644 --- a/src/kekengine/res/shader/mesh/fragment.glsl +++ b/src/kekengine/res/shader/mesh/fragment.glsl @@ -1,19 +1,28 @@ #version 330 core -!include types.glsl - in VS_OUT { vec3 fragmentPosition; vec2 textureCoordinate; vec3 normal; } fs_in; +!include types.glsl + +uniform Material material; + uniform vec3 cameraPos; out vec4 color; +!include lightcalc.glsl + void main() { - //vec3 norm = normalize(fs_in.normal); - //vec3 viewDir = normalize(cameraPos - fs_in.fragmentPosition); - color = length(fs_in.fragmentPosition) / 2 * vec4(1.0); + vec3 norm = normalize(fs_in.normal); + vec3 viewDir = normalize(cameraPos - fs_in.fragmentPosition); + //color = length(fs_in.fragmentPosition) / 2 * vec4(1.0); + //color = vec4(norm, 1.0); + + color = texture(material.diffuse, fs_in.textureCoordinate); + + //PointLight light = {}; } diff --git a/src/kekgame/cpp/kekgame.cpp b/src/kekgame/cpp/kekgame.cpp index b8a5173..ebc21f3 100644 --- a/src/kekgame/cpp/kekgame.cpp +++ b/src/kekgame/cpp/kekgame.cpp @@ -10,7 +10,7 @@ int main(int argc, char **argv) { Engine::init(); MemoryBuffer *buf = Resource::loadResource("object/sphere/Sphere.obj"); - Mesh *mesh = ObjParser::parse(buf); + Mesh *mesh = ObjParser::parseMesh(buf, "object/sphere/"); delete buf; GameObject *test = new GameObject(); @@ -18,6 +18,20 @@ int main(int argc, char **argv) { Scene *scene = new Scene(); scene->addObject(test); + + MemoryBuffer *buf2 = Resource::loadResource("object/cube_colored/Cube.obj"); + Mesh *mesh2 = ObjParser::parseMesh(buf2, "object/cube_colored/"); + delete buf2; + + for(int i = 0; i < 1; i++) { + GameObject *test2 = new GameObject(); + test2->addMesh(new Mesh(mesh2->vertices, mesh2->indices, mesh2->material)); + test2->moveTo(glm::vec3(0.0f, 5.0f, 3 * i)); + scene->addObject(test2); + } + + delete mesh2; + Engine::setActiveScene(scene); Engine::start(); diff --git a/src/kekgame/res/object/cube_colored/Cube.mtl b/src/kekgame/res/object/cube_colored/Cube.mtl new file mode 100644 index 0000000..cd19e34 --- /dev/null +++ b/src/kekgame/res/object/cube_colored/Cube.mtl @@ -0,0 +1,12 @@ +# Blender 3.3.0 MTL File: 'None' +# www.blender.org + +newmtl Material +Ns 250.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.071425 0.000000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 diff --git a/src/kekgame/res/object/cube_colored/Cube.obj b/src/kekgame/res/object/cube_colored/Cube.obj new file mode 100644 index 0000000..f5afe82 --- /dev/null +++ b/src/kekgame/res/object/cube_colored/Cube.obj @@ -0,0 +1,46 @@ +# Blender 3.3.0 +# www.blender.org +mtllib Cube.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vn -0.0000 1.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.875000 0.500000 +vt 0.625000 0.250000 +vt 0.125000 0.500000 +vt 0.375000 0.250000 +vt 0.875000 0.750000 +vt 0.625000 1.000000 +vt 0.625000 0.000000 +vt 0.375000 0.000000 +vt 0.375000 1.000000 +vt 0.125000 0.750000 +s 0 +usemtl Material +f 5/5/1 3/3/1 1/1/1 +f 3/3/2 8/13/2 4/4/2 +f 7/11/3 6/8/3 8/12/3 +f 2/2/4 8/14/4 6/7/4 +f 1/1/5 4/4/5 2/2/5 +f 5/6/6 2/2/6 6/8/6 +f 5/5/1 7/9/1 3/3/1 +f 3/3/2 7/10/2 8/13/2 +f 7/11/3 5/6/3 6/8/3 +f 2/2/4 4/4/4 8/14/4 +f 1/1/5 3/3/5 4/4/5 +f 5/6/6 1/1/6 2/2/6