From 4c1305d3c99ccbcd359e1af447af9b530be00eba Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 7 Jul 2024 18:01:30 +0300 Subject: [PATCH] Redesign Object to support submeshes This allows objects with multiple materials to be exported easily. Some functions were removed without leaving compatibility wrappers because they would have been too cumbersome to implement. --- source/render/object.cpp | 227 +++++++++++++++++++++++++-------------- source/render/object.h | 79 ++++++++------ 2 files changed, 196 insertions(+), 110 deletions(-) diff --git a/source/render/object.cpp b/source/render/object.cpp index 04ccbb6b..9581959d 100644 --- a/source/render/object.cpp +++ b/source/render/object.cpp @@ -16,86 +16,98 @@ namespace GL { const Matrix Object::identity_matrix; -Object::Object(): - lods(1) -{ } - Object::Object(const Mesh *m, const Technique *t): Object() { - set_mesh(m); - set_technique(t); + add_submesh(m, t); } Object::Object(const Object &other): - lods(other.lods), + submeshes(other.submeshes), + n_lods(other.n_lods), + lod_bounds(other.lod_bounds), bounding_sphere(other.bounding_sphere) { - if(other.lod0_watched) - watch_lod0(); + if(other.sub0_watched) + watch_sub0(); } Object::Object(Object &&other): - lods(move(other.lods)), + submeshes(move(other.submeshes)), + n_lods(other.n_lods), + lod_bounds(other.lod_bounds), bounding_sphere(move(other.bounding_sphere)) { - if(other.lod0_watched) - watch_lod0(); + if(other.sub0_watched) + watch_sub0(); } Object::~Object() { - if(lods[0].mesh && lod0_watched) - if(ResourceManager *rm = lods[0].mesh->get_manager()) - rm->unobserve_resource(*lods[0].mesh, *this); + if(sub0_watched) + if(ResourceManager *rm = submeshes.front().mesh->get_manager()) + rm->unobserve_resource(*submeshes.front().mesh, *this); } -Object::LevelOfDetail &Object::get_lod(unsigned i, const char *caller) +void Object::set_mesh(const Mesh *m) { - if(i>lods.size()) - throw out_of_range(caller); - if(i>0 && (!lods[0].mesh || !lods[0].technique)) - throw invalid_operation(caller); + if(submeshes.empty()) + add_submesh(m, nullptr); + else + { + if(sub0_watched) + if(ResourceManager *rm = submeshes.front().mesh->get_manager()) + rm->unobserve_resource(*submeshes.front().mesh, *this); - if(i==lods.size()) - lods.push_back(lods.back()); + submeshes.front().mesh = m; + sub0_watched = false; - return lods[i]; + watch_sub0(); + } } -void Object::set_mesh(unsigned i, const Mesh *m) +void Object::add_lod(const Mesh *m, const Technique *t) { - const Mesh *&ptr = get_lod(i, "Object::set_mesh").mesh; - if(i==0 && ptr && lod0_watched) - if(ResourceManager *rm = ptr->get_manager()) - rm->unobserve_resource(*ptr, *this); - ptr = m; - lod0_watched = false; + if(n_lods==MAX_LODS) + throw invalid_operation("Object::add_lod"); + if(n_lods>0 && lod_bounds[n_lods]==lod_bounds[n_lods-1]) + throw invalid_operation("Object::add_lod"); + + ++n_lods; + add_submesh(m, t); +} - if(i==0 && m) - watch_lod0(); +void Object::add_submesh(const Mesh *m, const Technique *t) +{ + Submesh &sm = submeshes.emplace_back(); + sm.mesh = m; + sm.technique = t; + lod_bounds[n_lods] = submeshes.size(); - update_bounding_sphere(); + if(submeshes.size()==1) + watch_sub0(); } -void Object::watch_lod0() +void Object::watch_sub0() { - if(ResourceManager *rm = lods[0].mesh->get_manager()) - { - rm->observe_resource(*lods[0].mesh, *this); - lod0_watched = true; - } + // TODO Should watch all submeshes of lod0 + if(!submeshes.empty() && submeshes.front().mesh) + if(ResourceManager *rm = submeshes.front().mesh->get_manager()) + { + rm->observe_resource(*submeshes.front().mesh, *this); + sub0_watched = true; + } } void Object::update_bounding_sphere() { vector points; - for(const LevelOfDetail &l: lods) + for(const Submesh &s: submeshes) { - if(!l.mesh || !l.mesh->is_loaded()) + if(!s.mesh || !s.mesh->is_loaded()) continue; - const VertexArray &vertices = l.mesh->get_vertices(); + const VertexArray &vertices = s.mesh->get_vertices(); int offset = vertices.get_format().offset(VERTEX3); bool three = true; @@ -124,25 +136,32 @@ void Object::update_bounding_sphere() bounding_sphere = Geometry::BoundingSphere::from_point_cloud(points.begin(), points.end()); } -const Mesh *Object::get_mesh(unsigned i) const +const Mesh *Object::get_mesh(unsigned i, unsigned j) const { - if(i>=lods.size()) + if(i>=n_lods) + return nullptr; + if(lod_bounds[i]+j>=lod_bounds[i+1]) return nullptr; - return lods[i].mesh; + return submeshes[lod_bounds[i]+j].mesh; } -void Object::set_technique(unsigned i, const Technique *t) +void Object::set_technique(const Technique *t) { - get_lod(i, "Object::set_technique").technique = t; + if(submeshes.empty()) + add_submesh(nullptr, t); + else + submeshes.front().technique = t; } -const Technique *Object::get_technique(unsigned i) const +const Technique *Object::get_technique(unsigned i, unsigned j) const { - if(i>=lods.size()) + if(i>=n_lods) + return nullptr; + if(lod_bounds[i]+j>=lod_bounds[i+1]) return nullptr; - return lods[i].technique; + return submeshes[lod_bounds[i]+j].technique; } void Object::render(Renderer &renderer, Tag tag) const @@ -152,49 +171,55 @@ void Object::render(Renderer &renderer, Tag tag) const void Object::render(Renderer &renderer, const ObjectInstance &inst, Tag tag) const { - unsigned lod = min(inst.get_level_of_detail(renderer), lods.size()-1); + unsigned lod = min(inst.get_level_of_detail(renderer), n_lods-1); render(renderer, lod, &inst, tag); } void Object::render(Renderer &renderer, unsigned lod, const ObjectInstance *inst, Tag tag) const { - Renderer::Push push(renderer); - const Mesh *mesh = lods[lod].mesh; - const Technique *tech = lods[lod].technique; - if(!mesh || !tech) + if(lod_bounds[lod]==lod_bounds[lod+1]) return; - const RenderMethod *method = tech->find_method(tag); - if(!method) - return; + Renderer::Push push(renderer); + for(unsigned i=lod_bounds[lod]; ifind_method(tag); + if(!method) + continue; - renderer.set_pipeline_key(this, tag.id); - method->apply(renderer); + renderer.set_pipeline_key(this, tag.id^i); + method->apply(renderer); - setup_render(renderer, tag); - if(inst) - inst->setup_render(renderer, tag); - mesh->draw(renderer); - if(inst) - inst->finish_render(renderer, tag); - finish_render(renderer, tag); + setup_render(renderer, tag); + if(inst) + inst->setup_render(renderer, tag); + mesh->draw(renderer); + if(inst) + inst->finish_render(renderer, tag); + finish_render(renderer, tag); + } } void Object::resource_loaded(Resource &res) { - if(&res==lods.front().mesh && bounding_sphere.is_empty()) + if(&res==submeshes.front().mesh && bounding_sphere.is_empty()) update_bounding_sphere(); } void Object::resource_removed(Resource &res) { - if(&res==lods.front().mesh) - lod0_watched = false; + if(&res==submeshes.front().mesh) + sub0_watched = false; } Object::Loader::Loader(Object &o, Collection &c): - LodLoader(o, 0, c) + LodLoader(o, c) { add("bounding_sphere_hint", &Loader::bounding_sphere_hint); add("level_of_detail", &Loader::level_of_detail); @@ -202,6 +227,7 @@ Object::Loader::Loader(Object &o, Collection &c): void Object::Loader::finish() { + obj.watch_sub0(); obj.update_bounding_sphere(); } @@ -210,42 +236,83 @@ void Object::Loader::bounding_sphere_hint(float x, float y, float z, float r) obj.bounding_sphere = Geometry::BoundingSphere(Vector3(x, y, z), r); } -void Object::Loader::level_of_detail(unsigned i) +void Object::Loader::level_of_detail() { - LodLoader ldr(obj, i, get_collection()); + obj.add_lod(nullptr, nullptr); + LodLoader ldr(obj, get_collection()); load_sub_with(ldr); } -Object::LodLoader::LodLoader(Object &o, unsigned i, Collection &c): +Object::LodLoader::LodLoader(Object &o, Collection &c): DataFile::CollectionObjectLoader(o, &c), - index(i), - lod(obj.get_lod(index, "Object::LodLoader::LodLoader")) + index(obj.n_lods-1) { add("mesh", &LodLoader::mesh_inline); add("mesh", &LodLoader::mesh); + add("submesh", &LodLoader::submesh); add("technique", &LodLoader::technique_inline); add("technique", &LodLoader::technique); } +Object::Submesh &Object::LodLoader::get_submesh() const +{ + if(index==0 && obj.submeshes.empty()) + { + obj.submeshes.emplace_back(); + obj.lod_bounds[index+1] = obj.submeshes.size(); + } + return obj.submeshes[obj.lod_bounds[index]]; +} + void Object::LodLoader::mesh(const string &n) { - obj.set_mesh(index, &get_collection().get(n)); + get_submesh().mesh = &get_collection().get(n); } void Object::LodLoader::mesh_inline() { - lod.mesh = make_sub().into(get_collection(), format("lod%d.mesh", index)).load(); + get_submesh().mesh = make_sub().into(get_collection(), format("lod%d.mesh", index)).load(); +} + +void Object::LodLoader::submesh() +{ + Submesh sm; + load_sub(sm, index, obj.submeshes.size()-obj.lod_bounds[index], get_collection()); + obj.submeshes.push_back(sm); + obj.lod_bounds[index+1] = obj.submeshes.size(); } void Object::LodLoader::technique(const string &n) { - obj.set_technique(index, &get_collection().get(n)); + get_submesh().technique = &get_collection().get(n); } void Object::LodLoader::technique_inline() { - lod.technique = make_sub().into(get_collection(), format("lod%d.tech", index)).load(); + get_submesh().technique = make_sub().into(get_collection(), format("lod%d.tech", index)).load(); +} + + +Object::Submesh::Loader::Loader(Submesh &m, unsigned l, unsigned s, Collection &c): + CollectionObjectLoader(m, &c), + lod_index(l), + sub_index(s) +{ + add("mesh", &Submesh::mesh); + add("mesh", &Loader::mesh_inline); + add("technique", &Submesh::technique); + add("technique", &Loader::technique_inline); +} + +void Object::Submesh::Loader::mesh_inline() +{ + obj.mesh = make_sub().into(get_collection(), format("lod%d_%d.mesh", lod_index, sub_index)).load(); +} + +void Object::Submesh::Loader::technique_inline() +{ + obj.technique = make_sub().into(get_collection(), format("lod%d_%d.tech", lod_index, sub_index)).load(); } } // namespace GL diff --git a/source/render/object.h b/source/render/object.h index f4fa2384..4dd49631 100644 --- a/source/render/object.h +++ b/source/render/object.h @@ -1,6 +1,7 @@ #ifndef MSP_GL_OBJECT_H_ #define MSP_GL_OBJECT_H_ +#include #include #include "mspgl_api.h" #include "renderable.h" @@ -15,16 +16,17 @@ class ObjectInstance; class Technique; /** -Combines a Mesh with a Technique for a complete model. +Combines a Mesh with a Technique for a complete model. Multiple submeshes are +possible, each with its own Technique. An object does not have a model matrix and will be rendered at origin if used by itself. The ObjectInstance class provides a way to position objects in a scene and customize them in other ways. Objects can have multiple levels of detail, with different resources. The most -detailed level has index 0, with increasing indices having less detail. When -rendering an instance, the instance's get_level_of_detail method is called to -determine which LoD to use. +detailed level has index 0, with increasing indices expected to have less +detail. When rendering an instance, the instance's get_level_of_detail method +is called to determine which LoD to use. An Object can be rendered with any tag its Technique supports. Unknown tags are silently ignored. @@ -32,20 +34,22 @@ are silently ignored. class MSPGL_API Object: public Renderable, private ResourceObserver { private: - struct LevelOfDetail; + struct Submesh; class LodLoader: public DataFile::CollectionObjectLoader { private: unsigned index; - LevelOfDetail &lod; public: - LodLoader(Object &, unsigned, Collection &); + LodLoader(Object &, Collection &); private: + Submesh &get_submesh() const; + void mesh(const std::string &); void mesh_inline(); + void submesh(); void technique(const std::string &); void technique_inline(); }; @@ -59,57 +63,72 @@ public: void finish() override; void bounding_sphere_hint(float, float, float, float); - void level_of_detail(unsigned); + void level_of_detail(); }; + static constexpr unsigned MAX_LODS = 6; + private: - struct LevelOfDetail + struct Submesh { + class Loader: public DataFile::CollectionObjectLoader + { + private: + unsigned lod_index; + unsigned sub_index; + + public: + Loader(Submesh &, unsigned, unsigned, Collection &); + + private: + void mesh_inline(); + void technique_inline(); + }; + const Mesh *mesh; const Technique *technique; }; - std::vector lods; + std::vector submeshes; + uint8_t n_lods = 1; + std::array lod_bounds = { 0, 0 }; Geometry::BoundingSphere bounding_sphere; - bool lod0_watched = false; + bool sub0_watched = false; static const Matrix identity_matrix; public: - Object(); + Object() = default; Object(const Mesh *, const Technique *); Object(const Object &); Object(Object &&); ~Object() override; private: - LevelOfDetail &get_lod(unsigned, const char *); - void watch_lod0(); + void watch_sub0(); public: - /** Sets the mesh for the highest level of detail (index 0). */ - void set_mesh(const Mesh *m) { set_mesh(0, m); } + /** Sets the mesh for the first submesh of the highest level of detail. */ + void set_mesh(const Mesh *); + + /** Adds a new level of detail to the object. The previous level of detail + must have at least one submesh. */ + void add_lod(const Mesh *, const Technique *); - /** Sets the mesh for a specific level of detail. LoDs must be defined in - order, without gaps. If this call creates a new LoD, technique is copied - from the previous one. */ - void set_mesh(unsigned, const Mesh *); + /** Adds a new submesh to the last level of detail of the object. */ + void add_submesh(const Mesh *, const Technique *); private: void update_bounding_sphere(); -public: - const Mesh *get_mesh(unsigned = 0) const; - /** Sets the technique for the highest level of detail (index 0). */ - void set_technique(const Technique *t) { set_technique(0, t); } +public: + const Mesh *get_mesh(unsigned = 0, unsigned = 0) const; - /** Sets the technique for a specific level of detail. LoDs must be defined - in order, without gaps. If this call creates a new LoD, mesh is copied from - the previous one. */ - void set_technique(unsigned, const Technique *); + /** Sets the technique for the first submesh of the highest level of detail. */ + void set_technique(const Technique *); - const Technique *get_technique(unsigned = 0) const; - unsigned get_n_lods() const { return lods.size(); } + const Technique *get_technique(unsigned = 0, unsigned = 0) const; + unsigned get_n_lods() const { return n_lods; } const Matrix *get_matrix() const override { return &identity_matrix; } const Geometry::BoundingSphere *get_bounding_sphere() const override { return &bounding_sphere; } -- 2.45.2