/*
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 .
*/
#include "loader_3ds.h"
void Loader3DS::init3DSMesh(GLObjectBase * o, const QVector & smooth) {
QVector & vertices(o->VBO().vertices()), & normals(o->VBO().normals()), & uvs(o->VBO().texcoords());
QVector & points(o->points), & puvws(o->puvws), fnormals;
QVector & faces(o->faces);
Vector3d pos = Vector3d(o->pos());
bool has_uv = !puvws.isEmpty();
Vector3i cf;
Vector3d v0, v1, v2, cn0, cn1, cn2;
fnormals.resize(faces.size());
for (int i = 0; i < points.size(); ++i)
points[i] -= pos;
for (int i = 0; i < fnormals.size(); ++i) {
cf = faces[i];
v0 = points[cf.p0];
v1 = points[cf.p1];
v2 = points[cf.p2];
fnormals[i] = ((v1 - v0) * (v2 - v0)).normalized();
}
int fcnt = faces.size() * 3;
vertices.resize(fcnt * 3);
normals.resize(vertices.size());
if (has_uv) uvs.resize(fcnt * 2);
int ind = 0, induv = 0, ncnt0, ncnt1, ncnt2, csg;
//qDebug() << faces.size();
if (smooth.isEmpty()) {
for (int i = 0; i < faces.size(); ++i) {
cf = faces[i];
cn0 = fnormals[i];
v0 = points[cf.p0];
v1 = points[cf.p1];
v2 = points[cf.p2];
vertices[ind] = v0.x; normals[ind] = cn0.x; ++ind;
vertices[ind] = v0.y; normals[ind] = cn0.y; ++ind;
vertices[ind] = v0.z; normals[ind] = cn0.z; ++ind;
vertices[ind] = v1.x; normals[ind] = cn0.x; ++ind;
vertices[ind] = v1.y; normals[ind] = cn0.y; ++ind;
vertices[ind] = v1.z; normals[ind] = cn0.z; ++ind;
vertices[ind] = v2.x; normals[ind] = cn0.x; ++ind;
vertices[ind] = v2.y; normals[ind] = cn0.y; ++ind;
vertices[ind] = v2.z; normals[ind] = cn0.z; ++ind;
if (has_uv) {
uvs[induv] = puvws[cf.p0].x; ++induv;
uvs[induv] = puvws[cf.p0].y; ++induv;
uvs[induv] = puvws[cf.p1].x; ++induv;
uvs[induv] = puvws[cf.p1].y; ++induv;
uvs[induv] = puvws[cf.p2].x; ++induv;
uvs[induv] = puvws[cf.p2].y; ++induv;
}
}
} else {
for (int i = 0; i < faces.size(); ++i) {
cf = faces[i];
csg = smooth[i];
v0 = points[cf.p0];
v1 = points[cf.p1];
v2 = points[cf.p2];
cn0 = cn1 = cn2 = fnormals[i];
ncnt0 = ncnt1 = ncnt2 = 1;
for (int j = 0; j < faces.size(); ++j) {
if (csg != smooth[j] || j == i) continue;
if (faces[j].p0 == cf.p0 || faces[j].p1 == cf.p0 || faces[j].p2 == cf.p0 ||
points[faces[j].p0] == v0 || points[faces[j].p1] == v0 || points[faces[j].p2] == v0) {
cn0 += fnormals[j];
++ncnt0;
}
if (faces[j].p0 == cf.p1 || faces[j].p1 == cf.p1 || faces[j].p2 == cf.p1 ||
points[faces[j].p0] == v1 || points[faces[j].p1] == v1 || points[faces[j].p2] == v1) {
cn1 += fnormals[j];
++ncnt1;
}
if (faces[j].p0 == cf.p2 || faces[j].p1 == cf.p2 || faces[j].p2 == cf.p2 ||
points[faces[j].p0] == v2 || points[faces[j].p1] == v2 || points[faces[j].p2] == v2) {
cn2 += fnormals[j];
++ncnt2;
}
}
cn0 /= ncnt0;
cn1 /= ncnt1;
cn2 /= ncnt2;
vertices[ind] = v0.x; normals[ind] = cn0.x; ++ind;
vertices[ind] = v0.y; normals[ind] = cn0.y; ++ind;
vertices[ind] = v0.z; normals[ind] = cn0.z; ++ind;
vertices[ind] = v1.x; normals[ind] = cn1.x; ++ind;
vertices[ind] = v1.y; normals[ind] = cn1.y; ++ind;
vertices[ind] = v1.z; normals[ind] = cn1.z; ++ind;
vertices[ind] = v2.x; normals[ind] = cn2.x; ++ind;
vertices[ind] = v2.y; normals[ind] = cn2.y; ++ind;
vertices[ind] = v2.z; normals[ind] = cn2.z; ++ind;
if (has_uv) {
uvs[induv] = puvws[cf.p0].x; ++induv;
uvs[induv] = puvws[cf.p0].y; ++induv;
uvs[induv] = puvws[cf.p1].x; ++induv;
uvs[induv] = puvws[cf.p1].y; ++induv;
uvs[induv] = puvws[cf.p2].x; ++induv;
uvs[induv] = puvws[cf.p2].y; ++induv;
}
}
}
}
Material Loader3DS::materialByName(const QVector & materials, const QString & name) {
foreach (const Material & m, materials)
if (m.name == name)
return m;
return Material();
}
GLObjectBase * loadFrom3DSFile(const QString & filepath, float scale) {
QFile f(filepath);
if (!f.exists()) {
qDebug() << "[Loader 3DS] Error: can`t open \"" + filepath + "\"";
return nullptr;
}
f.open(QIODevice::ReadOnly);
QDataStream stream(&f);
QVector materials;
QVector smooth;
QVector face_mats;
GLObjectBase * root = new GLObjectBase(), * co = nullptr;
Material mat;
Loader3DS::Chunk cc;
Loader3DS::Face face;
Vector3d pos;
QString str;
ushort cnt;
QString name;
QByteArray ba;
int cur_map = 0;
float fl, fl1, matrix[3][3];
uint col;
root->setName(QFileInfo(f).baseName());
while (!stream.atEnd()) {
stream.readRawData((char * )&cc, sizeof(cc));
switch (cc.id) {
case LOADER_3DS_CHUNK_MAIN: /*qDebug() << "main" << cc.size;*/ break;
case LOADER_3DS_CHUNK_OBJECTS: /*qDebug() << " objects" << cc.size;*/ break;
case LOADER_3DS_CHUNK_OBJECT:
if (co != nullptr) {
Loader3DS::init3DSMesh(co, smooth);
root->addChild(co);
}
co = new GLObjectBase();
co->setName(readCharsUntilNull(stream));
smooth.clear();
//qDebug() << " object" << co->name();
break;
case LOADER_3DS_CHUNK_MESH: /*qDebug() << " mesh" << cc.size;*/ break;
case LOADER_3DS_CHUNK_VERTLIST:
stream.readRawData((char * )&cnt, sizeof(ushort));
co->points.resize(cnt);
//qDebug() << " vertices" << cnt;
for (int i = 0; i < cnt; ++i) {
stream.readRawData((char * )&co->points[i].x, sizeof(float));
stream.readRawData((char * )&co->points[i].y, sizeof(float));
stream.readRawData((char * )&co->points[i].z, sizeof(float));
co->points[i] *= scale;
}
break;
case LOADER_3DS_CHUNK_FACELIST:
stream.readRawData((char * )&cnt, sizeof(ushort));
co->faces.resize(cnt);
//qDebug() << " faces" << cnt;
for (int i = 0; i < cnt; ++i) {
stream.readRawData((char * )&face, sizeof(Loader3DS::Face));
co->faces[i].p0 = face.v0;
co->faces[i].p1 = face.v1;
co->faces[i].p2 = face.v2;
}
break;
case LOADER_3DS_CHUNK_FACEMAT:
name = readCharsUntilNull(stream);
stream.readRawData((char * )&cnt, sizeof(ushort));
face_mats.resize(cnt);
for (int i = 0; i < cnt; ++i)
stream.readRawData((char * )&(face_mats[i]), sizeof(ushort));
//qDebug() << " facemat name" << name << cnt;
co->material().name = name;
break;
case LOADER_3DS_CHUNK_MAPLIST:
stream.readRawData((char * )&cnt, sizeof(ushort));
co->puvws.resize(cnt);
//qDebug() << " texcoords" << cnt;
for (int i = 0; i < cnt; ++i) {
stream.readRawData((char * )&co->puvws[i].x, sizeof(float));
stream.readRawData((char * )&co->puvws[i].y, sizeof(float));
}
break;
case LOADER_3DS_CHUNK_SMOOTH:
cnt = co->faces.size();
smooth.resize(cnt);
//qDebug() << " smooth" << cnt;
for (int i = 0; i < cnt; ++i)
stream.readRawData((char * )&smooth[i], sizeof(uint));
break;
case LOADER_3DS_CHUNK_TRMATRIX:
//qDebug() << co->name();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j)
stream.readRawData((char * )&(matrix[i][j]), sizeof(float));
//qDebug() << matrix[i][0] << matrix[i][1] << matrix[i][2];
}
stream.readRawData((char * )&pos, sizeof(Vector3d));
pos *= scale;
//qDebug() << "pos =" << pos;
co->setPos(pos.toQVector3D());
break;
case LOADER_3DS_CHUNK_LIGHT:
//qDebug() << " light" << cc.size;
str = co->name();
delete co;
co = new Light();
co->setName(str);
stream.readRawData((char * )&pos, sizeof(Vector3d));
pos *= scale;
co->setPos(pos.toQVector3D());
break;
case LOADER_3DS_CHUNK_SPOTLIGHT:
stream.readRawData((char * )&pos, sizeof(Vector3d));
pos *= scale;
globject_cast(co)->light_type = Light::Cone;
globject_cast(co)->direction = (pos.toQVector3D() - co->pos()).normalized();
stream.readRawData((char * )&fl1, sizeof(float));
stream.readRawData((char * )&fl, sizeof(float));
globject_cast(co)->angle_start = fl1;
globject_cast(co)->angle_end = fl;
//qDebug() << "spotlight" << globject_cast(co)->direction << globject_cast(co)->angle_spread;
break;
case LOADER_3DS_CHUNK_LIGHT_OFF:
stream.skipRawData(cc.size - 6);
co->hide();
break;
case LOADER_3DS_CHUNK_ATTENUATION_ON:
stream.skipRawData(cc.size - 6);
fl = globject_cast(co)->decay_end;
//fl1 = globject_cast(co)->decay_start;
globject_cast(co)->decay_quadratic = 4.f / fl;
//qDebug() << "decay" << globject_cast(co)->decay_quadratic;
break;
case LOADER_3DS_CHUNK_COLOR_F:
stream.readRawData((char * )&pos, sizeof(Vector3d));
co->setColor(QColor::fromRgbF(pos.x, pos.y, pos.z));
//qDebug() << " color_f" << co->color();
break;
case LOADER_3DS_CHUNK_COLOR_B:
stream.readRawData((char * )&col, 3);
co->setColor(QColor::fromRgb(((uchar * )&col)[0], ((uchar * )&col)[1], ((uchar * )&col)[2]));
//qDebug() << " color_b" << co->color();
break;
case LOADER_3DS_CHUNK_MULTIPLIER:
stream.readRawData((char * )&fl, sizeof(float));
globject_cast(co)->intensity = fl;
//qDebug() << " multiplier" << fl;
break;
case LOADER_3DS_CHUNK_RANGE_START:
stream.readRawData((char * )&fl, sizeof(float));
globject_cast(co)->decay_start = fl;
//qDebug() << " range start" << fl;
break;
case LOADER_3DS_CHUNK_RANGE_END:
stream.readRawData((char * )&fl, sizeof(float));
globject_cast(co)->decay_end = fl;
//qDebug() << " range end" << fl;
break;
case LOADER_3DS_CHUNK_MATERIAL:
//stream.skipRawData(cc.size - 6);
if (!mat.name.isEmpty())
materials << mat;
mat = Material();
break;
case LOADER_3DS_CHUNK_MATERIAL_NAME:
mat.name = readCharsUntilNull(stream);
//qDebug() << "matname" << mat.name;
break;
case LOADER_3DS_CHUNK_AMBIENT_COLOR:
stream.skipRawData(cc.size - 9);
stream.readRawData((char * )&col, 3);
mat.color_self_illumination = QColor::fromRgb(((uchar * )&col)[0], ((uchar * )&col)[1], ((uchar * )&col)[2]);
//qDebug() << "mat diffuse" << mat.color_diffuse;
break;
case LOADER_3DS_CHUNK_DIFFUSE_COLOR:
stream.skipRawData(cc.size - 9);
stream.readRawData((char * )&col, 3);
mat.color_diffuse = QColor::fromRgb(((uchar * )&col)[0], ((uchar * )&col)[1], ((uchar * )&col)[2]);
//qDebug() << "mat diffuse" << mat.color_diffuse;
break;
case LOADER_3DS_CHUNK_SPECULAR_COLOR:
stream.skipRawData(cc.size - 9);
stream.readRawData((char * )&col, 3);
mat.color_specular = QColor::fromRgb(((uchar * )&col)[0], ((uchar * )&col)[1], ((uchar * )&col)[2]);
//qDebug() << "mat diffuse" << mat.color_diffuse;
break;
case LOADER_3DS_CHUNK_TEXTURE_MAP:
cur_map = LOADER_3DS_CHUNK_TEXTURE_MAP;
break;
case LOADER_3DS_CHUNK_BUMP_MAP:
cur_map = LOADER_3DS_CHUNK_BUMP_MAP;
break;
case LOADER_3DS_CHUNK_REFLECTION_MAP:
cur_map = LOADER_3DS_CHUNK_REFLECTION_MAP;
break;
case LOADER_3DS_CHUNK_MAP_FILENAME:
name = readCharsUntilNull(stream);
//qDebug() << " mat map" << QString::number(cur_map, 16) << name;
switch (cur_map) {
case LOADER_3DS_CHUNK_TEXTURE_MAP: mat.map_diffuse.bitmap_path = name; break;
case LOADER_3DS_CHUNK_BUMP_MAP: mat.map_normal.bitmap_path = name; break;
}
break;
default: /*qDebug() << "???" << QString::number(cc.id, 16).rightJustified(4, '0') << cc.size;*/ stream.skipRawData(cc.size - 6);
}
}
if (!mat.name.isEmpty())
materials << mat;
foreach (const Material & m, materials)
qDebug() << m.name;
if (co != nullptr) {
Loader3DS::init3DSMesh(co, smooth);
root->addChild(co);
}
for (int i = 0; i < root->childCount(); ++i)
root->child(i)->material() = Loader3DS::materialByName(materials, root->child(i)->material().name);
qDebug() << "[Loader 3DS] Loaded" << root->childCount() << "objects from" << filepath;
return root;
}