499 lines
11 KiB
C++
499 lines
11 KiB
C++
/*
|
|
GLObjectBase & Light
|
|
Copyright (C) 2019 Ivan Pelipenko peri4ko@yandex.ru
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "glscene.h"
|
|
#include "glcamera.h"
|
|
#include "glmesh.h"
|
|
#include "qglview.h"
|
|
#include <chunkstream.h>
|
|
|
|
|
|
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;
|
|
destroy();
|
|
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) {
|
|
destroy();
|
|
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::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<QString> & 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);
|
|
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<Mesh*> 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<Material*> 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->selected_ = o->selected_aim = 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];
|
|
}
|
|
|
|
|
|
const Box3D & Scene::boundingBox() const {
|
|
root_->calculateBoundingBox();
|
|
return root_->boundingBox();
|
|
}
|
|
|
|
|
|
Material * Scene::newMaterial() {
|
|
materials << new Material();
|
|
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<QString> 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->selected_)
|
|
selected_ << o;
|
|
}
|
|
|
|
|
|
void Scene::attachObject(ObjectBase * o) {
|
|
if (!o) return;
|
|
o->setScene(this);
|
|
if (o->mesh()) { // search suitable mesh in this scene
|
|
uint ohash = o->mesh()->hash();
|
|
bool need_new = true;
|
|
foreach (Mesh * m, geometries) {
|
|
if (m == o->mesh()) { // already exists by ptr
|
|
need_new = false;
|
|
setObjectMeshChanged(o);
|
|
break;
|
|
}
|
|
if (m->hash() == ohash) { // already exists by hash
|
|
need_new = false;
|
|
o->setMesh(m);
|
|
break;
|
|
}
|
|
}
|
|
if (need_new) { // need to clone mesh and add to scene
|
|
Mesh * nmesh = o->mesh()->clone();
|
|
o->setMesh(nmesh);
|
|
geometries << nmesh;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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<Light * >(co);
|
|
lights_used[l->light_type] << l;
|
|
} break;
|
|
case ObjectBase::glMesh:
|
|
if (co->mesh()) {
|
|
geometries_used[co->pass()][co->mesh()] << co;
|
|
co->setObjectsChanged();
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
prepareTree(co);
|
|
}
|
|
}
|
|
|
|
|
|
bool Scene::prepare() {
|
|
changed_materials.clear();
|
|
foreach (Material * m, materials) {
|
|
if (m->_changed) {
|
|
need_reload_materials = true;
|
|
changed_materials << m;
|
|
}
|
|
}
|
|
|
|
ObjectBaseList aol;
|
|
if (!tree_changed && !mat_changed) return false;
|
|
aol = root_->children(true);
|
|
if (tree_changed) {
|
|
tree_changed = false;
|
|
lights_changed = true;
|
|
if (tree_struct_changed) {
|
|
tree_struct_changed = false;
|
|
QMetaObject::invokeMethod(this, "treeChanged", Qt::QueuedConnection);
|
|
}
|
|
geometries_used[rpSolid ].clear();
|
|
geometries_used[rpTransparent].clear();
|
|
lights_used.clear();
|
|
prepareTree(root_);
|
|
}
|
|
mat_changed = false;
|
|
return true;
|
|
}
|
|
|
|
|
|
void Scene::destroy() {
|
|
selected_.clear();
|
|
selected_top.clear();
|
|
emitSelectionChanged();
|
|
root_->clearChildren(true);
|
|
qDeleteAll(geometries);
|
|
qDeleteAll(materials);
|
|
geometries.clear();
|
|
materials.clear();
|
|
emit __destroyed();
|
|
emit treeChanged();
|
|
}
|
|
|
|
|
|
QDataStream & operator <<(QDataStream & s, const Scene * p) {
|
|
ChunkStream cs;
|
|
//qDebug() << "place" << p->name() << "...";
|
|
QVector<short> 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());
|
|
//s << cs.data();
|
|
return s;
|
|
}
|
|
QDataStream & operator >>(QDataStream & s, Scene *& p) {
|
|
p = new Scene();
|
|
//ChunkStream cs(s);
|
|
|
|
QByteArray ba;
|
|
s >> ba;
|
|
ba = qUncompress(ba);
|
|
ChunkStream cs(ba);
|
|
|
|
QVector<short> 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;
|
|
}
|