]> git.tdb.fi Git - libs/gl.git/commitdiff
Redesign Object to support submeshes
authorMikko Rasa <tdb@tdb.fi>
Sun, 7 Jul 2024 15:01:30 +0000 (18:01 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sun, 7 Jul 2024 20:16:39 +0000 (23:16 +0300)
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
source/render/object.h

index 04ccbb6b994007b6057dbbbc08f3f6fcf443ba3a..9581959d8b4044c8e27b68dadf42950715d85c70 100644 (file)
@@ -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<Vector3> 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<float, 3>::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<unsigned>(inst.get_level_of_detail(renderer), lods.size()-1);
+       unsigned lod = min<unsigned>(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]; i<lod_bounds[lod+1]; ++i)
+       {
+               const Mesh *mesh = submeshes[i].mesh;
+               const Technique *tech = submeshes[i].technique;
+               if(!mesh || !tech)
+                       continue;
+
+               const RenderMethod *method = tech->find_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<float, 3>(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<Object>(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<Mesh>(n));
+       get_submesh().mesh = &get_collection().get<Mesh>(n);
 }
 
 void Object::LodLoader::mesh_inline()
 {
-       lod.mesh = make_sub<Mesh>().into(get_collection(), format("lod%d.mesh", index)).load();
+       get_submesh().mesh = make_sub<Mesh>().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<Technique>(n));
+       get_submesh().technique = &get_collection().get<Technique>(n);
 }
 
 void Object::LodLoader::technique_inline()
 {
-       lod.technique = make_sub<Technique>().into(get_collection(), format("lod%d.tech", index)).load();
+       get_submesh().technique = make_sub<Technique>().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<Mesh>().into(get_collection(), format("lod%d_%d.mesh", lod_index, sub_index)).load();
+}
+
+void Object::Submesh::Loader::technique_inline()
+{
+       obj.technique = make_sub<Technique>().into(get_collection(), format("lod%d_%d.tech", lod_index, sub_index)).load();
 }
 
 } // namespace GL
index f4fa23847b394c2b22a4c3727d64493b72768217..4dd49631f93de928bfd20cbe8fbca145c5ebddc6 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef MSP_GL_OBJECT_H_
 #define MSP_GL_OBJECT_H_
 
+#include <array>
 #include <vector>
 #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<Object>
        {
        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<Submesh>
+               {
+               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<LevelOfDetail> lods;
+       std::vector<Submesh> submeshes;
+       uint8_t n_lods = 1;
+       std::array<uint8_t, MAX_LODS+1> lod_bounds = { 0, 0 };
        Geometry::BoundingSphere<float, 3> 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<float, 3> *get_bounding_sphere() const override { return &bounding_sphere; }