427 lines
11 KiB
C++
427 lines
11 KiB
C++
/*
|
|
QGL Mesh
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define GL_GLEXT_PROTOTYPES
|
|
#include <QOpenGLExtraFunctions>
|
|
#include "glmesh.h"
|
|
#include "globject.h"
|
|
#include <QTime>
|
|
|
|
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<VertexObject*> 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<Object> & objects, int type) {
|
|
VertexObject * vao = vaoByType(type);
|
|
vao->loadObjects(f, objects);
|
|
}
|
|
|
|
|
|
void Mesh::loadSelections(QOpenGLExtraFunctions * f, const QVector<uchar> & 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<Mesh*>(this))->vaoByType(type)->isObjectsChanged();
|
|
}
|
|
|
|
|
|
bool Mesh::isSelectionChanged(int type) const {
|
|
return (const_cast<Mesh*>(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<int, VertexObject * > it(vao_map);
|
|
while (it.hasNext())
|
|
it.next().value()->setObjectsChanged(yes);
|
|
}
|
|
|
|
|
|
void Mesh::setAllSelectionChanged(bool yes) {
|
|
QMapIterator<int, VertexObject * > 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<Vector3i> tri = m->triangles_;
|
|
for (int i = 0; i < tri.size(); ++i)
|
|
tri[i] += vcnt;
|
|
triangles_.append(tri);
|
|
QVector<Vector2i> 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<int>(); break;
|
|
}
|
|
}
|
|
m->changed = true;
|
|
return s;
|
|
}
|