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;
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
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);
void Object::Loader::finish()
{
+ obj.watch_sub0();
obj.update_bounding_sphere();
}
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
#ifndef MSP_GL_OBJECT_H_
#define MSP_GL_OBJECT_H_
+#include <array>
#include <vector>
#include "mspgl_api.h"
#include "renderable.h"
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.
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();
};
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; }