From d655b52aa2d389dd8b99a046f760d9d81a189cc0 Mon Sep 17 00:00:00 2001 From: Dane Johnson Date: Wed, 20 Jan 2021 14:54:54 -0600 Subject: [PATCH] Can add models with multiple textures --- core/Ball.cpp | 4 +++ core/Mesh.cpp | 65 ++++++++++++++++++++++++------------ core/Mesh.h | 36 ++++++++++++++------ core/Shaders/FlatShader.cpp | 4 --- core/Shaders/FlatShader.h | 7 +++- core/couch.cpp | 15 +-------- demo/main.lua | 30 ++++++++++++----- demo/railing.png | Bin 0 -> 17152 bytes scripting/couch.i | 15 +++++++-- 9 files changed, 115 insertions(+), 61 deletions(-) create mode 100644 demo/railing.png diff --git a/core/Ball.cpp b/core/Ball.cpp index 05ce039..abf78c0 100644 --- a/core/Ball.cpp +++ b/core/Ball.cpp @@ -1,6 +1,8 @@ #include "Ball.h" Ball::Ball() { + IndexList indices; + VertexList vertices; // It's a cube really // Front vertices.push_back(Vertex(1.0f, 1.0f, 1.0f, 0.0f, 1.0f)); @@ -31,5 +33,7 @@ Ball::Ball() { // Right side indices.push_back(Index(2, 3, 7)); indices.push_back(Index(2, 6, 7)); + + submeshes.push_back(new SubMesh(vertices, indices)); } diff --git a/core/Mesh.cpp b/core/Mesh.cpp index 8891d01..4dcb830 100644 --- a/core/Mesh.cpp +++ b/core/Mesh.cpp @@ -1,21 +1,15 @@ #include "Mesh.h" -Mesh::Mesh() { +SubMesh::SubMesh() { material = new Material(); } -Mesh::~Mesh() { - if (material) { - delete material; - } -} - -Mesh::Mesh(VertexList vertices, IndexList indices) { +SubMesh::SubMesh(VertexList vertices, IndexList indices) { this->vertices = vertices; this->indices = indices; } -void Mesh::SetupMesh() { +void SubMesh::SetupSubMesh() { glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO); @@ -40,6 +34,32 @@ void Mesh::SetupMesh() { glBindVertexArray(0); } +void SubMesh::Draw(Shader *shader) { + shader->UpdateColor(material->usesColor, material->color); + shader->UpdateTex(material->usesTex, material->tex); + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES, indices.size() * 3, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); +} + +Mesh::Mesh() {} + +Mesh::~Mesh() { + for (SubMesh *sub : submeshes) { + delete sub; + } +} + +void Mesh::SetupMesh() { + for (SubMesh *sub : submeshes) { + sub->SetupSubMesh(); + } +} + +void Mesh::SetMaterial(int submesh, Material *material) { + submeshes[submesh]->material = material; +} + Mesh* Mesh::FromFile(const char *filename) { // HOCUS: https://assimp-docs.readthedocs.io/en/latest/usage/use_the_lib.html Assimp::Importer importer; @@ -56,31 +76,34 @@ Mesh* Mesh::FromFile(const char *filename) { } aiNode *root = scene->mRootNode; - aiMesh *mesh_to_import = scene->mMeshes[root->mMeshes[0]]; + Mesh *my_mesh = new Mesh(); + for (int i = 0; i < root->mNumMeshes; i++) { + aiMesh *mesh_to_import = scene->mMeshes[root->mMeshes[i]]; + my_mesh->submeshes.push_back(aiMesh2SubMesh(mesh_to_import)); + } - return aiMesh2Mesh(mesh_to_import); + return my_mesh; } -Mesh *Mesh::aiMesh2Mesh(aiMesh *aimesh){ - Mesh *mymesh = new Mesh(); +SubMesh *Mesh::aiMesh2SubMesh(aiMesh *aimesh){ + SubMesh *sub = new SubMesh(); for (int i = 0; i < aimesh->mNumVertices; i++) { aiVector3D aiPosition = aimesh->mVertices[i]; aiVector3D aiUV = aimesh->mTextureCoords[0][i]; // TODO get ALL texture coords Vertex vertex(aiPosition.x, aiPosition.y, aiPosition.z, aiUV.x, aiUV.y); - mymesh->vertices.push_back(vertex); + sub->vertices.push_back(vertex); } for (int i = 0; i < aimesh->mNumFaces; i++) { // We're importing triangulated meshes, so each face is three indices unsigned int *face = aimesh->mFaces[i].mIndices; Index index(face[0], face[1], face[2]); - mymesh->indices.push_back(index); + sub->indices.push_back(index); } - return mymesh; + return sub; } - -void Mesh::Draw() { - glBindVertexArray(VAO); - glDrawElements(GL_TRIANGLES, indices.size() * 3, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); +void Mesh::Draw(Shader *shader) { + for (SubMesh *sub : submeshes) { + sub->Draw(shader); + } } diff --git a/core/Mesh.h b/core/Mesh.h index 4353926..f045f55 100644 --- a/core/Mesh.h +++ b/core/Mesh.h @@ -2,8 +2,9 @@ #define MESH_H #include +#include -// Thirdpart includes +// Thirdparty includes #include #include #include @@ -15,21 +16,36 @@ #include "Index.h" #include "Material.h" -class Mesh : public Spatial { +#include "Shaders/Shader.h" + +class SubMesh { public: + SubMesh(); + SubMesh(VertexList vertices, IndexList indices); VertexList vertices; IndexList indices; Material *material; - Mesh(); - ~Mesh(); - Mesh(VertexList vertices, IndexList indices); - static Mesh *FromFile(const char *filename); - virtual bool IsDrawable() const {return true;} - virtual void Draw(); - virtual void SetupMesh(); + void SetupSubMesh(); + void Draw(Shader *shader); private: Id VAO, VBO, EBO; - static Mesh *aiMesh2Mesh(aiMesh *mesh); +}; + +typedef std::vector SubMeshList; + +class Mesh : public Spatial { +public: + Mesh(); + ~Mesh(); + void SetMaterial(int submesh, Material *material); + static Mesh *FromFile(const char *filename); + virtual bool IsDrawable() const {return true;} + virtual void Draw(Shader *shader); + virtual void SetupMesh(); +protected: + SubMeshList submeshes; +private: + static SubMesh *aiMesh2SubMesh(aiMesh *mesh); }; typedef std::list MeshList; diff --git a/core/Shaders/FlatShader.cpp b/core/Shaders/FlatShader.cpp index 70e4f57..1030b87 100644 --- a/core/Shaders/FlatShader.cpp +++ b/core/Shaders/FlatShader.cpp @@ -3,7 +3,3 @@ #include "flat.frag.h" FlatShader::FlatShader() : Shader(flat_vert, flat_frag) {} - -void FlatShader::UpdateColor(Vector3 color) { - glUniform3f(glGetUniformLocation(id, "color"), color.r, color.g, color.b); -} diff --git a/core/Shaders/FlatShader.h b/core/Shaders/FlatShader.h index 165b58f..dd0e90b 100644 --- a/core/Shaders/FlatShader.h +++ b/core/Shaders/FlatShader.h @@ -1,7 +1,12 @@ +#ifndef FLATSHADER_H +#define FLATSHADER_H + #include "Shader.h" class FlatShader : public Shader { public: FlatShader(); - void UpdateColor(Vector3 color); }; + +#endif /* FLATSHADER_H */ + diff --git a/core/couch.cpp b/core/couch.cpp index 0a8d12d..990610c 100644 --- a/core/couch.cpp +++ b/core/couch.cpp @@ -41,17 +41,7 @@ void render(Node *curr, Shader *shader, Matrix model) { shader->UpdateModel(model); } Mesh *mesh = dynamic_cast(curr); - if (mesh->material->usesColor) { - shader->UpdateColor(true, mesh->material->color); - } else { - shader->UpdateColor(false); - } - if (mesh->material->usesTex) { - shader->UpdateTex(true, mesh->material->tex); - } else { - shader->UpdateTex(false); - } - mesh->Draw(); + mesh->Draw(shader); } for (Node *child : curr->children) { render(child, shader, model); @@ -85,9 +75,6 @@ int main() { glViewport(0, 0, width, height); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - root = Node::GetRoot(); Input *input = Input::GetInstance(); diff --git a/demo/main.lua b/demo/main.lua index 44aa2b8..8a7fc26 100644 --- a/demo/main.lua +++ b/demo/main.lua @@ -24,33 +24,47 @@ local RED = couch.Color(1.0, 0.0, 0.0) local BLUE = couch.Color(0.0, 0.0, 1.0) function init() + local material + camera = couch.Camera() camera:MakeCurrent() camera.transform:Translate(0.0, 0.0, 10.0) ball = couch.Ball() ball:SetupMesh() - ball.material.color = RED - ball.material.usesColor = true + material = couch.Material.new() + material.color = RED + material.usesColor = true + ball:SetMaterial(0, material) couch.Node.GetRoot().children:Append(ball) ball1 = couch.Ball() ball1:SetupMesh() - ball1.material.tex = couch.Texture.FromFile("container.png") - ball1.material.usesTex = true + material = couch.Material.new() + material.tex = couch.Texture.FromFile("container.png") + material.usesTex = true + ball1:SetMaterial(0, material) couch.Node.GetRoot().children:Append(ball1) ball1.transform:Translate(0.0, 3.0, 0.0) trough = couch.Mesh.FromFile("trough.glb") trough:SetupMesh() - trough.material.tex = couch.Texture.FromFile("wood_lowres.png") - trough.material.usesTex = true + material = couch.Material.new() + material.tex = couch.Texture.FromFile("wood_lowres.png") + material.usesTex = true + trough:SetMaterial(0, material) couch.Node.GetRoot().children:Append(trough) trough.transform:Translate(10.0, 0.0, 0.0) scaffold = couch.Mesh.FromFile("scaffold.glb") scaffold:SetupMesh() - scaffold.material.tex = couch.Texture.FromFile("grate_floor_lowres.png") - scaffold.material.usesTex = true + material = couch.Material.new() + material.tex = couch.Texture.FromFile("grate_floor_lowres.png") + material.usesTex = true + scaffold:SetMaterial(0, material) + material = couch.Material.new() + material.tex = couch.Texture.FromFile("railing.png") + material.usesTex = true + scaffold:SetMaterial(1, material) couch.Node.GetRoot().children:Append(scaffold) scaffold.transform:Translate(-10.0, 0.0, 0.0) end diff --git a/demo/railing.png b/demo/railing.png new file mode 100644 index 0000000000000000000000000000000000000000..f7aaa57c91580a43dfa5e05612288c6684f04a78 GIT binary patch literal 17152 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