diff --git a/qglengine/CMakeLists.txt b/qglengine/CMakeLists.txt
new file mode 100644
index 0000000..f161de0
--- /dev/null
+++ b/qglengine/CMakeLists.txt
@@ -0,0 +1,65 @@
+project(qglengine)
+cmake_minimum_required(VERSION 2.6)
+find_package(QAD REQUIRED)
+if (NOT Qt5)
+ message(WARNING "Building ${PROJECT_NAME} available only on Qt5!")
+ return()
+endif()
+if (POLICY CMP0017)
+ cmake_policy(SET CMP0017 NEW)
+endif()
+if (IBPROJECT)
+ include(SDKMacros)
+else()
+ option(LIB "System install" 0)
+ option(DEBUG "Build with -g3" 0)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wall")
+ if (DEBUG)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g3")
+ endif()
+endif()
+if (MINGW)
+ find_package(MinGW REQUIRED)
+endif()
+find_package(OpenGL REQUIRED)
+include_directories(${QAD_INCLUDES} {CMAKE_CURRENT_SOURCE_DIR})
+find_qt(Qt5 Core Gui OpenGL Xml)
+qt_sources(SRC)
+qt_sources(FSRC DIR "formats")
+list(APPEND SRC ${FSRC})
+qt_wrap(${SRC} HDRS out_HDR CPPS out_CPP QMS out_QM)
+qt_add_library(qglengine_core SHARED out_CPP)
+qt_target_link_libraries(qglengine_core qad_utils qad_widgets assimp ${OPENGL_LIBRARIES})
+message(STATUS "Building qglengine_core")
+list(APPEND QT_MULTILIB_LIST qglengine_core)
+add_subdirectory(widgets)
+set(QT_MULTILIB_LIST ${QT_MULTILIB_LIST} PARENT_SCOPE)
+if (LIBPROJECT)
+ sdk_install("qglengine" "qglengine_core" "${out_HDR}" "${out_QM}")
+else()
+ if (LIB)
+ if (WIN32)
+ qt_install(FILES ${out_HDR} DESTINATION ${MINGW_INCLUDE}/qglengine)
+ qt_install(TARGETS qglengine_core DESTINATION ${MINGW_LIB})
+ qt_install(TARGETS qglengine_core DESTINATION ${MINGW_BIN})
+ qt_install(TARGETS qglengine_core DESTINATION QtBin)
+ else()
+ qt_install(FILES ${out_HDR} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/qglengine)
+ qt_install(TARGETS qglengine_core DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
+ endif()
+ message(STATUS "Install qglengine_core to system \"${CMAKE_INSTALL_PREFIX}\"")
+ else()
+ qt_install(TARGETS qglengine_core DESTINATION bin)
+ message(STATUS "Install qglengine_core to local \"bin\"")
+ endif()
+endif()
+if (NOT DEFINED ANDROID_PLATFORM)
+ if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/plugin")
+ #add_subdirectory(plugin)
+ endif()
+endif()
+
+qt_sources(test_SRC DIR "qglview_test")
+qt_wrap(${test_SRC} CPPS test_CPP)
+qt_add_executable(qglview_test test_CPP)
+qt_target_link_libraries(qglview_test qglengine_core qglengine_widgets)
diff --git a/qglengine/formats/loader_assimp.cpp b/qglengine/formats/loader_assimp.cpp
new file mode 100644
index 0000000..e43ae31
--- /dev/null
+++ b/qglengine/formats/loader_assimp.cpp
@@ -0,0 +1,133 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#include "loader_assimp.h"
+#include "glscene.h"
+#include "glmesh.h"
+#include "glmaterial.h"
+#include "globject.h"
+#include
+#include
+#include
+#include
+#include
+
+QVector3D fromAiVector3D(const aiVector3D & v) {return QVector3D(v.x, v.y, v.z);}
+ Vector3i fromAiFace (const aiFace & v) {return Vector3i(v.mIndices[0], v.mIndices[1], v.mIndices[2]);}
+QMatrix4x4 fromAiMatrix4D(const aiMatrix4x4 & v) {return QMatrix4x4(v.a1, v.a2, v.a3, v.a4,
+ v.b1, v.b2, v.b3, v.b4,
+ v.c1, v.c2, v.c3, v.c4,
+ v.d1, v.d2, v.d3, v.d4);}
+bool isAiMeshTriangles(const aiMesh * m) {return (m->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) == aiPrimitiveType_TRIANGLE;}
+
+
+Mesh * assimpMesh(const aiMesh * m) {
+ if (!m) return 0;
+ if (!isAiMeshTriangles(m)) return 0;
+ Mesh * ret = new Mesh();
+ int vcnt = m->mNumVertices, tcnt = m->mNumFaces;
+
+ QVector & v(ret->vertices()); v.resize(vcnt);
+ QVector & t(ret->texcoords()); t.resize(vcnt);
+ QVector< Vector3i> & ind(ret->indices());
+
+ for (int i = 0; i < vcnt; ++i)
+ v[i] = fromAiVector3D(m->mVertices[i]);
+
+ if (m->HasNormals()) {
+ QVector & n(ret->normals());
+ n.resize(vcnt);
+ for (int i = 0; i < vcnt; ++i)
+ n[i] = fromAiVector3D(m->mNormals[i]);
+ }
+
+ if (m->HasTextureCoords(0)) {
+ for (int i = 0; i < vcnt; ++i)
+ t[i] = fromAiVector3D(m->mTextureCoords[0][i]).toVector2D();
+ }
+
+ if (m->HasFaces()) {
+ ind.resize(tcnt);
+ for (int i = 0; i < tcnt; ++i)
+ ind[i] = fromAiFace(m->mFaces[i]);
+ } else {
+ tcnt = vcnt / 3;
+ ind.resize(tcnt);
+ int si = 0;
+ for (int i = 0; i < tcnt; ++i) {
+ si = i+i+i;
+ ind[i] = Vector3i(si, si+1, si+2);
+ }
+ }
+
+ //qDebug() << "add mesh" << v.size() << ret->normals().size() << ret->texcoords().size() << ret->indices().size();
+
+ return ret;
+}
+
+
+ObjectBase * assimpObject(const aiNode * n, const QVector & meshes) {
+ if (!n) return 0;
+ ObjectBase * ret = new ObjectBase();
+ ret->setName(n->mName.C_Str());
+ ret->setTransform(fromAiMatrix4D(n->mTransformation));
+ //qDebug() << "add object" << ret << ret->name();
+ for (uint i = 0; i < n->mNumMeshes; ++i) {
+ int mi = n->mMeshes[i];
+ if (meshes[mi]) {
+ ret->setMesh(meshes[mi]);
+ //qDebug() << "set mesh" << mi << ret->mesh();
+ break;
+ }
+ }
+ for (uint i = 0; i < n->mNumChildren; ++i) {
+ ObjectBase * co = assimpObject(n->mChildren[i], meshes);
+ if (co) ret->addChild(co);
+ }
+
+ return ret;
+}
+
+
+Scene * loadScene(const QString & filepath) {
+ if (filepath.isEmpty()) return 0;
+ qDebug() << "[Loader Assimp] Import" << filepath << "...";
+ Assimp::Importer importer;
+ const aiScene * ais = importer.ReadFile(filepath.toUtf8(), aiProcess_Triangulate |
+ aiProcess_SortByPType |
+ aiProcess_GenUVCoords |
+ aiProcess_TransformUVCoords);
+ if (!ais) {
+ qDebug() << "[Loader Assimp] Error: \"" + QString(importer.GetErrorString()) + "\"";
+ return 0;
+ }
+ qDebug() << "[Loader Assimp] Imported" << ais->mNumMeshes << "meshes";
+ QVector meshes;
+ for (uint i = 0; i < ais->mNumMeshes; ++i)
+ meshes << assimpMesh(ais->mMeshes[i]);
+
+ ObjectBase * root = assimpObject(ais->mRootNode, meshes);
+ if (!root) return 0;
+
+ Scene * scene = new Scene();
+ scene->setName(root->name());
+ foreach (ObjectBase * o, root->children())
+ scene->addObject(o);
+
+ return scene;
+}
diff --git a/qglengine/formats/loader_assimp.h b/qglengine/formats/loader_assimp.h
new file mode 100644
index 0000000..43070dc
--- /dev/null
+++ b/qglengine/formats/loader_assimp.h
@@ -0,0 +1,26 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#ifndef LOADER_ASSIMP_H
+#define LOADER_ASSIMP_H
+
+#include "gltypes.h"
+
+Scene * loadScene(const QString & filepath);
+
+#endif // LOADER_ASSIMP_H
diff --git a/qglengine/formats/loader_qgl.cpp b/qglengine/formats/loader_qgl.cpp
new file mode 100644
index 0000000..5804c21
--- /dev/null
+++ b/qglengine/formats/loader_qgl.cpp
@@ -0,0 +1,67 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#include "loader_qgl.h"
+#include "glscene.h"
+#include
+
+
+Scene * loadFromQGLFile(const QString & filepath) {
+ QFile f(filepath);
+ if (!f.exists()) {
+ qDebug() << "[Loader QGL] Error: can`t open \"" + filepath + "\"";
+ return 0;
+ }
+ f.open(QIODevice::ReadOnly);
+ QDataStream s(&f);
+ s.setVersion(QDataStream::Qt_5_0);
+ char sign[4];
+ s.readRawData(sign, 4);
+ if ((sign[0] != 'Q') || (sign[1] != 'G') || (sign[2] != 'L') || (sign[3] != 'E')) {
+ qDebug() << "[Loader QGL] Error: \"" + filepath + "\" is not valid QGLEngine file!";
+ return 0;
+ }
+ ushort version = 0x1;
+ f.peek((char*)&version, 2);
+ if (version != 1) {
+ qDebug() << "[Loader QGL] Error: \"" + filepath + "\" unsupported version!";
+ return 0;
+ }
+ Scene * ret = 0;
+ s.skipRawData(2);
+ s >> ret;
+ //root->buildTransform();
+ qDebug() << "[Loader QGL] Loaded" << ret->objectsCount(true) << "objects from" << filepath;
+ return ret;
+}
+
+
+bool saveToQGLFile(const QString & filepath, const Scene * scene) {
+ QFile f(filepath);
+ if (!f.open(QIODevice::ReadWrite))
+ return false;
+ f.resize(0);
+ QDataStream s(&f);
+ s.setVersion(QDataStream::Qt_5_0);
+ char sign[4] = {'Q', 'G', 'L', 'E'};
+ ushort version = 0x1;
+ s.writeRawData(sign, 4);
+ s.writeRawData((char*)&version, 2);
+ s << scene;
+ return true;
+}
diff --git a/qglengine/formats/loader_qgl.h b/qglengine/formats/loader_qgl.h
new file mode 100644
index 0000000..3d379f8
--- /dev/null
+++ b/qglengine/formats/loader_qgl.h
@@ -0,0 +1,28 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#ifndef LOADER_QGL_H
+#define LOADER_QGL_H
+
+#include "gltypes.h"
+#include
+
+Scene * loadFromQGLFile(const QString & filepath);
+bool saveToQGLFile(const QString & filepath, const Scene * scene);
+
+#endif // LOADER_QGL_H
diff --git a/qglengine/glbuffer.cpp b/qglengine/glbuffer.cpp
new file mode 100644
index 0000000..9f17793
--- /dev/null
+++ b/qglengine/glbuffer.cpp
@@ -0,0 +1,87 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#define GL_GLEXT_PROTOTYPES
+#include
+#include "glbuffer.h"
+
+
+Buffer::Buffer(GLenum target, GLenum _usage) {
+ target_ = target;
+ usage_ = _usage;
+ buffer_ = 0;
+ prev_size = 0;
+}
+
+
+Buffer::~Buffer() {
+}
+
+
+void Buffer::init(QOpenGLExtraFunctions * f) {
+ if (!isInit()) {
+ f->glGenBuffers(1, &buffer_);
+ }
+}
+
+
+void Buffer::destroy(QOpenGLExtraFunctions * f) {
+ if (buffer_ != 0) {
+ f->glDeleteBuffers(1, &buffer_);
+ }
+ buffer_ = 0;
+}
+
+
+void Buffer::bind(QOpenGLExtraFunctions * f) {
+ f->glBindBuffer(target_, buffer_);
+}
+
+
+void Buffer::release(QOpenGLExtraFunctions * f) {
+ f->glBindBuffer(target_, 0);
+}
+
+
+void * Buffer::map(QOpenGLExtraFunctions * f, GLbitfield mode, int size) {
+ if (size < 0) size = prev_size;
+ return f->glMapBufferRange(target_, 0, size, mode);
+}
+
+
+void Buffer::unmap(QOpenGLExtraFunctions * f) {
+ f->glUnmapBuffer(target_);
+}
+
+
+bool Buffer::resize(QOpenGLExtraFunctions * f, int new_size) {
+ if (new_size <= 0) return false;
+ //qDebug() << "check resize buffer" << buffer_ << "bytes" << new_size << ", old =" << prev_size;
+ if (new_size <= prev_size) return false;
+ prev_size = new_size;
+ //qDebug() << "resize buffer " << buffer_ << target_ << "for" << new_size << "bytes";
+ f->glBufferData(target_, new_size, 0, usage_);
+ return true;
+}
+
+
+void Buffer::load(QOpenGLExtraFunctions * f, const void * data, int size, int offset) {
+ if (!data || size <= 0) return;
+ //qDebug() << "load buffer" << buffer_ << "bytes" << size;
+ f->glBufferSubData(target_, offset, size, data);
+}
diff --git a/qglengine/glbuffer.h b/qglengine/glbuffer.h
new file mode 100644
index 0000000..381c021
--- /dev/null
+++ b/qglengine/glbuffer.h
@@ -0,0 +1,56 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#ifndef GLBUFFER_H
+#define GLBUFFER_H
+
+#include "gltypes.h"
+
+
+class Buffer
+{
+ friend class ObjectBase;
+public:
+ Buffer(GLenum target, GLenum usage = GL_DYNAMIC_DRAW);
+ ~Buffer();
+
+ void init (QOpenGLExtraFunctions * f);
+ void destroy (QOpenGLExtraFunctions * f);
+
+ void bind (QOpenGLExtraFunctions * f);
+ void release (QOpenGLExtraFunctions * f);
+ void * map (QOpenGLExtraFunctions * f, GLbitfield mode, int size = -1);
+ void unmap (QOpenGLExtraFunctions * f);
+
+ // returns true if size changed
+ bool resize (QOpenGLExtraFunctions * f, int new_size);
+ void load (QOpenGLExtraFunctions * f, const void * data, int size, int offset = 0);
+
+ GLuint ID() const {return buffer_;}
+ GLenum usage() const {return usage_;}
+ bool isInit() const {return buffer_ != 0;}
+
+private:
+ GLenum target_, usage_;
+ GLuint buffer_;
+ int prev_size;
+
+};
+
+
+#endif // GLBUFFER_H
diff --git a/qglengine/glcamera.cpp b/qglengine/glcamera.cpp
new file mode 100644
index 0000000..7c7f6cb
--- /dev/null
+++ b/qglengine/glcamera.cpp
@@ -0,0 +1,313 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#include "gltypes.h"
+#include "qglview.h"
+
+
+Camera::Camera() {
+ type_ = glCamera;
+ fov_ = 60.;
+ angle_limit_lower_xy = 0.f;
+ angle_limit_upper_xy = 360.f;
+ angles_.setY(270.f);
+ depth_start = 0.1f;
+ depth_end = 1000.f;
+ mirror_x = mirror_y = false;
+}
+
+
+void Camera::anglesFromPoints() {
+ QVector3D dv = aim_ - pos_, tv;
+ tv = QVector3D(dv.x(), dv.y(), 0.);
+ angles_.setZ(atan2f(tv.x(), tv.y()) * rad2deg);
+ angles_.setY(piClamp(atan2f(tv.length(), dv.z()) * rad2deg, angle_limit_lower_xy, angle_limit_upper_xy) + 180.f);
+}
+
+
+void Camera::apply(const GLfloat & aspect) {
+ glMatrixMode(GL_PROJECTION);
+ if (aspect <= 1.f)
+ glScalef(aspect, aspect, 1.f);
+ QMatrix4x4 pm = glMatrixPerspective(fov_, aspect, depth_start, depth_end);
+ //pm.perspective(fov_, aspect, depth_start, depth_end);
+ //qDebug() << pm;// << glMatrixPerspective(fov_, aspect, depth_start, depth_end);
+ //qDebug() << pm;
+ //setGLMatrix(pm);
+ glMatrixMode(GL_MODELVIEW);
+ pm.setToIdentity();
+ pm.translate(0., 0., -distance());
+ pm.rotate(angles_.y(), 1., 0., 0.);
+ pm.rotate(angles_.x(), 0., 1., 0.);
+ pm.rotate(angles_.z(), 0., 0., 1.);
+ //pm.translate(-aim_);
+ if (parent_) {
+ QMatrix4x4 pmat = parent_->worldTransform();
+ offset_ = pmat.column(3).toVector3D();
+ pmat(0, 3) = pmat(1, 3) = pmat(2, 3) = 0.;
+ pmat.translate(aim_);
+ pm *= pmat.inverted();
+ //qDebug() << pmat;
+ }
+ //setGLMatrix(pm);
+ //qDebug() << angles_;
+}
+
+
+QMatrix4x4 Camera::offsetMatrix() const {
+ QMatrix4x4 ret;
+ ret.translate(parent_ ? -offset_ : -aim_);
+ return ret;
+}
+
+/*
+void Camera::localTransform(QMatrix4x4 & m) {
+ return;
+ if (parent_)
+ m *= parent_->worldTransform();
+ QMatrix4x4 ret;
+ //qDebug() << "local camera";
+ ret.translate(0., 0., -distance());
+ ret.rotate(angles_.y(), 1., 0., 0.);
+ ret.rotate(angles_.x(), 0., 1., 0.);
+ ret.rotate(angles_.z(), 0., 0., 1.);
+ //m *= ret.inverted();
+}
+*/
+
+void Camera::assign(const Camera & c) {
+ pos_ = c.pos_;
+ aim_ = c.aim_;
+ fov_ = c.fov_;
+ angles_ = c.angles_;
+ angle_limit_lower_xy = c.angle_limit_lower_xy;
+ angle_limit_upper_xy = c.angle_limit_upper_xy;
+ mirror_x = c.mirror_x;
+ mirror_y = c.mirror_y;
+ depth_start = c.depth_start;
+ depth_end = c.depth_end;
+ buildTransform();
+}
+
+
+ObjectBase * Camera::clone(bool withChildren) {
+ Camera * o = new Camera(*this);
+ //GLObjectBase::clone(withChildren);
+ o->is_init = false;
+ o->name_ = name_;// + "_copy";
+ o->scene_ = nullptr;
+ o->children_.clear();
+ if (withChildren) {
+ for (int i = 0; i < children_.size(); ++i)
+ o->addChild(children_[i]->clone(withChildren));
+ }
+ o->pos_ = pos_;
+ o->aim_ = aim_;
+ o->fov_ = fov_;
+ o->angles_ = angles_;
+ o->angle_limit_lower_xy = angle_limit_lower_xy;
+ o->angle_limit_upper_xy = angle_limit_upper_xy;
+ o->mirror_x = mirror_x;
+ o->mirror_y = mirror_y;
+ o->depth_start = depth_start;
+ o->depth_end = depth_end;
+ o->meta = meta;
+ return o;
+}
+
+
+QMatrix4x4 Camera::viewMatrix() const {
+ QMatrix4x4 ret;
+ ret.translate(0., 0., -distance());
+ ret.rotate(angles_.y(), 1., 0., 0.);
+ ret.rotate(angles_.x(), 0., 1., 0.);
+ ret.rotate(angles_.z(), 0., 0., 1.);
+ //pm.translate(-aim_);
+ if (parent_) {
+ QMatrix4x4 pmat = parent_->worldTransform();
+ offset_ = pmat.column(3).toVector3D();
+ pmat(0, 3) = pmat(1, 3) = pmat(2, 3) = 0.;
+ pmat.translate(aim_);
+ ret *= pmat.inverted();
+ }
+ return ret;
+}
+
+
+QMatrix4x4 Camera::projectionMatrix(double aspect) const {
+ return glMatrixPerspective(fov_, aspect, depth_start, depth_end);
+}
+
+
+void Camera::panZ(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = QVector2D(dv.x(), dv.y()).length();
+ angles_.setZ(angles_.z() + a);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tl, cosf(angles_.z() * deg2rad) * tl, dv.z());
+ aim_ = pos_ + dv;
+ buildTransform();
+}
+
+
+void Camera::panXY(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = dv.length(), tc;
+ angles_.setY(angles_.y() + a);
+ angles_.setY(piClamp(angles_.y(), angle_limit_lower_xy, angle_limit_upper_xy));
+ tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ aim_ = pos_ + dv * tl;
+ buildTransform();
+}
+
+
+void Camera::rotateZ(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = QVector2D(dv.x(), dv.y()).length();
+ angles_.setZ(angles_.z() + a);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tl, cosf(angles_.z() * deg2rad) * tl, dv.z());
+ aim_ = pos_ + dv;
+ buildTransform();
+}
+
+
+void Camera::rotateXY(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = dv.length(), tc;
+ angles_.setY(angles_.y() + a);
+ angles_.setY(piClamp(angles_.y(), angle_limit_lower_xy, angle_limit_upper_xy));
+ tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ aim_ = pos_ + dv * tl;
+ buildTransform();
+}
+
+
+void Camera::orbitZ(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = QVector2D(dv.x(), dv.y()).length();
+ angles_.setZ(angles_.z() + a);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tl, cosf(angles_.z() * deg2rad) * tl, dv.z());
+ pos_ = aim_ - dv;
+ buildTransform();
+}
+
+
+void Camera::orbitXY(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = dv.length(), tc;
+ angles_.setY(angles_.y() + a);
+ angles_.setY(piClamp(angles_.y(), angle_limit_lower_xy, angle_limit_upper_xy));
+ tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ pos_ = aim_ - dv * tl;
+ buildTransform();
+}
+
+
+void Camera::setAngleZ(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = QVector2D(dv.x(), dv.y()).length();
+ angles_.setZ(a);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tl, cosf(angles_.z() * deg2rad) * tl, dv.z());
+ aim_ = pos_ + dv;
+ buildTransform();
+}
+
+
+void Camera::setAngleXY(const float & a) {
+ QVector3D dv = aim_ - pos_;
+ float tl = dv.length(), tc;
+ angles_.setY(a);
+ tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ //pos_ = aim_ - dv;
+ aim_ = pos_ + dv * tl;
+ buildTransform();
+ //anglesFromPoints();
+}
+
+
+void Camera::moveForward(const float & x, bool withZ) {
+ QVector3D dv;// = aim_ - pos_;
+ float tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, 0.);
+ if (withZ) dv.setZ(-cosf(angles_.y() * deg2rad));
+ dv.normalize();
+ dv *= x;
+ pos_ += dv;
+ aim_ += dv;
+ buildTransform();
+}
+
+
+void Camera::moveLeft(const float & x, bool withZ) {
+ QVector3D dv;// = aim_ - pos_;
+ float tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad - float(M_PI_2)) * tc, cosf(angles_.z() * deg2rad - float(M_PI_2)) * tc, 0.f);
+ if (withZ) dv.setZ(-sinf(angles_.x() * deg2rad));
+ dv.normalize();
+ dv *= x;
+ pos_ += dv;
+ aim_ += dv;
+ buildTransform();
+}
+
+
+void Camera::moveUp(const float & x, bool onlyZ) {
+ QVector3D dv;
+ if (onlyZ)
+ dv = QVector3D(0., 0., x);
+ else {
+ float tc = cosf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -sinf(angles_.y() * deg2rad));
+ dv.normalize();
+ dv *= x;
+ }
+ pos_ += dv;
+ aim_ += dv;
+ buildTransform();
+}
+
+
+void Camera::flyCloser(const float & s) {
+ QVector3D dv = aim_ - pos_;
+ float tl = dv.length() / (1.f + s), tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ pos_ = aim_ - dv * tl;
+ buildTransform();
+}
+
+
+void Camera::flyFarer(const float & s) {
+ QVector3D dv = aim_ - pos_;
+ float tl = dv.length() * (1.f + s), tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ pos_ = aim_ - dv * tl;
+ buildTransform();
+}
+
+
+void Camera::flyToDistance(const float & d) {
+ QVector3D dv = aim_ - pos_;
+ float tc = -sinf(angles_.y() * deg2rad);
+ dv = QVector3D(sinf(angles_.z() * deg2rad) * tc, cosf(angles_.z() * deg2rad) * tc, -cosf(angles_.y() * deg2rad));
+ pos_ = aim_ - dv * d;
+ buildTransform();
+}
+
diff --git a/qglengine/glcamera.h b/qglengine/glcamera.h
new file mode 100644
index 0000000..ea11a0b
--- /dev/null
+++ b/qglengine/glcamera.h
@@ -0,0 +1,107 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#ifndef GLCAMERA_H
+#define GLCAMERA_H
+
+#include "globject.h"
+
+//extern QMatrix4x4 globCameraMatrix;
+//extern Camera * currentCamera;
+
+class Camera: public ObjectBase
+{
+ friend class QGLView;
+ friend class GLParticlesSystem;
+ friend QDataStream & operator <<(QDataStream & s, const ObjectBase * p);
+ friend QDataStream & operator >>(QDataStream & s, ObjectBase *& p);
+public:
+ Camera();
+
+ void setPos(const QVector3D & p) {pos_ = p; anglesFromPoints(); buildTransform();}
+ void setAim(const QVector3D & p) {aim_ = p; anglesFromPoints(); buildTransform();}
+ void move(const QVector3D & p) {pos_ += p; aim_ += p; buildTransform();}
+ void move(const float & x, const float & y = 0., const float & z = 0.) {pos_ += QVector3D(x, y, z); aim_ += QVector3D(x, y, z); buildTransform();}
+ void moveForward(const float & x, bool withZ = true);
+ void moveBackward(const float & x, bool withZ = true) {moveForward(-x, withZ);}
+ void moveLeft(const float & x, bool withZ = true);
+ void moveRight(const float & x, bool withZ = true) {moveLeft(-x, withZ);}
+ void moveUp(const float & x, bool onlyZ = false);
+ void moveDown(const float & x, bool onlyZ = false) {moveUp(-x, onlyZ);}
+ void rotateZ(const float & a);
+ void rotateXY(const float & a);
+ void rotateRoll(const float & a) {angles_.setX(angles_.x() + a); buildTransform();}
+ void orbitZ(const float & a);
+ void orbitXY(const float & a);
+ void panZ(const float & a);
+ void panXY(const float & a);
+ void setFOV(const float & f) {fov_ = f;}
+ void setAngles(const QVector3D & a) {setRotation(a);}
+ void setAngleZ(const float & a);
+ void setAngleXY(const float & a);
+ void setAngleRoll(const float & a) {angles_.setX(a); buildTransform();}
+ void setAngleLowerLimitXY(const float & a) {angle_limit_lower_xy = a; buildTransform();}
+ void setAngleUpperLimitXY(const float & a) {angle_limit_upper_xy = a; buildTransform();}
+ void setAngleLimitsXY(const float & lower, const float & upper) {angle_limit_lower_xy = lower; angle_limit_upper_xy = upper; buildTransform();}
+ void setDepthStart(const float & d) {depth_start = d;}
+ void setDepthEnd(const float & d) {depth_end = d;}
+ void setMirrorX(bool yes) {mirror_x = yes;}
+ void setMirrorY(bool yes) {mirror_y = yes;}
+ void flyCloser(const float & s);
+ void flyFarer(const float & s);
+ void flyToDistance(const float & d);
+
+ QVector3D aim() const {return aim_;}
+ QVector3D angles() const {return rotation();}
+ QVector3D direction() const {return (aim_ - pos_).normalized();}
+ QVector3D directionXY() const {QVector3D tv = aim_ - pos_; return QVector3D(tv.x(), tv.y(), 0.).normalized();}
+ float FOV() const {return fov_;}
+ float distance() const {return (pos_ - aim_).length();}
+ float angleZ() const {return angles_.z();}
+ float angleXY() const {return angles_.y();}
+ float angleRoll() const {return angles_.x();}
+ float angleLowerLimitXY() const {return angle_limit_lower_xy;}
+ float angleUpperLimitXY() const {return angle_limit_upper_xy;}
+ float depthStart() const {return depth_start;}
+ float depthEnd() const {return depth_end;}
+ bool isMirrorX() const {return mirror_x;}
+ bool isMirrorY() const {return mirror_y;}
+ void anglesFromPoints();
+ void apply(const GLfloat & aspect = 1.);
+ void assign(const Camera & c);
+
+
+ virtual ObjectBase * clone(bool withChildren = true);
+ QMatrix4x4 viewMatrix() const;
+ QMatrix4x4 projectionMatrix(double aspect) const;
+ QMatrix4x4 offsetMatrix() const;
+
+private:
+ QVector3D aim_;
+ mutable QVector3D offset_;
+ GLfloat fov_;
+ GLfloat depth_start;
+ GLfloat depth_end;
+ GLfloat angle_limit_lower_xy;
+ GLfloat angle_limit_upper_xy;
+ bool mirror_x;
+ bool mirror_y;
+
+};
+
+#endif // GLCAMERA_H
diff --git a/qglengine/glframebuffer.cpp b/qglengine/glframebuffer.cpp
new file mode 100644
index 0000000..7bd3142
--- /dev/null
+++ b/qglengine/glframebuffer.cpp
@@ -0,0 +1,295 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#include
+#include "glframebuffer.h"
+#include
+
+
+Framebuffer::Framebuffer(QOpenGLExtraFunctions * f_, int colorAttachments_, bool withDepth, GLenum colorFormat_, GLenum _target): f(f_),
+ pbo(GL_PIXEL_PACK_BUFFER, GL_STREAM_DRAW) {
+ is_depth = withDepth;
+ color_format = colorFormat_;
+ target_ = _target;
+ colors.fill(0, colorAttachments_);
+ fbo = drbo = 0;
+ tex_d = 0;
+ wid = hei = 0;
+ pbo_queried = 0;
+ is_changed = false;
+}
+
+
+Framebuffer::~Framebuffer() {
+ deleteGLFramebuffer(fbo);
+ deleteGLRenderbuffer(drbo);
+ for (int i = 0; i < colors.size(); ++i)
+ deleteGLTexture(f, colors[i]);
+ deleteGLTexture(f, tex_d);
+}
+
+
+void Framebuffer::resize(int width, int height, bool force) {
+ if ((wid == width) && (hei == height) && !force) return;
+ wid = width;
+ hei = height;
+ deleteGLFramebuffer(fbo);
+ f->glGenFramebuffers(1, &fbo);
+ f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ for (int i = 0; i < colors.size(); ++i) {
+ deleteGLTexture(f, colors[i]);
+ createGLTexture(f, colors[i], width, height, color_format, target_);
+ f->glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ f->glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ f->glTexParameteri(target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ f->glTexParameteri(target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ f->glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, 4);
+ //f->glTexParameteri(target_, GL_GENERATE_MIPMAP_SGIS, GL_FALSE);
+ f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, target_, colors[i], 0);
+ }
+ if (is_depth) {
+ deleteGLTexture(f, tex_d);
+ deleteGLRenderbuffer(drbo);
+ f->glGenRenderbuffers(1, &drbo);
+ f->glBindRenderbuffer(GL_RENDERBUFFER, drbo);
+ f->glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
+ f->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, drbo);
+ createGLTexture(f, tex_d, width, height, GL_DEPTH_COMPONENT);
+ f->glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ f->glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ f->glTexParameteri(target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ f->glTexParameteri(target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ //f->glTexParameteri(target_, GL_GENERATE_MIPMAP_SGIS, GL_FALSE);
+ f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, target_, tex_d, 0);
+ }
+ f->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ if (pbo.isInit()) {
+ pbo.bind(f);
+ pbo.resize(f, width*height*4);
+ pbo.release(f);
+ }
+ is_changed = false;
+}
+
+
+QImage Framebuffer::grab() const {
+ //glReadPixels(0, 0, wid, hei, GL_RGBA, );
+ //QImage ret();
+ return QImage();
+}
+
+
+void Framebuffer::queryPoint(int index, QPoint p) {
+ pbo_queried = 0;
+ if (index < 0 || index >= colors.size()) return;
+ if (!rect().contains(p) || !pbo.isInit()) return;
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+ f->glReadBuffer(GL_COLOR_ATTACHMENT0 + index);
+ //QTime tm; tm.restart();
+ pbo.bind(f);
+ f->glReadPixels(p.x(), height() - p.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ pbo_queried = 1;
+ pbo.release(f);
+ //qDebug() << tm.elapsed();
+}
+
+
+void Framebuffer::queryPoints(int index, QRect rect_) {
+ pbo_queried = 0;
+ if (index < 0 || index >= colors.size()) return;
+ rect_ &= rect();
+ if (rect_.isEmpty() || !pbo.isInit()) return;
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+ f->glReadBuffer(GL_COLOR_ATTACHMENT0 + index);
+ //QTime tm; tm.restart();
+ pbo.bind(f);
+ f->glReadPixels(rect_.x(), height() - rect_.bottom(), rect_.width(), rect_.height(), GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ pbo_queried = rect_.width() * rect_.height();
+ pbo.release(f);
+ //qDebug() << tm.elapsed();
+}
+
+
+void Framebuffer::queryImage(int index) {
+ queryPoints(index, rect());
+}
+
+
+uint Framebuffer::getPoint() const {
+ if (!pbo.isInit() || (pbo_queried == 0)) return 0;
+ //QTime tm; tm.restart();
+ uint ret = 0;
+ pbo.bind(f);
+ //glClearError();
+ void * map = pbo.map(f, GL_MAP_READ_BIT, sizeof(uint));
+ //qDebug() << map << QString::number(glGetError(), 16);
+ if (map)
+ memcpy(&ret, map, sizeof(uint));
+ pbo.unmap(f);
+ pbo.release(f);
+ //qDebug() << tm.elapsed();
+ return ret;
+}
+
+
+QVector Framebuffer::getPoints() const {
+ QVector ret;
+ if (!pbo.isInit() || (pbo_queried == 0)) return ret;
+ ret.resize(pbo_queried);
+ //QTime tm; tm.restart();
+ pbo.bind(f);
+ //glClearError();
+ void * map = pbo.map(f, GL_MAP_READ_BIT, pbo_queried * sizeof(uint));
+ //qDebug() << map << QString::number(glGetError(), 16);
+ if (map)
+ memcpy(ret.data(), map, pbo_queried * sizeof(uint));
+ pbo.unmap(f);
+ pbo.release(f);
+ //qDebug() << tm.elapsed();
+ return ret;
+}
+
+
+QImage Framebuffer::getImage() const {
+ QImage ret;
+ if (!pbo.isInit() || (pbo_queried == 0)) return ret;
+ ret = QImage(size(), QImage::Format_RGBA8888);
+ int bytes = width() * height() * 4;
+ //QTime tm; tm.restart();
+ pbo.bind(f);
+ //glClearError();
+ void * map = pbo.map(f, GL_MAP_READ_BIT, bytes);
+ //qDebug() << map << QString::number(glGetError(), 16);
+ if (map)
+ memcpy(ret.bits(), map, bytes);
+ pbo.unmap(f);
+ pbo.release(f);
+ //qDebug() << tm.elapsed();
+ return ret;
+}
+
+
+QVector Framebuffer::grabF(int index) const {
+ QVector ret;
+ if (index < 0 || index >= colors.size()) return ret;
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+ f->glReadBuffer(GL_COLOR_ATTACHMENT0 + index);
+ ret.resize(wid * hei * 4);
+ f->glReadPixels(0, 0, wid, hei, GL_RGBA, GL_FLOAT, ret.data());
+ return ret;
+}
+
+
+void Framebuffer::blit(int index_from, GLuint fb_to, int index_to, QRect from, QRect to, GLbitfield mask, GLenum filter) {
+ if (index_from < 0 || index_from >= colors.size()) return;
+ GLenum e = GL_COLOR_ATTACHMENT0 + index_to;
+ f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb_to);
+ f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+ f->glReadBuffer(GL_COLOR_ATTACHMENT0 + index_from);
+ f->glDrawBuffers(1, &e);
+ f->glBlitFramebuffer(from.x(), from.y(), from.right(), from.bottom(), to.x(), to.y(), to.right(), to.bottom(), mask, filter);
+}
+
+
+void Framebuffer::bind() {
+ if (is_changed) resize(wid, hei);
+ if (fbo == 0) return;
+ //glFlush();
+ f->glGetIntegerv(GL_VIEWPORT, prev_view);
+ //glClearError();
+ f->glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ //qDebug() << QString::number(glGetError(), 16);
+ setWriteBuffers();
+ f->glReadBuffer(GL_COLOR_ATTACHMENT0);
+ //f->glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ f->glViewport(0, 0, wid, hei);
+}
+
+
+void Framebuffer::release() {
+ is_changed = false;
+ if (fbo == 0) return;
+ //glFlush();
+ f->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ f->glViewport(prev_view[0], prev_view[1], prev_view[2], prev_view[3]);
+}
+
+
+void Framebuffer::setWriteBuffer(int index) {
+ //QVector buffers; buffers << GL_COLOR_ATTACHMENT0 + index;
+ GLenum e = GL_COLOR_ATTACHMENT0 + index;
+ f->glDrawBuffers(1, &e);
+}
+
+
+void Framebuffer::setWriteBuffers(int * indeces, int count) {
+ QVector buffers;
+ for (int i = 0; i < count; ++i)
+ buffers << GL_COLOR_ATTACHMENT0 + indeces[i];
+ f->glDrawBuffers(buffers.size(), buffers.constData());
+}
+
+
+void Framebuffer::setWriteBuffers() {
+ QVector buffers;
+ for (int i = 0; i < colors.size(); ++i)
+ buffers << GL_COLOR_ATTACHMENT0 + i;
+ f->glDrawBuffers(buffers.size(), buffers.constData());
+}
+
+
+void Framebuffer::enablePixelBuffer() {
+ pbo.init(f);
+}
+
+
+void Framebuffer::bindColorTexture(int index, int channel) {
+ if (index < 0 || index >= colors.size()) return;
+ f->glActiveTexture(GL_TEXTURE0 + channel);
+ f->glBindTexture(GL_TEXTURE_2D, colors[index]);
+}
+
+
+void Framebuffer::bindColorTextures() {
+ for (int i = colors.size() - 1; i >= 0; --i) {
+ f->glActiveTexture(GL_TEXTURE0 + i);
+ f->glBindTexture(GL_TEXTURE_2D, colors[i]);
+ //f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ //f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ }
+}
+
+
+void Framebuffer::bindDepthTexture(int channel) {
+ f->glActiveTexture(GL_TEXTURE0 + channel);
+ f->glBindTexture(GL_TEXTURE_2D, tex_d);
+}
+
+
+void Framebuffer::deleteGLRenderbuffer(GLuint & drbo) {
+ if (drbo != 0)
+ f->glDeleteRenderbuffers(1, &drbo);
+ drbo = 0;
+}
+
+
+void Framebuffer::deleteGLFramebuffer(GLuint & fbo) {
+ if (fbo != 0)
+ f->glDeleteFramebuffers(1, &fbo);
+ fbo = 0;
+}
diff --git a/qglengine/glframebuffer.h b/qglengine/glframebuffer.h
new file mode 100644
index 0000000..d4704a3
--- /dev/null
+++ b/qglengine/glframebuffer.h
@@ -0,0 +1,81 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#ifndef GLFRAMEBUFFER_H
+#define GLFRAMEBUFFER_H
+
+#include "glbuffer.h"
+
+
+class Framebuffer
+{
+public:
+ Framebuffer(QOpenGLExtraFunctions * f_, int colorAttachments = 1, bool withDepth = true, GLenum colorFormat = GL_RGBA8, GLenum _target = GL_TEXTURE_2D);
+ virtual ~Framebuffer();
+
+ GLuint id() const {return fbo;}
+ GLuint colorTexture(int index = 0) const {return colors[index];}
+ GLenum colorFormat() const {return color_format;}
+ GLuint depthTexture() const {return tex_d;}
+ GLenum target() const {return target_;}
+ int width() const {return wid;}
+ int height() const {return hei;}
+ QSize size() const {return QSize(wid, hei);}
+ QRect rect() const {return QRect(0, 0, wid, hei);}
+ QImage grab() const;
+ QVector grabF(int index) const;
+ void queryPoint(int index, QPoint p);
+ void queryPoints(int index, QRect rect);
+ void queryImage(int index);
+ uint getPoint() const;
+ QVector getPoints() const;
+ QImage getImage() const;
+ int queriedPoints() const {return pbo_queried;}
+ void blit(int index_from, GLuint fb_to, int index_to, QRect from, QRect to, GLbitfield mask = GL_COLOR_BUFFER_BIT, GLenum filter = GL_NEAREST );
+
+ void resize(int width, int height, bool force = false);
+ void bind();
+ void release();
+ void setReadBuffer(int index) {glReadBuffer(GL_COLOR_ATTACHMENT0 + index);}
+ void setWriteBuffer(int index);
+ void setWriteBuffers(int * indeces, int count);
+ void setWriteBuffers();
+ void setColorFormat(GLenum format) {color_format = format; is_changed = true;}
+ void enablePixelBuffer();
+
+ void copyDepthFrom(GLuint tex) {;}
+ void bindColorTexture(int index, int channel = 0);
+ void bindColorTextures();
+ void bindDepthTexture(int channel);
+
+private:
+ void deleteGLRenderbuffer(GLuint & drbo);
+ void deleteGLFramebuffer(GLuint & fbo);
+
+ bool is_depth, is_changed;
+ int pbo_queried;
+ QOpenGLExtraFunctions * f;
+ mutable Buffer pbo;
+ QVector colors;
+ GLenum color_format, target_;
+ GLuint fbo, drbo, tex_d;
+ GLint prev_view[4], wid, hei;
+
+};
+
+#endif // GLFRAMEBUFFER_H
diff --git a/qglengine/glmaterial.cpp b/qglengine/glmaterial.cpp
new file mode 100644
index 0000000..3c45fc3
--- /dev/null
+++ b/qglengine/glmaterial.cpp
@@ -0,0 +1,200 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#include "gltypes.h"
+#include "gltexture_manager.h"
+#include "qglview.h"
+
+using namespace QGLEngineShaders;
+
+/*
+bool CubeTexture::create() {
+ //qDebug("create");
+ destroy();
+ glGenTextures(1, &id_);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, id_);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ //glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
+ //glClearError();
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ //qDebug() << glGetError();
+ changed_ = false;
+ return id_ > 0;
+}
+
+
+void CubeTexture::load() {
+ if (isEmpty()) return;
+ create();
+ if (!path(0).isEmpty()) loadFront(path(0));
+ if (!path(1).isEmpty()) loadBack(path(1));
+ if (!path(2).isEmpty()) loadLeft(path(2));
+ if (!path(3).isEmpty()) loadRight(path(3));
+ if (!path(4).isEmpty()) loadTop(path(4));
+ if (!path(5).isEmpty()) loadBottom(path(5));
+}
+
+
+void CubeTexture::loadFromDirectory(const QString & dir) {
+ QDir d(dir); QFileInfoList sl;
+ sl = d.entryInfoList(QStringList("front.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) loadFront(sl[0].absoluteFilePath());
+ sl = d.entryInfoList(QStringList("back.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) loadBack(sl[0].absoluteFilePath());
+ sl = d.entryInfoList(QStringList("left.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) loadLeft(sl[0].absoluteFilePath());
+ sl = d.entryInfoList(QStringList("right.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) loadRight(sl[0].absoluteFilePath());
+ sl = d.entryInfoList(QStringList("top.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) loadTop(sl[0].absoluteFilePath());
+ sl = d.entryInfoList(QStringList("bottom.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) loadBottom(sl[0].absoluteFilePath());
+}
+
+
+void CubeTexture::loadPathesFromDirectory(const QString & dir) {
+ QDir d(dir); QFileInfoList sl;
+ sl = d.entryInfoList(QStringList("front.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) pathes[0] = sl[0].absoluteFilePath();
+ sl = d.entryInfoList(QStringList("back.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) pathes[1] = sl[0].absoluteFilePath();
+ sl = d.entryInfoList(QStringList("left.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) pathes[2] = sl[0].absoluteFilePath();
+ sl = d.entryInfoList(QStringList("right.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) pathes[3] = sl[0].absoluteFilePath();
+ sl = d.entryInfoList(QStringList("top.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) pathes[4] = sl[0].absoluteFilePath();
+ sl = d.entryInfoList(QStringList("bottom.*"), QDir::Files | QDir::NoDotAndDotDot); if (!sl.isEmpty()) pathes[5] = sl[0].absoluteFilePath();
+}
+*/
+
+
+
+Map::Map() {
+ bitmap_id = 0;
+ color_amount = 1.f;
+ color_offset = 0.f;
+ bitmap_scale = QPointF(1., 1.);
+ _changed = true;
+ _layer = 0;
+}
+
+
+void Map::setBitmapPath(const QString & p) {
+ bitmap_path = p;
+ _changed = true;
+}
+
+
+void Map::load(TextureManager * tm) {
+ if (bitmap_id == 0)
+ bitmap_id = tm->loadTexture(bitmap_path, true, _type == mtNormal);
+}
+
+
+void Map::copyToQGLMap(QGLMap & m) const {
+ m.amount = color_amount;
+ m.offset = color_offset;
+ m.scale = QVector2D(bitmap_scale);
+ if (hasBitmap()) {
+ m.array_index = tarMaps;
+ m.map_index = _layer;
+ } else {
+ m.array_index = tarEmpty;
+ m.map_index = (_type == mtNormal ? emrBlue : emrWhite);
+ }
+}
+
+
+
+
+Material::Material(const QString _name)/*: map_reflection(512)*/ {
+ setTypes();
+ name = _name;
+ color_diffuse = color_specular = Qt::darkGray;
+ color_emission = Qt::black;
+ glass = false;
+ transparency = reflectivity = 0.f;
+ map_roughness.color_amount = 0.75f;
+ map_specular.color_amount = 1.f;
+ iof = 1.f;
+ dispersion = 0.05f;
+ _changed = true;
+ _index = 0;
+}
+
+
+uint Material::hash() {
+ return qHash(name);
+}
+
+
+bool Material::hasTransparency() const {
+ return float(color_diffuse.alphaF()) * (1.f - transparency) < 1.f;
+}
+
+
+bool Material::isMapsChanged() const {
+ return map_diffuse ._changed ||
+ map_normal ._changed ||
+ map_specular ._changed ||
+ map_roughness._changed ||
+ map_emission ._changed ||
+ map_relief ._changed;
+}
+
+
+bool Material::isMapChanged(int type) const {
+ switch (type) {
+ case mtDiffuse : return map_diffuse ._changed;
+ case mtNormal : return map_normal ._changed;
+ case mtSpecular : return map_specular ._changed;
+ case mtRoughness: return map_roughness._changed;
+ case mtEmission : return map_emission ._changed;
+ case mtRelief : return map_relief ._changed;
+ }
+ return false;
+}
+
+
+void Material::load(TextureManager * tm) {
+ map_diffuse .load(tm);
+ map_normal .load(tm);
+ map_specular .load(tm);
+ map_roughness.load(tm);
+ map_emission .load(tm);
+ map_relief .load(tm);
+}
+
+
+void Material::setMapsChanged() {
+ map_diffuse ._changed = true;
+ map_normal ._changed = true;
+ map_specular ._changed = true;
+ map_roughness._changed = true;
+ map_emission ._changed = true;
+ map_relief ._changed = true;
+}
+
+
+void Material::setTypes() {
+ map_diffuse ._type = mtDiffuse;
+ map_normal ._type = mtNormal;
+ map_specular ._type = mtSpecular;
+ map_roughness._type = mtRoughness;
+ map_emission ._type = mtEmission;
+ map_relief ._type = mtRelief;
+}
diff --git a/qglengine/glmaterial.h b/qglengine/glmaterial.h
new file mode 100644
index 0000000..d1fb6a0
--- /dev/null
+++ b/qglengine/glmaterial.h
@@ -0,0 +1,173 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#ifndef GLMATERIAL_H
+#define GLMATERIAL_H
+
+#include "glshaders_types.h"
+#include "chunkstream.h"
+
+/*
+class Texture {
+public:
+ Texture(int _width, int _height, const GLenum & _format = GL_RGBA8, const GLenum & _target = GL_TEXTURE_2D) {wid = _width; hei = _height; format_ = _format; target_ = _target; id_ = 0;}
+ bool create() {destroy(); createGLTexture(id_, wid, hei, format_, target_); return id_ > 0;}
+ void destroy() {if (id_ > 0) glDeleteTextures(1, &id_); id_ = 0;}
+ void bind() {if (id_ > 0) glBindTexture(target_, id_);}
+ void release() {glBindTexture(target_, 0);}
+ int width() const {return wid;}
+ int height() const {return hei;}
+ GLenum format() const {return format_;}
+ GLenum target() const {return target_;}
+ GLuint id() const {return id_;}
+private:
+ int wid, hei;
+ GLenum format_, target_;
+ GLuint id_;
+};
+
+
+class CubeTexture {
+public:
+ CubeTexture(int _size, const GLenum & _format = GL_RGBA8) {size = _size; format_ = _format; id_ = 0; changed_ = false; pathes.resize(6);}
+ bool create();
+ void destroy() {if (id_ > 0) glDeleteTextures(1, &id_); id_ = 0;}
+ void bind() {if (changed_) {changed_ = false; create();} if (id_ > 0) glBindTexture(GL_TEXTURE_CUBE_MAP, id_);}
+ void release() {glBindTexture(GL_TEXTURE_CUBE_MAP, 0);}
+ void resize(int _size) {size = _size; changed_ = true;}
+ void loadFromDirectory(const QString & dir);
+ void loadFront(const QString & path) {bind(); pathes[0] = path; createGLTexture(id_, rotateQImageLeft(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), format_, GL_TEXTURE_CUBE_MAP_POSITIVE_X);}
+ void loadBack(const QString & path) {bind(); pathes[1] = path; createGLTexture(id_, rotateQImageRight(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), format_, GL_TEXTURE_CUBE_MAP_NEGATIVE_X);}
+ void loadLeft(const QString & path) {bind(); pathes[2] = path; createGLTexture(id_, QImage(path).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), format_, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);}
+ void loadRight(const QString & path) {bind(); pathes[3] = path; createGLTexture(id_, rotateQImage180(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), format_, GL_TEXTURE_CUBE_MAP_POSITIVE_Y);}
+ void loadTop(const QString & path) {bind(); pathes[4] = path; createGLTexture(id_, rotateQImageLeft(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), format_, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);}
+ void loadBottom(const QString & path) {bind(); pathes[5] = path; createGLTexture(id_, rotateQImageLeft(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), format_, GL_TEXTURE_CUBE_MAP_POSITIVE_Z);}
+ void load();
+ bool isEmpty() const {foreach (const QString & i, pathes) if (!i.isEmpty()) return false; return true;}
+ GLenum format() const {return format_;}
+ void setFormat(GLenum f) {format_ = f; changed_ = true;}
+ GLuint id() const {return id_;}
+ const QString & path(int side) const {return pathes[side];}
+ void setPath(int side, const QString & p) {pathes[side] = p;}
+ void loadPathesFromDirectory(const QString & dir);
+private:
+ bool changed_;
+ int size;
+ GLenum format_;
+ GLuint id_;
+ QVector pathes;
+};
+*/
+
+class Map {
+public:
+ Map();
+ void setBitmapPath(const QString & p);
+ void clearBitmap() {setBitmapPath(QString());}
+ bool hasBitmap() const {return !bitmap_path.isEmpty();}
+ void load(TextureManager * tm);
+ void copyToQGLMap(QGLEngineShaders::QGLMap & m) const;
+ QString bitmap_path;
+ GLuint bitmap_id;
+ QPointF bitmap_offset;
+ QPointF bitmap_scale;
+ float color_amount;
+ float color_offset;
+
+ bool _changed;
+ int _type, _layer;
+};
+
+class Material {
+public:
+ Material(const QString _name = QString());
+ uint hash();
+ bool hasTransparency() const;
+ bool isMapsChanged() const;
+ bool isMapChanged(int type) const;
+ void load(TextureManager * tm);
+ void setMapsChanged();
+ void setTypes();
+ QString name;
+ QColor color_diffuse;
+ QColor color_specular;
+ QColor color_emission;
+ bool glass;
+ float transparency;
+ float reflectivity;
+ float iof;
+ float dispersion;
+ Map map_diffuse ;
+ Map map_normal ;
+ Map map_specular ;
+ Map map_roughness;
+ Map map_emission ;
+ Map map_relief ;
+ bool _changed;
+ int _index;
+ //GLCubeTexture map_reflection;
+};
+
+
+inline QDataStream & operator <<(QDataStream & s, const Map & m) {
+ ChunkStream cs;
+ cs.add(1, m.bitmap_path).add(2, m.color_amount).add(3, m.color_offset).add(6, m.bitmap_scale);
+ s << cs.data(); return s;
+}
+inline QDataStream & operator >>(QDataStream & s, Map & m) {
+ ChunkStream cs(s);
+ cs.readAll();
+ cs.get(1, m.bitmap_path).get(2, m.color_amount).get(3, m.color_offset).get(6, m.bitmap_scale);
+ return s;
+}
+
+inline QDataStream & operator <<(QDataStream & s, const Material * m) {
+ ChunkStream cs;
+ cs.add(1, m->name).add(2, m->color_diffuse).add(3, m->color_specular).add(4, m->color_emission)
+ .add(5, m->transparency).add(6, m->reflectivity).add(7, m->glass).add(8, m->map_diffuse).add(9, m->map_normal)
+ .add(10, m->map_relief).add(11, m->map_specular).add(12, m->map_roughness).add(13, m->map_emission);
+ s << /*qCompress*/(cs.data()); return s;
+}
+inline QDataStream & operator >>(QDataStream & s, Material *& m) {
+ m = new Material();
+ //QByteArray ba;
+ //s >> ba;
+ //ba = qUncompres(ba);
+ ChunkStream cs(s);
+ while (!cs.atEnd()) {
+ switch (cs.read()) {
+ case 1: cs.get(m->name); break;
+ case 2: cs.get(m->color_diffuse); break;
+ case 3: cs.get(m->color_specular); break;
+ case 4: cs.get(m->color_emission); break;
+ case 5: cs.get(m->transparency); break;
+ case 6: cs.get(m->reflectivity); break;
+ case 7: cs.get(m->glass); break;
+ case 8: cs.get(m->map_diffuse); break;
+ case 9: cs.get(m->map_normal); break;
+ case 10: cs.get(m->map_relief); break;
+ case 11: cs.get(m->map_specular); break;
+ case 12: cs.get(m->map_roughness); break;
+ case 13: cs.get(m->map_emission); break;
+ }
+ }
+ m->setTypes();
+ return s;
+}
+
+#endif // GLMATERIAL_H
diff --git a/qglengine/glmesh.cpp b/qglengine/glmesh.cpp
new file mode 100644
index 0000000..ced3d35
--- /dev/null
+++ b/qglengine/glmesh.cpp
@@ -0,0 +1,422 @@
+/*
+ QGLView
+ 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 .
+*/
+
+#define GL_GLEXT_PROTOTYPES
+#include
+#include "glmesh.h"
+#include "globject.h"
+#include
+
+using namespace QGLEngineShaders;
+
+static int _count = 0;
+
+Mesh::Mesh(): buffer_geom(GL_ARRAY_BUFFER, GL_STATIC_DRAW),
+ buffer_ind (GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW),
+ buffer_obj (GL_ARRAY_BUFFER, GL_STREAM_DRAW),
+ buffer_sel (GL_ARRAY_BUFFER, GL_STREAM_DRAW) {
+ vao = 0;
+ hash_ = 0;
+ changed = hash_changed = objects_changed = selected_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->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);
+ buffer_obj .init(f);
+ buffer_sel .init(f);
+ f->glGenVertexArrays(1, &vao);
+ }
+ changed = true;
+}
+
+
+void Mesh::destroy(QOpenGLExtraFunctions * f) {
+ if (vao != 0) {
+ f->glDeleteVertexArrays(1, &vao);
+ buffer_geom.destroy(f);
+ buffer_ind .destroy(f);
+ buffer_obj .destroy(f);
+ buffer_sel .destroy(f);
+ }
+ vao = 0;
+}
+
+
+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();
+}
+
+
+void Mesh::loadBuffer(QOpenGLExtraFunctions * f, Buffer & buf, const void * data, int size) {
+ if (!isInit()) init(f);
+ if (!buf.isInit() || !data) return;
+ buf.bind(f);
+ buf.resize(f, size);
+ buf.load(f, data, size);
+ //qDebug() << "loadBuffer" << size << "bytes";
+ /*void * map = buf.map(f, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+ qDebug() << map;
+ if (map)
+ memcpy(map, objects_.constData(), osize);
+ buf.unmap(f);*/
+}
+
+
+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 isize = triangles_.size() * sizeof(Vector3i);
+ f->glBindVertexArray(vao);
+
+ buffer_geom.bind(f);
+ buffer_geom.resize(f, gsize);
+ buffer_geom.load(f, data_.constData(), gsize);
+ prepareDrawGeom(f);
+
+ buffer_ind.bind(f);
+ buffer_ind.resize(f, isize);
+ buffer_ind.load(f, triangles_.constData(), isize);
+
+ buffer_obj.bind(f);
+ prepareDrawObj(f);
+
+ buffer_sel.bind(f);
+ prepareDrawSel(f);
+
+ f->glBindVertexArray(0);
+ return !isEmpty();
+}
+
+
+void Mesh::prepareDrawGeom(QOpenGLExtraFunctions * f) {
+ //qDebug() << "prepareDrawGeom";
+
+ f->glEnableVertexAttribArray(pos_loc );
+ f->glEnableVertexAttribArray(normal_loc );
+ f->glEnableVertexAttribArray(tangent_loc );
+ f->glEnableVertexAttribArray(bitangent_loc);
+ f->glEnableVertexAttribArray(tex_loc );
+
+ int size = sizeof(Vertex);
+ f->glVertexAttribPointer(pos_loc , 3, GL_FLOAT, GL_FALSE, size, (const void *)pos_offset );
+ f->glVertexAttribPointer(normal_loc , 3, GL_FLOAT, GL_FALSE, size, (const void *)normal_offset );
+ f->glVertexAttribPointer(tangent_loc , 3, GL_FLOAT, GL_FALSE, size, (const void *)tangent_offset );
+ f->glVertexAttribPointer(bitangent_loc, 3, GL_FLOAT, GL_FALSE, size, (const void *)bitangent_offset);
+ f->glVertexAttribPointer(tex_loc , 2, GL_FLOAT, GL_FALSE, size, (const void *)tex_offset );
+}
+
+
+void Mesh::prepareDrawObj(QOpenGLExtraFunctions * f) {
+ //qDebug() << "prepareDrawObj";
+
+ f->glEnableVertexAttribArray(material_loc );
+ f->glEnableVertexAttribArray(object_id_loc);
+ f->glEnableVertexAttribArray(color_loc );
+ for (int i = 0; i < 4; ++i) {
+ f->glEnableVertexAttribArray(modelmatrix_loc + i);
+ }
+
+ GLsizei size = sizeof(Object);
+ f->glVertexAttribIPointer(material_loc , 1, GL_UNSIGNED_INT , size, (const void *)material_offset );
+ f->glVertexAttribIPointer(object_id_loc, 1, GL_UNSIGNED_INT , size, (const void *)object_id_offset);
+ f->glVertexAttribPointer (color_loc , 4, GL_FLOAT, GL_FALSE, size, (const void *)color_offset );
+ for (int i = 0; i < 4; ++i) {
+ f->glVertexAttribPointer(modelmatrix_loc + i, 4, GL_FLOAT, GL_FALSE, size, (const void *)(modelmatrix_offset + sizeof(QVector4D)*i));
+ }
+
+ f->glVertexAttribDivisor(material_loc , 1);
+ f->glVertexAttribDivisor(object_id_loc, 1);
+ f->glVertexAttribDivisor(color_loc , 1);
+ for (int i = 0; i < 4; ++i) {
+ f->glVertexAttribDivisor(modelmatrix_loc + i, 1);
+ }
+
+}
+
+
+void Mesh::prepareDrawSel(QOpenGLExtraFunctions * f) {
+ //qDebug() << "prepareDrawObj";
+
+ f->glEnableVertexAttribArray(is_selected_loc);
+ GLsizei size = 1;
+ f->glVertexAttribIPointer(is_selected_loc, 1, GL_UNSIGNED_BYTE, size, (const void *)is_selected_offset);
+ f->glVertexAttribDivisor(is_selected_loc, 1);
+
+}
+
+
+void Mesh::draw(QOpenGLExtraFunctions * f, int count) {
+ if (isEmpty()) return;
+ if (!isInit()) init(f);
+ if (changed) rebuffer(f);
+ //qDebug() << "draw" << vert_count << count;
+
+ f->glBindVertexArray(vao);
+ f->glDrawElementsInstanced(GL_TRIANGLES, triangles_.size() * 3, GL_UNSIGNED_INT, 0, count);
+ f->glBindVertexArray(0);
+}
+
+
+void Mesh::clear() {
+ vertices_.clear();
+ normals_.clear();
+ tangents_.clear();
+ bitangents_.clear();
+ texcoords_.clear();
+ triangles_.clear();
+ data_.clear();
+ changed = hash_changed = true;
+}
+
+
+void Mesh::loadObject(QOpenGLExtraFunctions * f, const Object & object) {
+ loadBuffer(f, buffer_obj, &object, sizeof(Object));
+}
+
+
+void Mesh::loadObjects(QOpenGLExtraFunctions * f, const QVector