/*
    QGLView
    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/>.
*/

#include "qglview.h"

#include <QApplication>
#include <QKeyEvent>
#include <qad_types.h>


QGLView::QGLView(): OpenGLWindow(), fbo_selection(3) {
	//	setFrameShape(QFrame::NoFrame);
	//	setViewportUpdateMode(FullViewportUpdate);
	//	setCacheMode(CacheNone);
	//	setMouseTracking(true);
	//	setFocusPolicy(Qt::WheelFocus);
	//	setScene(new QGraphicsScene());
	//	setInteractive(true);
	setIcon(QIcon("://icons/qglview.png"));
	deleting_  = false;
	timer      = 0;
	need_init_ = is_first_draw = true;
	objects_.is_root           = true;
	objects_.view_             = this;
	backColor_                 = Qt::darkGray;
	hoverHaloColor_            = QColor(195, 140, 255, 96);
	selectionHaloColor_        = QColor(175, 255, 140);
	ambientColor_              = QColor(10, 10, 10);
	lastPos                    = QPoint(-1, -1);
	lineWidth_                 = 1.;
	max_anisotropic            = 1;
	max_texture_chanels        = 8;
	cameraOrbit_ = lightEnabled_ = true;
	shaders_supported = selecting_ = customMouseMove_ = false;
	sel_button                                        = Qt::LeftButton;
	sel_mod                                           = Qt::NoModifier;
	renderer_                                         = nullptr;
	fps_cnt                                           = 0;
	fps_tm = fps_ = 0.;
	sel_obj = hov_obj = nullptr;
	fogDensity_ = fogEnd_ = 1.;
	fogStart_             = 0.;
	fogMode_              = Exp;
	hoverHaloFill_        = 0.333f;
	selectionHaloFill_    = 0.5f;
	// lmode = Simple;
	shader_select = shader_halo = nullptr;
	setFeature(qglMSAA, false);
	setFeature(qglFXAA, false);
	setFeature(qglLinearFiltering, true);
	setFeature(qglAnisotropicLevel, 8);
	setFeature(qglHDR, false);
	setFeature(qglEyeAccomodationEnabled, false);
	setFeature(qglEyeAccomodationTime, 16.);
	setFeature(qglEyeAccomodationMaxSpeed, 0.2);
	setFeature(qglBloomEnabled, false);
	setFeature(qglBloomThreshold, 0.9);
	setFeature(qglBloomFactor, 1.);
	setFeature(qglBloomRadius, 8);
	setFeature(qglMotionBlurEnabled, false);
	setFeature(qglMotionBlurFactor, 1.);
	setFeature(qglMotionBlurSteps, 8);
	setFeature(qglShadowsEnabled, false);
	setFeature(qglShadowsMapSize, 512);
	setFeature(qglShadowsSoftEnabled, true);
	setFeature(qglReflectionsEnabled, false);
	setFeature(qglReflectionsBlur, true);
	setFeature(qglSSAOEnabled, false);
	setFeature(qglSSAORadius, 5);
	setFeature(qglDepthOfFieldEnabled, false);
	setFeature(qglDepthOfFieldAutoFocusEnabled, true);
	setFeature(qglDepthOfFieldAutoFocusSpeed, 0.1);
	setFeature(qglDepthOfFieldFocus, 1.);
	setFeature(qglDepthOfFieldDiaphragm, 8.);
	mouse_first = mouseSelect_ = hoverHalo_ = selectionHalo_ = true;
	mouseRotate_                                             = true;
	fogEnabled_ = is_init = grabMouse_ = shaders_bind = changed_ = false;
	rmode                                                        = GLObjectBase::Fill;
	sel_mode                                                     = QGLView::SingleSelection;
	//	sel_pen = QPen(Qt::black, 1, Qt::DashLine);
	//	sel_brush = QBrush(QColor(170, 100, 255, 120));
	camera()->setAim(QVector3D());
	camera()->setPos(QVector3D(2, 2, 2));
	camera()->setName("Camera");
	addObject(camera());
	emit cameraPosChanged(camera()->pos());
	// camera().aim_ = camera().pos_;
	ktm_.restart();
}


QGLView::~QGLView() {
	stop();
	if (shader_select) delete shader_select;
	if (shader_halo) delete shader_halo;
	deleting_ = true;
}


void QGLView::stop() {
	if (timer) killTimer(timer);
	timer = 0;
}


void QGLView::start(float freq) {
	stop();
	timer = startTimer(freq <= 0.f ? 0 : int(1000.f / freq));
}


GLRendererBase * QGLView::renderer() {
	return renderer_;
}


void QGLView::setRenderer(GLRendererBase * r, GLRendererBase ** prev) {
	if (prev != nullptr) *prev = renderer_;
	renderer_ = r;
}


void QGLView::addObject(GLObjectBase * o) {
	objects_.addChild(o);
	o->setView(this);
	collectLights();
	QList<GLObjectBase *> cl = o->children(true);
	cl << o;
	foreach(GLObjectBase * i, cl) {
		emit objectAdded(i);
	}
	if (is_init) {
		o->init();
	}
}


int QGLView::objectsCount(bool all) {
	if (!all) return objects_.childCount();
	int cnt = 0;
	objectsCountInternal(&cnt, &objects_);
	return cnt;
}


void QGLView::removeObject(GLObjectBase * o, bool inChildren) {
	o->setView(nullptr);
	if (inChildren)
		removeObjectInternal(o, &objects_);
	else
		objects_.removeChild(o);
	objectDeleted(o);
}


void QGLView::removeObject(GLObjectBase & o, bool inChildren) {
	removeObject(&o, inChildren);
}


void QGLView::clearObjects(bool deleteAll) {
	removeObject(camera_);
	objects_.clearChildren(deleteAll);
	addObject(camera());
	selectObject(nullptr);
	hov_obj = nullptr;
}


QList<GLObjectBase *> QGLView::objects(bool all) {
	return objects_.children(all);
}


int QGLView::lightsCount() const {
	return lights_.size();
}


void QGLView::removeLight(int index) {
	removeObject(lights_.at(index));
	lights_.removeAt(index);
}


void QGLView::removeLight(Light * l) {
	foreach(Light * i, lights_)
		if (i == l) removeObject(i);
	lights_.removeAll(l);
}


void QGLView::clearLights(bool deleteAll) {
	if (deleteAll)
		foreach(Light * i, lights_)
			delete i;
	lights_.clear();
}


void QGLView::addTexture(const QString & path) {
	textures_manager->addTexture(path);
}


void QGLView::addAnimation(const QString & dir, const QString & name) {
	textures_manager->addAnimation(dir, name);
}


Light * QGLView::light(int index) {
	return lights_[index];
}


Light * QGLView::light(const QString & name) {
	foreach(Light * i, lights_)
		if (i->name_ == name) return i;
	return nullptr;
}


void QGLView::selectObject(GLObjectBase * o) {
	if (o == sel_obj) return;
	GLObjectBase * pso = sel_obj;
	sel_obj            = o;
	emit selectionChanged(sel_obj, pso);
}


void QGLView::resizeEvent(QResizeEvent * e) {
	renderLater();
}

void QGLView::timerEvent(QTimerEvent *) {
	renderNow();
	if (ktm_.elapsed() < QApplication::keyboardInputInterval()) return;
	Qt::KeyboardModifiers km = QApplication::keyboardModifiers();
	foreach(int i, keys_)
		emit keyEvent((Qt::Key)i, km);
}


void QGLView::render() {
	if (!isVisible()) return;
	resizeGL(width(), height());
	QRect g_rect(QPoint(), size());
	emit glBeforePaint();
	// qDebug() << "paintGL";
	// QMutexLocker ml_v(&v_mutex);
	glEnable(GL_CULL_FACE);
	// glDisable(GL_CULL_FACE);
	camera()->apply(aspect);
	// objects_.preparePos(camera());
	start_rp.cam_offset_matrix = camera()->offsetMatrix();
	start_rp.proj_matrix       = getGLMatrix(GL_PROJECTION_MATRIX);
	start_rp.view_matrix       = getGLMatrix(GL_MODELVIEW_MATRIX);
	// objects_.buildTransform();

	/// Selection detect
	// glClearFramebuffer(QColor(100, 0, 0, 0));
	if (mouseSelect_) {
		glReleaseTextures();
		glEnableDepth();
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_TEXTURE_CUBE_MAP);
		glDisable(GL_MULTISAMPLE);
		glDisable(GL_LIGHTING);
		glDisable(GL_BLEND);
		glDisable(GL_ALPHA_TEST);
		glDisable(GL_RESCALE_NORMAL);
		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		fbo_selection.bind();
		fbo_selection.setWriteBuffer(0);
		glClearFramebuffer(QColor(0, 0, 0, 0));
		if (shaders_supported && shader_select->isLinked()) shader_select->bind();
		renderSelection();
		if (shaders_supported && shader_select->isLinked()) shader_select->release();
		uchar cgid[4]     = {0, 0, 0, 0};
		uint iid          = 0;
		GLObjectBase * so = nullptr;
		if (!g_rect.contains(lastPos)) {
			if (hov_obj != nullptr) {
				hov_obj = nullptr;
				emit hoverChanged(nullptr, hov_obj);
			}
		} else {
			glReadPixels(lastPos.x(), height() - lastPos.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, cgid);
			iid = uint(cgid[0] << 24) | uint(cgid[1] << 16) | uint(cgid[2] << 8) | cgid[3];
			so  = ids.value(iid, nullptr);
			// qDebug() <<cgid[0]<<cgid[1]<<cgid[2]<<cgid[3]<< iid;
			if (so != hov_obj) {
				emit hoverChanged(so, hov_obj);
				hov_obj = so;
			}
			// if (so != 0) qDebug() << sel_obj->name() << cgid[3];
		}
		if (selectionHalo_ && sel_obj) {
			fbo_selection.setWriteBuffer(2);
			renderHalo(sel_obj, qHash((quint64)sel_obj), selectionHaloColor_, selectionHaloFill_);
		}
		if (hoverHalo_ && hov_obj) {
			fbo_selection.setWriteBuffer(1);
			renderHalo(hov_obj, iid, hoverHaloColor_, hoverHaloFill_);
		}
		fbo_selection.release();
		glEnableDepth();
		/*glEnableClientState(GL_NORMAL_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);*/
	}

	camera()->apply(aspect);
	start_rp.cam_offset_matrix        = camera()->offsetMatrix();
	cur_mvpm                          = start_rp.proj_matrix * start_rp.view_matrix * start_rp.cam_offset_matrix;
	// objects_.preparePos(camera());

	static GLRendererBase * prev_rend = nullptr;
	glShadeModel(GL_SMOOTH);
	if (prev_rend != renderer_) {
		prev_rend = renderer_;
		if (renderer_ != nullptr) {
			renderer_->init(width(), height());
			renderer_->resize(width(), height());
			renderer_->reloadShaders();
		}
	}
	emit glBeginPaint();
	if (renderer_ != nullptr) {
		renderer_->rp.prepare();
		renderer_->prepareScene();
		renderer_->renderScene();
	}
	emit glPainting();
	glUseProgram(0);
	if (selectionHalo_ || hoverHalo_) {
		glReleaseTextures();
		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		// glClearFramebuffer(Qt::black, false);

		glActiveTexture(GL_TEXTURE0);
		glEnable(GL_BLEND);
		glDisable(GL_TEXTURE_CUBE_MAP);
		glDisable(GL_LIGHTING);
		glDisableDepth();
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		if (selectionHalo_ && sel_obj) {
			glBindTexture(GL_TEXTURE_2D, fbo_selection.colorTexture(2));
			// qDebug() << "draw sel";
			glDrawQuad();
		}
		if (hoverHalo_ && hov_obj) {
			glBindTexture(GL_TEXTURE_2D, fbo_selection.colorTexture(1));
			// qDebug() << "draw hover";
			// glBindTexture(GL_TEXTURE_2D, textures_manager->loadTexture("batt_pn.jpg"));
			glDrawQuad();
		}
	}

	glResetAllTransforms();
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glUseProgram(0);

	// glDisable(GL_BLEND);
	// glDisable(GL_LIGHTING);
	// glActiveTexture(GL_TEXTURE0);
	// glBindTexture(GL_TEXTURE_2D, textures_manager->loadTexture("batt_pn.jpg"));
	// glDrawQuad();

	emit glEndPaint();

	/*releaseShaders();
	glActiveTextureChannel(0);
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4f(0.3, 0.5, 0.8, 0.5);
	glResetAllTransforms();
	glBegin(GL_QUADS);
	glTexCoord2f(0.f, 0.f); glVertex2f(-1.f, -1.f);
	glTexCoord2f(1.f, 0.f); glVertex2f(1.f, -1.);
	glTexCoord2f(1.f, 1.f); glVertex2f(1.f, 1.f);
	glTexCoord2f(0.f, 1.f); glVertex2f(-1.f, 1.f);
	glEnd();*/
	/*
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glDisable(GL_LIGHTING);
	glActiveTextureChannel(0);
	glBindTexture(GL_TEXTURE_2D, fbo->texture());
	glDisable(GL_DEPTH_TEST);
	glBegin(GL_QUADS);
	glColor3f(1.f, 1.f, 1.f);
	glTexCoord2f(0.f, 0.f); glVertex2f(-1.f, -1.f);
	glTexCoord2f(0.f, 1.f); glVertex2f(-1.f, 1.f);
	glTexCoord2f(1.f, 1.f); glVertex2f(1.f, 1.f);
	glTexCoord2f(1.f, 0.f); glVertex2f(1.f, -1.);
	glEnd();
	glEnable(GL_DEPTH_TEST);*/
	fps_tm += time.elapsed();
	time.restart();
	fps_cnt++;
	if (fps_tm < 1000.) return;
	fps_    = fps_cnt / fps_tm * 1000.;
	fps_tm  = 0.;
	fps_cnt = 0;
}


void QGLView::initialize() {
	// initializeOpenGLFunctions();
	glEnable(GL_TEXTURE_MAX_ANISOTROPY_EXT);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glEnableDepth();
	glEnable(GL_CULL_FACE);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
	glActiveTexture(GL_TEXTURE0 + 3);
	glEnable(GL_TEXTURE_GEN_S);
	glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_TEXTURE_GEN_R);
	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
	glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
	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);
	glActiveTexture(GL_TEXTURE0);
	glShadeModel(GL_SMOOTH);
	glCullFace(GL_BACK);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_DIFFUSE);

	textures_manager->loadTextures();
	objects_.initInternal();
	checkCaps();

	shader_select = new QOpenGLShaderProgram(context());
	shader_halo   = new QOpenGLShaderProgram(context());
	reloadThisShaders();
	is_init    = true;
	// resizeGL(width(), height());
	need_init_ = false;
	emit glInitializeDone();
}


void QGLView::renderHalo(const GLObjectBase * obj, const uint iid, const QColor & color, const float & fill) {
	if (!shaders_supported) return;
	if (!shader_halo) return;
	if (!shader_halo->isLinked()) return;
	if (obj) {
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fbo_selection.colorTexture());
		shader_halo->bind();
		shader_halo->setUniformValue("qgl_ModelViewProjectionMatrix", QMatrix4x4());
		shader_halo->setUniformValue("t0", 0);
		shader_halo->setUniformValue("dt", QVector2D(1.f / width(), 1.f / height()));
		shader_halo->setUniformValue("selected",
		                             QVector4D(float((iid >> 24) & 0xFF) / 255.f,
		                                       float((iid >> 16) & 0xFF) / 255.f,
		                                       float((iid >> 8) & 0xFF) / 255.f,
		                                       float(iid & 0xFF) / 255.f));
		shader_halo->setUniformValue("color", color);
		shader_halo->setUniformValue("fill", GLfloat(fill));
		// qDebug() << "render halo" << iid << shader_halo->log() << shader_halo->programId();
		glDisableDepth();
		// glClearFramebuffer(color);
		glDrawQuad(shader_halo);
		glDepthMask(GL_TRUE);
		// glFlush();
		shader_halo->release();
	} else {
		glClearFramebuffer(Qt::black, false);
	}
}


void QGLView::renderSelection() {
	//	cid = 1;
	ids.clear();
	if (shaders_supported) {
		if (shader_select) {
			if (shader_select->isLinked()) {
				sh_id_loc = shader_select->uniformLocation("id");
				shader_select->setUniformValue("z_far", GLfloat(depthEnd()));
				shader_select->setUniformValue("z_near", GLfloat(depthStart()));
			}
		}
	}
	// qDebug() << sh_id_loc;
	start_rp.view_matrix = getGLMatrix(GL_MODELVIEW_MATRIX);
	glPushMatrix();
	renderSingleSelection(objects_);
	glPopMatrix();
}


void QGLView::renderSingleSelection(GLObjectBase & o) {
	if (!o.isInit()) {
		o.init();
		o.loadTextures();
	}
	if (!o.visible_ || !o.select_) return;
	QMatrix4x4 curview = start_rp.view_matrix * start_rp.cam_offset_matrix * o.itransform_;
	uint id            = qHash((quint64)&o);
	ids.insert(id, &o);
	glLineWidth(o.line_width > 0.f ? o.line_width : lineWidth_);
	glPointSize(o.line_width > 0.f ? o.line_width : lineWidth_);
	if (shaders_supported) {
		if (shader_select) {
			if (shader_select->isLinked()) {
				setUniformMatrices(shader_select, start_rp.proj_matrix, curview);
				shader_select->setUniformValue(sh_id_loc,
				                               QVector4D(float((id >> 24) & 0xFF) / 255.f,
				                                         float((id >> 16) & 0xFF) / 255.f,
				                                         float((id >> 8) & 0xFF) / 255.f,
				                                         float(id & 0xFF) / 255.f));
			}
		}
	} else {
		setGLMatrix(curview);
		glColor4f(float((id >> 24) & 0xFF) / 255.f,
		          float((id >> 16) & 0xFF) / 255.f,
		          float((id >> 8) & 0xFF) / 255.f,
		          float(id & 0xFF) / 255.f);
	}
	// qDebug() << o.name() << "assign to" << sh_id_loc << cid;
	// glColor4f(float((cid >> 24) & 0xFF) / 255.f, float((cid >> 16) & 0xFF) / 255.f, float((cid >> 8) & 0xFF) / 255.f, float(cid & 0xFF) /
	// 255.f);
	//	++cid;
	o.draw(nullptr, true);
	foreach(GLObjectBase * i, o.children_)
		renderSingleSelection(*i);
}


void QGLView::collectLights() {
	lights_.clear();
	collectObjectLights(&objects_);
}


void QGLView::objectDeleted(GLObjectBase * o) {
	if (deleting_) return;
	// qDebug() << "del" << o;
	if (sel_obj == o) selectObject(nullptr);
	if (hov_obj == o) hov_obj = nullptr;
	collectLights();
}


void QGLView::collectObjectLights(GLObjectBase * o) {
	if (o->type_ == GLObjectBase::glLight) {
		lights_ << globject_cast<Light *>(o);
		o->view_ = this;
	}
	foreach(GLObjectBase * i, o->children())
		collectObjectLights(i);
}


void QGLView::objectsCountInternal(int * cnt, GLObjectBase * where) {
	++(*cnt);
	foreach(GLObjectBase * i, where->children_)
		objectsCountInternal(cnt, i);
}


void QGLView::removeObjectInternal(GLObjectBase * o, GLObjectBase * where) {
	foreach(GLObjectBase * i, where->children_) {
		if (o == i)
			where->removeChild(i);
		else
			removeObjectInternal(o, i);
		objectDeleted(i);
	}
}


void QGLView::checkCaps() {
	glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropic);
	// glGetIntegerv(GL_MAX_TEXTURE_UNITS, &max_texture_chanels);
	// qDebug() << max_texture_chanels;
	// qDebug() << max_texture_chanels;
	shaders_supported = QOpenGLShaderProgram::hasOpenGLShaderPrograms();
}


void QGLView::reloadThisShaders() {
	if (!shaders_supported) return;
	loadShaders(shader_select, "selection", "://shaders");
	loadShaders(shader_halo, "selection_halo", "://shaders");
	// loadShaders(shader_rope, "rope", "://shaders");
}

void QGLView::glReleaseTextures(int channels) {
	for (int i = channels - 1; i >= 0; --i) {
		glActiveTexture(GL_TEXTURE0 + i);
		glBindTexture(GL_TEXTURE_2D, 0);
		glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
	}
}


void QGLView::applyFog() {
	GLfloat fog_col[4] = {float(fogColor_.redF()), float(fogColor_.greenF()), float(fogColor_.blueF()), .0f};
	if (fogEnabled_) {
		glEnable(GL_FOG);
		glFogf(GL_FOG_DENSITY, fogDensity_);
		glFogf(GL_FOG_START, fogStart_);
		glFogf(GL_FOG_END, fogEnd_);
		glFogi(GL_FOG_MODE, fogMode_);
		fog_col[0] = fogColor_.redF();
		fog_col[1] = fogColor_.greenF();
		fog_col[2] = fogColor_.blueF();
		glFogfv(GL_FOG_COLOR, fog_col);
	} else
		glDisable(GL_FOG);
}


void QGLView::resizeGL(int width, int height) {
	if (!is_init) return;
	if (width <= 0 || height <= 0) return;
	if (prev_size == QSize(width, height)) return;
	prev_size = QSize(width, height);
	aspect    = float(width) / float(height);
	if (renderer_) renderer_->resize(width, height);
	mouse_first = true;
	// qDebug() << "resize" << width << height;
	fbo_selection.resize(width, height);
	iaspect = (aspect == 0.f) ? 0. : 1 / aspect;
	glViewport(0, 0, width, height);
	emit glResize(width, height);
}


void QGLView::mouseReleaseEvent(QMouseEvent * e) {
	//	qDebug() << "mouseReleaseEvent" << e << isActive();
	//	QGraphicsView::mouseReleaseEvent(e);
	// setCursor(QCursor(Qt::ArrowCursor));
	selecting_ = false;
	if (mouseSelect_ && e->button() == Qt::LeftButton) {
		if ((lastPos - downPos).manhattanLength() < 8) {
			if (sel_obj != hov_obj) selectObject(hov_obj);
		}
	}
	emit glMouseReleaseEvent(e);
}


void QGLView::mousePressEvent(QMouseEvent * e) {
	//	qDebug() << "mousePressEvent" << e << isActive();
	//	QGraphicsView::mousePressEvent(e);
	//	mouseThis_ = (scene()->itemAt(mapToScene(e->pos()) , QTransform() ) == 0);
	selecting_ = false;
	if (!QRect(QPoint(), size()).contains(e->pos())) return;
	/// TODO select by rect
	// if (e->button() == sel_button && e->modifiers() == sel_mod)
	//	selecting_ = true;
	lastPos = e->pos();
	downPos = lastPos;
	// qDebug() << mouseThis_;
	emit glMousePressEvent(e);
}


void QGLView::mouseMoveEvent(QMouseEvent * e) {
	//	qDebug() << "mouseMoveEvent" << e << isActive();
	//	QGraphicsView::mouseMoveEvent(e);
	// lastPos = e->pos();
	if (selecting_) {
		return;
	}
	//	if (!QRect(QPoint(), size()).contains(e->pos())) return;
	// if (scene()->itemAt(mapToScene(e->pos())) != 0) return;
	/// qDebug() << e->x() << e->y();
	QRect g_rect(QPoint(), size());
	if (mouseRotate_) {
		float dx = e->x() - lastPos.x();
		float dy = e->y() - lastPos.y();
		if (e->buttons() & Qt::LeftButton) {
			// camera().angle_z += dx / 4.;
			// camera().angle_xy += dy / 4.;
			if (cameraOrbit_) {
				camera()->orbitZ(dx / 4.f);
				camera()->orbitXY(dy / 4.f);
			} else {
				camera()->rotateZ(dx / 4.f);
				camera()->rotateXY(dy / 4.f);
			}
			emit cameraPosChanged(camera()->pos());
		} else if (e->buttons() & Qt::RightButton) {
			float ad = camera()->distance();
			camera()->moveLeft(dx / 1000.f * ad);
			camera()->moveUp(dy / 1000.f * ad);
			// camera().pos.setX(camera().pos.x() + camera().pos.z() * dx / 500.);
			// camera().pos.setY(camera().pos.y() - camera().pos.z() * dy / 500.);
			emit cameraPosChanged(camera()->pos());
		}
		// lights[0]->pos_ = camera().pos();
	}
	if (customMouseMove_) emit customMouseMoveEvent(e->pos(), lastPos, e->buttons());
	lastPos = e->pos();
	if (grabMouse_) {
		// if (!isrunning) return;
		QCursor::setPos(mapToGlobal(QRect(QPoint(), size()).center()));
		static bool mouse_sec = false;
		if (mouse_sec) {
			mouse_sec = false;
			return;
		}
		if (mouse_first) {
			mouse_first = false;
			mouse_sec   = true;
			// qDebug() << "first" << e->pos();
			return;
		}
		lastPos = g_rect.center();
		int dx  = e->x() - lastPos.x();
		int dy  = e->y() - lastPos.y();
		emit glMouseMoveEvent(new QMouseEvent(QEvent::MouseMove, QPoint(dx, dy), e->button(), e->buttons(), e->modifiers()));
		return;
	}
	emit glMouseMoveEvent(e);
}


void QGLView::wheelEvent(QWheelEvent * e) {
	if (mouseRotate_) {
		double ang =
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
			e->delta();
#else
			e->angleDelta().y();
#endif
		if (ang > 0) camera()->flyCloser(0.1f); // camera().pos.setZ(camera().pos.z() - 0.1 * camera().pos.z());
		if (ang < 0) camera()->flyFarer(0.1f);  // camera().pos.setZ(camera().pos.z() + 0.1 * camera().pos.z());
		emit cameraPosChanged(camera()->pos());
	}
	emit glWheelEvent(e);
}


void QGLView::leaveEvent(QEvent *) {
	lastPos = QPoint(-1, -1);
	// qDebug() << lastPos;
}


void QGLView::keyPressEvent(QKeyEvent * e) {
	emit glKeyPressEvent(e);
	if (e->key() > 0) keys_.insert(e->key());
	if (e->key() == Qt::Key_F11) {
		emit doubleClick();
	}
}


void QGLView::keyReleaseEvent(QKeyEvent * e) {
	emit glKeyReleaseEvent(e);
	keys_.remove(e->key());
}


void QGLView::focusOutEvent(QFocusEvent *) {
	keys_.clear();
}


void QGLView::mouseDoubleClickEvent(QMouseEvent * e) {
	if (e->buttons().testFlag(QT_MID_BUTTON)) emit doubleClick();
}


QByteArray QGLView::saveCamera() {
	ChunkStream cs;
	const Camera * c = camera();
	cs.add(1, c->posX());
	cs.add(2, c->posY());
	cs.add(3, c->posZ());
	cs.add(4, c->aim().x());
	cs.add(5, c->aim().y());
	cs.add(6, c->aim().z());
	cs.add(7, c->angleZ());
	cs.add(8, c->angleXY());
	cs.add(9, c->angleRoll());
	cs.add(10, c->FOV());
	return cs.data();
}


void QGLView::restoreCamera(const QByteArray & ba) {
	if (ba.isEmpty()) return;
	ChunkStream cs(ba);
	QVector3D pos, aim, ang;
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: pos.setX(cs.getData<float>()); break;
		case 2: pos.setY(cs.getData<float>()); break;
		case 3: pos.setZ(cs.getData<float>()); break;
		case 4: aim.setX(cs.getData<float>()); break;
		case 5: aim.setY(cs.getData<float>()); break;
		case 6: aim.setZ(cs.getData<float>()); break;
		case 7: ang.setZ(cs.getData<float>()); break;
		case 8: ang.setY(cs.getData<float>()); break;
		case 9: ang.setX(cs.getData<float>()); break;
		case 10: setFOV(cs.getData<float>()); break;
		default: break;
		}
	}
	camera()->setPos(pos);
	camera()->setAim(aim);
	camera()->setAngles(ang);
}


QByteArray QGLView::saveFeatures() {
	QByteArray ba;
	QDataStream ds(&ba, QIODevice::WriteOnly);
	ds << features_;
	return ba;
}


void QGLView::restoreFeatures(const QByteArray & ba) {
	QHash<int, QVariant> f;
	QDataStream ds(ba);
	ds >> f;
	features_ = f;
}
