/* QGLView Copyright (C) 2020 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 . */ #define GL_GLEXT_PROTOTYPES #include #include "glmesh.h" #include "globject.h" #include using namespace QGLEngineShaders; static int _count = 0; Mesh::Mesh(GLenum geom_type_): geom_type(geom_type_), buffer_geom(GL_ARRAY_BUFFER, GL_STATIC_DRAW), buffer_ind (GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW) { hash_ = 0; changed = hash_changed = true; //qDebug() << "Mesh, now" << ++_count; } Mesh::~Mesh() { //qDebug() << "~Mesh, now" << --_count; //destroy(); } Mesh * Mesh::clone() { Mesh * c = new Mesh(); c->vertices_ = vertices_ ; c->normals_ = normals_ ; c->texcoords_ = texcoords_; c->triangles_ = triangles_; c->lines_ = lines_; c->geom_type = geom_type; c->hash_ = hash_; c->hash_changed = hash_changed; //qDebug() << "clone VBO"; return c; } void Mesh::init(QOpenGLExtraFunctions * f) { if (!isInit()) { buffer_geom.init(f); buffer_ind .init(f); changed = true; } } void Mesh::destroy(QOpenGLExtraFunctions * f) { buffer_geom.destroy(f); buffer_ind .destroy(f); QList vaol = vao_map.values(); foreach (VertexObject* vao, vaol) vao->destroy(f); qDeleteAll(vao_map); vao_map.clear(); } void Mesh::calculateNormals() { normals_.resize(vertices_.size()); QVector3D dv1, dv2, n; foreach (const Vector3i & t, triangles_) { QVector3D & v0(vertices_[t.p0]); QVector3D & v1(vertices_[t.p1]); QVector3D & v2(vertices_[t.p2]); dv1 = v1 - v0, dv2 = v2 - v0; n = QVector3D::crossProduct(dv1, dv2).normalized(); normals_[t.p0] = n; normals_[t.p1] = n; normals_[t.p2] = n; } } void Mesh::calculateTangents() { if (vertices_.isEmpty() || texcoords_.isEmpty()) return; if (texcoords_.size() != vertices_.size()) return; tangents_ .resize(vertices_.size()); bitangents_.resize(vertices_.size()); //qDebug() << "calculateBinormals" << vcnt << tcnt << vertices_.size() << texcoords_.size() << "..."; QVector3D dv1, dv2; QVector2D dt1, dt2; QVector3D tan, bitan; foreach (const Vector3i & t, triangles_) { QVector3D & v0(vertices_ [t.p0]); QVector3D & v1(vertices_ [t.p1]); QVector3D & v2(vertices_ [t.p2]); QVector2D & t0(texcoords_[t.p0]); QVector2D & t1(texcoords_[t.p1]); QVector2D & t2(texcoords_[t.p2]); dv1 = v1 - v0, dv2 = v2 - v0; dt1 = t1 - t0, dt2 = t2 - t0; tan = (dv1 * dt2.y() - dv2 * dt1.y()).normalized(); bitan = (dv2 * dt1.x() - dv1 * dt2.x()).normalized(); tangents_ [t.p0] = tan; tangents_ [t.p1] = tan; tangents_ [t.p2] = tan; bitangents_[t.p0] = bitan; bitangents_[t.p1] = bitan; bitangents_[t.p2] = bitan; //qDebug() << " t" << t << vi << ti << dv1.toQVector3D() << "..."; } //qDebug() << "calculateBinormals" << vcnt << tcnt << tangents_.size(); } VertexObject * Mesh::vaoByType(int type) { VertexObject *& vao(vao_map[type]); if (!vao) { vao = new VertexObject(); } return vao; } bool Mesh::rebuffer(QOpenGLExtraFunctions * f) { changed = false; if (vertices_.isEmpty()) return true; if (normals_.isEmpty()) calculateNormals(); calculateTangents(); vert_count = qMin(vertices_.size(), normals_.size()); vert_count = qMin(vert_count, tangents_.size()); vert_count = qMin(vert_count, bitangents_.size()); vert_count = qMin(vert_count, texcoords_.size()); data_.resize(vert_count); for (int i = 0; i < vert_count; ++i) { Vertex & v(data_[i]); v.pos = vertices_ [i]; v.normal = normals_ [i]; v.tangent = tangents_ [i]; v.bitangent = bitangents_[i]; v.tex = texcoords_ [i]; } int gsize = data_.size() * sizeof(Vertex); int tsize = triangles_.size() * sizeof(Vector3i); int lsize = lines_.size() * sizeof(Vector2i); buffer_geom.bind(f); buffer_geom.resize(f, gsize); buffer_geom.load(f, data_.constData(), gsize); buffer_ind.bind(f); if (geom_type == GL_TRIANGLES) { buffer_ind.resize(f, tsize); buffer_ind.load(f, triangles_.constData(), tsize); } else { buffer_ind.resize(f, lsize); buffer_ind.load(f, lines_.constData(), lsize); } return !isEmpty(); } void Mesh::draw(QOpenGLExtraFunctions * f, int count, int type) { if (isEmpty()) return; if (!isInit()) init(f); if (changed) rebuffer(f); //qDebug() << "draw" << geom_type << vert_count << count; VertexObject * vao = vaoByType(type); vao->bindBuffers(f, buffer_geom, buffer_ind); if (geom_type == GL_TRIANGLES) vao->draw(f, geom_type, triangles_.size() * 3, count); else vao->draw(f, geom_type, lines_.size() * 2, count); } void Mesh::clear() { vertices_ .clear(); normals_ .clear(); tangents_ .clear(); bitangents_.clear(); texcoords_ .clear(); triangles_ .clear(); lines_ .clear(); data_ .clear(); changed = hash_changed = true; } void Mesh::loadObject(QOpenGLExtraFunctions * f, const Object & object, int type) { VertexObject * vao = vaoByType(type); vao->loadObject(f, object); } void Mesh::loadObjects(QOpenGLExtraFunctions * f, const QVector & objects, int type) { VertexObject * vao = vaoByType(type); vao->loadObjects(f, objects); } void Mesh::loadSelections(QOpenGLExtraFunctions * f, const QVector & sels, int type) { VertexObject * vao = vaoByType(type); vao->loadSelections(f, sels); } uint Mesh::hash() const { if (hash_changed) { hash_changed = false; hash_ = qHashBits(vertices_ .constData(), vertices_ .size() * sizeof(QVector3D)); hash_ ^= qHashBits(normals_ .constData(), normals_ .size() * sizeof(QVector3D)); hash_ ^= qHashBits(texcoords_.constData(), texcoords_.size() * sizeof(QVector2D)); hash_ ^= qHashBits(triangles_.constData(), triangles_.size() * sizeof( Vector3i)); hash_ ^= qHashBits(lines_ .constData(), lines_ .size() * sizeof( Vector2i)); } return hash_; } bool Mesh::isObjectsChanged(int type) const { return (const_cast(this))->vaoByType(type)->isObjectsChanged(); } bool Mesh::isSelectionChanged(int type) const { return (const_cast(this))->vaoByType(type)->isSelectionChanged(); } void Mesh::setObjectsChanged(int type, bool yes) { vaoByType(type)->setObjectsChanged(yes); } void Mesh::setSelectionChanged(int type, bool yes) { vaoByType(type)->setSelectionChanged(yes); } void Mesh::setAllObjectsChanged(bool yes) { QMapIterator it(vao_map); while (it.hasNext()) it.next().value()->setObjectsChanged(yes); } void Mesh::setAllSelectionChanged(bool yes) { QMapIterator it(vao_map); while (it.hasNext()) it.next().value()->setSelectionChanged(yes); } void Mesh::translatePoints(const QVector3D & dp) { QMatrix4x4 m; m.translate(dp); transformPoints(m); } void Mesh::scalePoints(const QVector3D & dp) { QMatrix4x4 m; m.scale(dp); transformPoints(m); } void Mesh::rotatePoints(const double & angle, const QVector3D & a) { QMatrix4x4 m; m.rotate(angle, a); transformPoints(m); } void Mesh::transformPoints(const QMatrix4x4 & mat) { if (vertices_.isEmpty()) return; int vcnt = vertices_.size(), ncnt = normals_.size(); for (int i = 0; i < vcnt; ++i) { vertices_[i] = (mat * QVector4D(vertices_[i], 1)).toVector3D(); if (i < ncnt) normals_[i] = (mat * QVector4D(normals_[i], 0)).toVector3D(); } changed = hash_changed = true; } void Mesh::flipNormals() { if (vertices_.isEmpty()) return; for (int i = 0; i < triangles_.size(); ++i) piSwap(triangles_[i].p1, triangles_[i].p2); for (int i = 0; i < lines_.size(); ++i) piSwap(lines_[i].p0, lines_[i].p1); int ncnt = normals_.size(); for (int i = 0; i < ncnt; ++i) normals_[i] = -normals_[i]; changed = hash_changed = true; } void Mesh::append(const Mesh * m) { if (!m) return; if (m->isEmpty()) return; if (normals_.isEmpty()) calculateNormals(); int vcnt = vertices_.size(); vertices_ .append(m->vertices_ ); normals_ .append(m->normals_ ); texcoords_.append(m->texcoords_); QVector tri = m->triangles_; for (int i = 0; i < tri.size(); ++i) tri[i] += vcnt; triangles_.append(tri); QVector lin = m->lines_; for (int i = 0; i < lin.size(); ++i) lin[i] += vcnt; lines_.append(lin); } bool Mesh::saveToFile(const QString & filename) { if (filename.isEmpty()) return false; QFile f(filename); QByteArray ba; if (f.open(QFile::WriteOnly)) { QDataStream out(&ba, QFile::WriteOnly); out << vertices_ << normals_ << texcoords_ << triangles_ << lines_; ba = qCompress(ba); f.resize(0); f.write(ba); f.close(); return true; } return false; } bool Mesh::loadFromFile(const QString & filename) { if (filename.isEmpty()) return false; QFile f(filename); QByteArray ba; if (f.open(QFile::ReadOnly)) { ba = f.readAll(); if (ba.isEmpty()) return false; ba = qUncompress(ba); QDataStream in(ba); in >> vertices_ >> normals_ >> texcoords_ >> triangles_ >> lines_; changed = hash_changed = true; f.close(); return !isEmpty(); } return false; } Box3D Mesh::boundingBox() const { if (vertices_.isEmpty()) return Box3D(); int vcnt = vertices_.size(); //qDebug() << "calculateBinormals" << vcnt << tcnt << vertices_.size() << texcoords_.size() << "..."; GLfloat mix, miy, miz, max, may, maz; QVector3D v0(vertices_[0]); mix = max = v0.x(); miy = may = v0.y(); miz = maz = v0.z(); Box3D bound; for (int i = 1; i < vcnt; ++i) { const QVector3D & v(vertices_[i]); if (mix > v.x()) mix = v.x(); if (max < v.x()) max = v.x(); if (miy > v.y()) miy = v.y(); if (may < v.y()) may = v.y(); if (miz > v.z()) miz = v.z(); if (maz < v.z()) maz = v.z(); } bound.x = mix; bound.y = miy; bound.z = miz; bound.length = max - mix; bound.width = may - miy; bound.height = maz - miz; return bound; } QDataStream & operator <<(QDataStream & s, const Mesh * m) { ChunkStream cs; //qDebug() << "place VBO" << m.vertices_.size() << m.normals_.size() << m.texcoords_.size() << m.colors_.size() << "..."; cs.add(1, m->vertices_).add(2, m->normals_).add(3, m->texcoords_) .add(6, m->triangles_).add(7, m->lines_).add(10, int(m->geom_type)); //qDebug() << "place VBO done" << cs.data().size() << "..."; s << /*qCompress*/(cs.data()); return s; } QDataStream & operator >>(QDataStream & s, Mesh *& m) { m = new Mesh(); //QByteArray ba; //s >> ba; //ba = qUncompress(ba); ChunkStream cs(s); while (!cs.atEnd()) { switch (cs.read()) { case 1 : cs.get(m->vertices_ ); break; case 2 : cs.get(m->normals_ ); break; case 3 : cs.get(m->texcoords_); break; case 6 : cs.get(m->triangles_); break; case 7 : cs.get(m->lines_ ); break; case 10: m->geom_type = cs.getData(); break; } } m->changed = true; return s; }