/* QGL Scene Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "glscene.h" #include "glcamera.h" #include "glmesh.h" #include "qglview.h" #include Scene::Scene() { root_ = new ObjectBase(); root_->setScene(this); tree_changed = mat_changed = lights_changed = true; destroying = false; need_reload_materials = tree_struct_changed = true; sel_mode_ = smSingleSelection; } Scene::~Scene() { destroying = true; clear(); delete root_; } Scene * Scene::clone() { Scene * ret = new Scene(); ObjectBase * o = root_->clone(); foreach(ObjectBase * co, o->children()) ret->addObject(co); o->clearChildren(); delete o; return ret; } void Scene::addObject(ObjectBase * o) { ObjectBaseList aol = o->children(true); attachObject(o); foreach(ObjectBase * c, aol) attachObject(c); root_->addChild(o); tree_changed = tree_struct_changed = true; } void Scene::addScene(const Scene * s) { if (!s) return; // qDebug() << "addScene clone ..."; ObjectBase * o = s->root_->clone(); o->setName(s->name()); // qDebug() << "addScene clone ok" << o << o->children(true).size(); addObject(o); makeMaterialsUniqueNames(); // qDebug() << "addScene add ok" << o; } void Scene::assignFrom(const Scene * s) { clear(); if (!s) return; setName(s->name()); foreach(Material * m, s->materials) { Material * nm = new Material(); *nm = *m; nm->_changed = true; nm->setMapsChanged(); materials << nm; } for (int i = 0; i < s->root_->childCount(); ++i) { addObject(s->root_->child(i)->clone()); // qDebug() << i << o->child(i)->pos(); } tree_changed = mat_changed = lights_changed = need_reload_materials = tree_struct_changed = true; } void Scene::clear() { selected_.clear(); selected_top.clear(); emitSelectionChanged(); root_->clearChildren(true); td_geometries << geometries; qDeleteAll(materials); geometries.clear(); materials.clear(); emit __destroyed(); emit treeChanged(); } void Scene::reinitAll() { for (auto * i: geometries) i->reinit(); for (auto * i: td_geometries) i->reinit(); setTreeChanged(); mat_changed = lights_changed = true; need_reload_materials = true; tree_struct_changed = true; } void Scene::objectsCountInternal(int * cnt, ObjectBase * where) { ++(*cnt); foreach(ObjectBase * i, where->children()) objectsCountInternal(cnt, i); } int Scene::objectsCount(bool all) { if (!all) return root_->childCount(); int cnt = 0; objectsCountInternal(&cnt, root_); return cnt; } void Scene::removeObjectInternal(ObjectBase * o, ObjectBase * where) { if (destroying) return; foreach(ObjectBase * i, where->children()) { if (o == i) { where->removeChild(i); setObjectMeshChanged(i); } else removeObjectInternal(o, i); } } void Scene::emitSelectionChanged() { selected_top.clear(); foreach(ObjectBase * o, selected_) { ObjectBase * po = o->selectedParent(); if (!po) po = o; if (!selected_top.contains(po)) selected_top << po; } foreach(Mesh * m, geometries) m->setAllSelectionChanged(true); selectionChanged(); } QString Scene::uniqueName(QString n, const QSet & names) { if (!names.contains(n)) return n; QString num; while (!n.isEmpty()) { if (n.right(1)[0].isDigit()) { num.push_front(n.right(1)); n.chop(1); } else break; } if (!n.endsWith('_')) n += '_'; int in = num.toInt() + 1; QString nn = n + QString::number(in).rightJustified(3, '0'); while (names.contains(nn)) nn = n + QString::number(++in).rightJustified(3, '0'); return nn; } void Scene::removeObject(ObjectBase * o, bool inChildren) { if (destroying) return; o->setScene(nullptr); setObjectMeshChanged(o); if (inChildren) removeObjectInternal(o, root_); else root_->removeChild(o); __objectDeleted(o); setTreeStructChanged(); } void Scene::removeObject(ObjectBase & o, bool inChildren) { if (destroying) return; removeObject(&o, inChildren); } void Scene::clearObjects(bool deleteAll) { root_->clearChildren(deleteAll); cleanUnused(); setTreeStructChanged(); emitSelectionChanged(); } void Scene::selectObject(ObjectBase * o, bool add_to_selection) { // qDebug() << "selectObject" << o << add_to_selection; if (!add_to_selection || (sel_mode_ == smSingleSelection)) clearSelection(); if (o) { if (!add_to_selection) o->setSelected(true); else o->setSelected(!o->isSelected()); gatherSelection(); } emitSelectionChanged(); } void Scene::selectObjects(ObjectBaseList ol, bool add_to_selection) { if (!add_to_selection || (sel_mode_ == smSingleSelection)) clearSelection(); foreach(ObjectBase * o, ol) { if (!o) continue; o->setSelected(true); } gatherSelection(); emitSelectionChanged(); } void Scene::selectObjectsByMesh() { ObjectBaseList csl = selected_; QSet sml; foreach(ObjectBase * o, csl) if (o->mesh()) sml << o->mesh(); ObjectBaseList ol = root_->children(true); foreach(ObjectBase * o, ol) { if (sml.contains(o->mesh())) o->setSelected(true); } gatherSelection(); emitSelectionChanged(); } void Scene::selectObjectsByMaterial() { ObjectBaseList csl = selected_; QSet sml; foreach(ObjectBase * o, csl) if (o->material()) sml << o->material(); ObjectBaseList ol = root_->children(true); foreach(ObjectBase * o, ol) { if (sml.contains(o->material())) o->setSelected(true); } gatherSelection(); emitSelectionChanged(); } void Scene::clearSelection() { selected_.clear(); selected_top.clear(); ObjectBaseList ol = root_->children(true); foreach(ObjectBase * o, ol) { o->setSelected(false); o->setAimSelected(false); } emitSelectionChanged(); } ObjectBaseList Scene::selectedObjects(bool top_only) const { return top_only ? selected_top : selected_; } ObjectBase * Scene::selectedObject() const { if (selected_.isEmpty()) return 0; return selected_[0]; } void gatherMeshes(ObjectBase * o, QSet & ums) { if (o->mesh()) ums << o->mesh(); for (int i = 0; i < o->childCount(); ++i) gatherMeshes(o->child(i), ums); } void Scene::cleanUnused() { QSet ums; gatherMeshes(root_, ums); for (int i = 0; i < geometries.size(); ++i) { if (ums.contains(geometries[i])) continue; td_geometries << geometries[i]; geometries.removeAt(i); --i; } } const Box3D & Scene::boundingBox() const { root_->calculateBoundingBox(); return root_->boundingBox(); } Material * Scene::newMaterial(const QString & name) { materials << new Material(name); makeMaterialsUniqueNames(); mat_changed = true; return materials.back(); } void Scene::removeMaterial(Material * m) { if (!m || !materials.contains(m)) return; ObjectBaseList ol = root_->children(true); foreach(ObjectBase * o, ol) if (o->material_ == m) o->setMaterial(0); materials.removeAll(m); changed_materials.removeAll(m); mat_changed = true; } void Scene::makeMaterialsUniqueNames() { QSet names; foreach(Material * m, materials) { if (m->name.isEmpty()) m->name = "default_000"; m->name = uniqueName(m->name, names); names << m->name; } } ObjectBaseList Scene::objects(bool all) { return root_->children(all); } void Scene::removeLight(Light * l) { removeObject(l); } void Scene::dump() { qDebug() << "Scene" << name(); qDebug() << "Meshes:" << geometries.size(); qDebug() << "Objects:" << root_->children(true).size(); } void Scene::gatherSelection() { selected_.clear(); ObjectBaseList ol = root_->children(true); foreach(ObjectBase * o, ol) if (o->isSelected()) selected_ << o; } void Scene::attachObject(ObjectBase * o) { if (!o) return; o->setScene(this); if (o->mesh()) { // search suitable mesh in this scene o->setMesh(attachMesh(o->mesh())); setObjectMeshChanged(o); } if (o->material()) { // search suitable material in this scene uint ohash = o->material()->hash(); bool need_new = true; foreach(Material * m, materials) { if (m == o->material()) { // already exists by ptr need_new = false; break; } if (m->hash() == ohash) { // already exists by hash need_new = false; o->setMaterial(m); break; } } if (need_new) { // need to clone material and add to scene Material * nmat = new Material(); *nmat = *(o->material()); nmat->setMapsChanged(); o->setMaterial(nmat); materials << nmat; } } setTreeStructChanged(); } Mesh * Scene::attachMesh(Mesh * mesh) { if (!mesh) return 0; uint mhash = mesh->hash(); foreach(Mesh * m, geometries) { if (m == mesh) { // already exists by ptr return m; } if (m->hash() == mhash) { // already exists by hash return m; } } // need to clone mesh and add to scene Mesh * nmesh = mesh->clone(); geometries << nmesh; return nmesh; } void Scene::setTreeChanged() { if (destroying) return; tree_changed = true; foreach(Mesh * m, geometries) { m->setAllObjectsChanged(true); m->setAllSelectionChanged(true); } gatherSelection(); } void Scene::setTreeStructChanged() { tree_struct_changed = true; } void Scene::setObjectMeshChanged(ObjectBase * o) { if (o) o->setObjectsChanged(); } void Scene::prepareTree(ObjectBase * o) { foreach(ObjectBase * co, o->children_) { if (co->isHidden()) continue; switch (co->type_) { case ObjectBase::glLight: { Light * l = globject_cast(co); lights_used[l->light_type] << l; } break; case ObjectBase::glMesh: if (co->mesh()) { geometries_used[co->pass()][co->mesh()] << co; co->setObjectsChanged(); } break; case ObjectBase::glCamera: cameras_used << globject_cast(co); break; default: break; } prepareTree(co); } } bool Scene::prepare() { changed_materials.clear(); foreach(Material * m, materials) { if (m->_changed) { need_reload_materials = tree_changed = true; changed_materials << m; } } ObjectBaseList aol; if (!tree_changed && !mat_changed) return false; aol = root_->children(true); if (tree_changed) { geometries_used[rpSolid].clear(); geometries_used[rpTransparent].clear(); lights_used.clear(); cameras_used.clear(); prepareTree(root_); if (tree_struct_changed) { tree_struct_changed = false; cleanUnused(); QMetaObject::invokeMethod(this, "treeChanged", Qt::QueuedConnection); } tree_changed = false; lights_changed = true; } mat_changed = false; return true; } void Scene::destroy(QOpenGLExtraFunctions * f) { foreach(Mesh * g, geometries) g->destroy(f); } void Scene::destroyUnused(QOpenGLExtraFunctions * f) { foreach(Mesh * i, td_geometries) i->destroy(f); qDeleteAll(td_geometries); td_geometries.clear(); } QDataStream & operator<<(QDataStream & s, const Scene * p) { ChunkStream cs; // qDebug() << "place" << p->name() << "..."; QVector geom_ind, mat_ind; ObjectBaseList cl = p->root_->children(true); geom_ind.reserve(cl.size()); mat_ind.reserve(cl.size()); foreach(ObjectBase * c, cl) { geom_ind << p->geometries.indexOf(c->mesh()); mat_ind << p->materials.indexOf(c->material()); } cs.add(1, p->name_).add(10, p->geometries).add(11, p->materials).add(20, p->root_).add(21, geom_ind).add(22, mat_ind); s << qCompress(cs.data()); return s; } QDataStream & operator>>(QDataStream & s, Scene *& p) { p = new Scene(); QByteArray ba; s >> ba; ba = qUncompress(ba); ChunkStream cs(ba); QVector geom_ind, mat_ind; while (!cs.atEnd()) { switch (cs.read()) { case 1: cs.get(p->name_); break; case 10: cs.get(p->geometries); break; case 11: cs.get(p->materials); break; case 20: cs.get(p->root_); p->root_->setScene(p); break; case 21: cs.get(geom_ind); break; case 22: cs.get(mat_ind); break; } } p->makeMaterialsUniqueNames(); ObjectBaseList cl = p->root_->children(true); int cnt = qMin(qMin(cl.size(), geom_ind.size()), mat_ind.size()); for (int i = 0; i < cnt; ++i) { ObjectBase * c(cl[i]); if (geom_ind[i] >= 0) c->mesh_ = p->geometries[geom_ind[i]]; if (mat_ind[i] >= 0) c->material_ = p->materials[mat_ind[i]]; } return s; }