Files
qglengine/core/glmesh.cpp
2022-12-14 14:14:44 +03:00

434 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 "glmesh.h"
#include "globject.h"
#include <QOpenGLExtraFunctions>
#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;
}
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::reinit() {
buffer_geom.reinit();
buffer_ind.reinit();
QMapIterator<int, VertexObject *> it(vao_map);
while (it.hasNext())
it.next().value()->reinit();
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 << cs.data();
return s;
}
QDataStream & operator>>(QDataStream & s, Mesh *& m) {
m = new Mesh();
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;
}