1 /**
2  * Mandelbulber v2, a 3D fractal generator       ,=#MKNmMMKmmßMNWy,
3  *                                             ,B" ]L,,p%%%,,,§;, "K
4  * Copyright (C) 2014-21 Mandelbulber Team     §R-==%w["'~5]m%=L.=~5N
5  *                                        ,=mm=§M ]=4 yJKA"/-Nsaj  "Bw,==,,
6  * This file is part of Mandelbulber.    §R.r= jw",M  Km .mM  FW ",§=ß., ,TN
7  *                                     ,4R =%["w[N=7]J '"5=],""]]M,w,-; T=]M
8  * Mandelbulber is free software:     §R.ß~-Q/M=,=5"v"]=Qf,'§"M= =,M.§ Rz]M"Kw
9  * you can redistribute it and/or     §w "xDY.J ' -"m=====WeC=\ ""%""y=%"]"" §
10  * modify it under the terms of the    "§M=M =D=4"N #"%==A%p M§ M6  R' #"=~.4M
11  * GNU General Public License as        §W =, ][T"]C  §  § '§ e===~ U  !§[Z ]N
12  * published by the                    4M",,Jm=,"=e~  §  §  j]]""N  BmM"py=ßM
13  * Free Software Foundation,          ]§ T,M=& 'YmMMpM9MMM%=w=,,=MT]M m§;'§,
14  * either version 3 of the License,    TWw [.j"5=~N[=§%=%W,T ]R,"=="Y[LFT ]N
15  * or (at your option)                   TW=,-#"%=;[  =Q:["V""  ],,M.m == ]N
16  * any later version.                      J§"mr"] ,=,," =="""J]= M"M"]==ß"
17  *                                          §= "=C=4 §"eM "=B:m|4"]#F,§~
18  * Mandelbulber is distributed in            "9w=,,]w em%wJ '"~" ,=,,ß"
19  * the hope that it will be useful,                 . "K=  ,=RMMMßM"""
20  * but WITHOUT ANY WARRANTY;                            .'''
21  * without even the implied warranty
22  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23  *
24  * See the GNU General Public License for more details.
25  * You should have received a copy of the GNU General Public License
26  * along with Mandelbulber. If not, see <http://www.gnu.org/licenses/>.
27  *
28  * ###########################################################################
29  *
30  * Authors: Krzysztof Marczak (buddhi1980@gmail.com)
31  *
32  * RenderedImage class - extension for QWidget class. Widget prepared for displaying rendered image
33  * and 3D cursor
34  */
35 
36 #include "rendered_image_widget.hpp"
37 
38 #include <QApplication>
39 #include <QKeyEvent>
40 #include <QMouseEvent>
41 #include <QPainter>
42 #include <QStaticText>
43 #include <QVariant>
44 
45 #include "animation_path_data.hpp"
46 #include "camera_movement_modes.h"
47 #include "cimage.hpp"
48 #include "common_math.h"
49 #include "fractparams.hpp"
50 #include "light.h"
51 #include "nine_fractals.hpp"
52 #include "parameters.hpp"
53 #include "primitives.h"
54 #include "trace_behind.h"
55 
56 using namespace Qt;
57 
RenderedImage(QWidget * parent)58 RenderedImage::RenderedImage(QWidget *parent) : QWidget(parent)
59 {
60 	// makes RenderedImage focusable to catch keyboard events
61 	setFocusPolicy(Qt::StrongFocus);
62 	setMouseTracking(true);
63 
64 	image = nullptr;
65 	params = nullptr;
66 	fractals = nullptr;
67 	cursorVisible = true;
68 	lightsVisible = false;
69 	smoothLastZMouse = 0.0;
70 	redrawed = true;
71 	isFocus = false;
72 	isOnObject = false;
73 	lastDepth = 0.0;
74 	frontDist = 0.0;
75 	flightRotationDirection = 0;
76 	clickMode = clickDoNothing;
77 	anaglyphMode = false;
78 	gridType = gridTypeCrosshair;
79 	placeLightBehind = false;
80 	clickModesEnables = true;
81 	draggingStarted = false;
82 	draggingInitStarted = false;
83 	buttonsPressed = 0;
84 	currentLightIndex = 1;
85 
86 	QList<QVariant> mode;
87 	mode.append(int(RenderedImage::clickDoNothing));
88 	clickModeData = mode;
89 	cameraMovementMode = cameraMovementEnums::fixedDistance;
90 
91 	// timer to refresh image
92 	timerRefreshImage = new QTimer(this);
93 	timerRefreshImage->setInterval(40);
94 	connect(timerRefreshImage, SIGNAL(timeout()), this, SLOT(update()));
95 }
96 
paintEvent(QPaintEvent * event)97 void RenderedImage::paintEvent(QPaintEvent *event)
98 {
99 	(void)event;
100 
101 	if (image)
102 	{
103 		CVector2<int> point = lastMousePosition / image->GetPreviewScale();
104 		float z;
105 		if (point.x >= 0 && point.y >= 0 && point.x < int(image->GetWidth())
106 				&& point.y < int(image->GetHeight()))
107 			z = image->GetPixelZBuffer(point.x, point.y);
108 		else
109 			z = float(1e20);
110 
111 		if (params)
112 		{
113 			if ((cursorVisible && isFocus) || gridType != gridTypeCrosshair)
114 			{
115 				if (!anaglyphMode) DisplayCrosshair();
116 			}
117 
118 			if (lightsVisible)
119 			{
120 				DisplayAllLights();
121 			}
122 
123 			if (cursorVisible && isFocus)
124 			{
125 				if (z < 1e10 || enumClickMode(clickModeData.at(0).toInt()) == clickFlightSpeedControl)
126 				{
127 					redrawed = false;
128 					if (!isOnObject)
129 					{
130 						QApplication::setOverrideCursor(Qt::BlankCursor);
131 					}
132 					isOnObject = true;
133 
134 					Display3DCursor(lastMousePosition, z);
135 				}
136 				else
137 				{
138 					if (isOnObject)
139 					{
140 						QApplication::restoreOverrideCursor();
141 					}
142 					isOnObject = false;
143 				}
144 			}
145 		}
146 
147 		if (params && animationPathData.animationPath.length() > 0)
148 		{
149 			DrawAnimationPath();
150 		}
151 
152 		image->RedrawInWidget();
153 
154 		if (params)
155 		{
156 			if (cursorVisible && isFocus && !anaglyphMode
157 					&& (isOnObject || enumClickMode(clickModeData.at(0).toInt()) == clickFlightSpeedControl))
158 			{
159 				DisplayCoordinates();
160 			}
161 		}
162 
163 		if (params && cursorVisible && clickMode != clickFlightSpeedControl)
164 		{
165 			CVector3 rotation = params->Get<CVector3>("camera_rotation") / 180.0 * M_PI;
166 			Compass(rotation, QPointF(image->GetPreviewWidth() * 0.9, image->GetPreviewHeight() * 0.9),
167 				image->GetPreviewHeight() * 0.05);
168 		}
169 
170 		if (params)
171 		{
172 			if (clickMode == clickFlightSpeedControl)
173 			{
174 				Compass(flightData.rotation,
175 					QPointF(image->GetPreviewWidth() * 0.5, image->GetPreviewHeight() * 0.5),
176 					image->GetPreviewHeight() * 0.2);
177 			}
178 		}
179 
180 		PaintLastRenderedTilesInfo();
181 
182 		redrawed = true;
183 	}
184 	else
185 	{
186 		qCritical() << "RenderedImage::mouseMoveEvent(QMouseEvent * event): image not assigned";
187 	}
188 }
189 
DisplayCoordinates()190 void RenderedImage::DisplayCoordinates()
191 {
192 	QPainter painter(this);
193 	painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
194 
195 	QPen penWhite(Qt::white, 1, Qt::SolidLine);
196 	QBrush brushBrown(QColor(100, 50, 0));
197 	QBrush brushDarkBlue(QColor(0, 0, 100));
198 
199 	QString text;
200 	enumClickMode clickMode = enumClickMode(clickModeData.at(0).toInt());
201 	switch (clickMode)
202 	{
203 		case clickMoveCamera:
204 		{
205 			switch (cameraMovementEnums::enumCameraMovementMode(cameraMovementMode))
206 			{
207 				case cameraMovementEnums::fixedDistance: text = tr("Move camera and target"); break;
208 				case cameraMovementEnums::moveCamera: text = tr("Move camera"); break;
209 				case cameraMovementEnums::moveTarget: text = tr("Move target"); break;
210 			}
211 			break;
212 		}
213 		case clickFogVisibility: text = tr("Change fog visibility"); break;
214 		case clickDOFFocus: text = tr("Change DOF focus"); break;
215 		case clickPlaceLight:
216 			text = tr("Place light #") + QString::number(clickModeData.at(1).toInt());
217 			text += tr("\nCtrl + Mouse wheel - light fwd/bkwd ");
218 			text += tr("\nAlt + Mouse wheel - placement fwd/bkwd ");
219 			break;
220 		case clickPlacePrimitive:
221 			text = tr("Place ") + PrimitiveNames(fractal::enumObjectType(clickModeData.at(1).toInt()))
222 						 + QString(" #") + QString::number(clickModeData.at(2).toInt());
223 			break;
224 		case clickGetJuliaConstant: text = tr("Get Julia constant"); break;
225 		case clickFlightSpeedControl:
226 			text = tr("LMB - increase speed");
227 			text += tr("\nRMB - decrease speed");
228 			text += tr("\narrow keys - sidewards");
229 			text += tr("\nz, x keys - roll");
230 			text += tr("\nspacebar - pause");
231 			text += tr("\nhold shift key - orthogonal move");
232 			break;
233 		case clickDoNothing: text = ""; break;
234 		case clickPlaceRandomLightCenter:
235 			text = tr("Place center of random light");
236 			text += tr("\nalso calculates");
237 			text += tr("\ndistribution radius of lights to 50%,");
238 			text += tr("\nmax distance from fractal to 10%");
239 			text += tr("\nof distance [center to camera position]");
240 			break;
241 		case clickGetPoint:
242 			text = tr("Get coordinates");
243 			text += tr("\nand distance");
244 			break;
245 		case clickWrapLimitsAroundObject: text = tr("Wrap limits\naround object"); break;
246 	}
247 
248 	if (clickMode != clickDoNothing)
249 	{
250 		QRect textRect = painter.boundingRect(QRect(), Qt::AlignTop | Qt::AlignLeft, text);
251 		textRect.setHeight(textRect.height() + 2);
252 		textRect.moveBottomLeft(QPoint(lastMousePosition.x + 30, lastMousePosition.y - 3));
253 
254 		painter.setOpacity(0.8);
255 		painter.setPen(penWhite);
256 		painter.setBrush(brushBrown);
257 		painter.drawRoundedRect(textRect, 3, 3);
258 		painter.drawText(textRect, Qt::AlignTop | Qt::AlignLeft, text);
259 	}
260 
261 	QString textCoordinates;
262 	if (clickMode != clickFlightSpeedControl)
263 	{
264 		textCoordinates += "x: " + QString::number(lastCoordinates.x, 'g', 15);
265 		textCoordinates += "\ny: " + QString::number(lastCoordinates.y, 'g', 15);
266 		textCoordinates += "\nz: " + QString::number(lastCoordinates.z, 'g', 15);
267 		textCoordinates += "\ndist: " + QString::number(lastDepth, 'g', 15);
268 	}
269 	else
270 	{
271 		textCoordinates += "frame: " + QString::number(flightData.frame);
272 		textCoordinates += "\nx: " + QString::number(flightData.camera.x, 'g', 15);
273 		textCoordinates += "\ny: " + QString::number(flightData.camera.y, 'g', 15);
274 		textCoordinates += "\nz: " + QString::number(flightData.camera.y, 'z', 15);
275 		textCoordinates += "\ndist: " + QString::number(flightData.distance);
276 		textCoordinates += "\nspeed act: " + QString::number(flightData.speed);
277 		textCoordinates += "\nspeed set: " + QString::number(flightData.speedSp);
278 	}
279 
280 	QRect textRect2 = painter.boundingRect(QRect(), Qt::AlignTop | Qt::AlignLeft, textCoordinates);
281 	textRect2.setHeight(textRect2.height() + 2);
282 	textRect2.moveTopLeft(QPoint(lastMousePosition.x + 30, lastMousePosition.y + 3));
283 	painter.setOpacity(0.8);
284 	painter.setPen(penWhite);
285 	painter.setBrush(brushDarkBlue);
286 	painter.drawRoundedRect(textRect2, 3, 3);
287 	painter.drawText(textRect2, Qt::AlignTop | Qt::AlignLeft, textCoordinates);
288 }
289 
Display3DCursor(CVector2<int> screenPoint,double z)290 void RenderedImage::Display3DCursor(CVector2<int> screenPoint, double z)
291 {
292 	clickMode = enumClickMode(clickModeData.at(0).toInt());
293 	if (clickMode == clickPlaceLight)
294 	{
295 		if (!placeLightBehind)
296 		{
297 			z -= frontDist;
298 		}
299 	}
300 
301 	double diff = z - smoothLastZMouse;
302 	if (fabs(diff) >= 1.0)
303 	{
304 		smoothLastZMouse += diff * 0.01;
305 	}
306 	else
307 	{
308 		double delta = sqrt(fabs(diff)) * 0.01;
309 		smoothLastZMouse += (diff > 0 ? 1.0 : -1.0) * fmin(delta, fabs(diff));
310 	}
311 
312 	if (z > 0 && clickMode != clickFlightSpeedControl)
313 	{
314 		if (smoothLastZMouse < 0.0) smoothLastZMouse = 0.0;
315 
316 		bool legacyCoordinateSystem = params->Get<bool>("legacy_coordinate_system");
317 		double reverse = legacyCoordinateSystem ? -1.0 : 1.0;
318 
319 		// preparing rotation matrix
320 		CVector3 rotation = params->Get<CVector3>("camera_rotation") / 180.0 * M_PI;
321 		double sweetSpotHAngle = params->Get<double>("sweet_spot_horizontal_angle") / 180.0 * M_PI;
322 		double sweetSpotVAngle = params->Get<double>("sweet_spot_vertical_angle") / 180.0 * M_PI;
323 
324 		bool stereoEnabled = params->Get<bool>("stereo_enabled");
325 		cStereo::enumStereoMode stereoMode = cStereo::enumStereoMode(params->Get<int>("stereo_mode"));
326 		anaglyphMode = stereoMode == cStereo::stereoRedCyan && stereoEnabled;
327 		double stereoEyeDistance = params->Get<double>("stereo_eye_distance");
328 		double stereoInfiniteCorrection = params->Get<double>("stereo_infinite_correction");
329 		double distanceLimit = params->Get<double>("view_distance_max");
330 
331 		params::enumPerspectiveType perspType =
332 			params::enumPerspectiveType(params->Get<int>("perspective_type"));
333 		CVector3 camera = params->Get<CVector3>("camera");
334 
335 		CRotationMatrix mRot;
336 		mRot.RotateZ(rotation.x);
337 		mRot.RotateX(rotation.y);
338 		mRot.RotateY(rotation.z);
339 		mRot.RotateZ(-sweetSpotHAngle);
340 		mRot.RotateX(sweetSpotVAngle);
341 
342 		double fov = CalcFOV(params->Get<double>("fov"), perspType);
343 
344 		double sw = image->GetPreviewWidth();
345 		double sh = image->GetPreviewHeight();
346 		double aspectRatio = sw / sh;
347 
348 		if (perspType == params::perspEquirectangular) aspectRatio = 2.0;
349 
350 		CVector2<double> p;
351 		p.x = (screenPoint.x / sw - 0.5) * aspectRatio;
352 		p.y = (screenPoint.y / sh - 0.5);
353 
354 		double scale = smoothLastZMouse / z;
355 
356 		// calculate 3D point coordinates
357 		CVector2<double> pTemp = p;
358 		pTemp.y *= -1.0 * reverse;
359 		CVector3 viewVector = CalculateViewVector(pTemp, fov, perspType, mRot);
360 		CVector3 point = camera + viewVector * z;
361 
362 		if (clickMode == clickPlaceLight)
363 		{
364 			if (placeLightBehind)
365 			{
366 				double distanceBehind = traceBehindFractal(
367 					params, fractals, frontDist, viewVector, z, 1.0 / image->GetHeight(), distanceLimit);
368 				z += distanceBehind;
369 			}
370 		}
371 
372 		lastCoordinates = point;
373 
374 		if (anaglyphMode)
375 		{
376 			CVector2<float> p1, p2;
377 			p1.x = p.x;
378 			p1.y = p.y;
379 			Draw3DBox(scale, fov, p1, z, cStereo::eyeLeft);
380 			if (perspType == params::perspThreePoint || perspType == params::perspFishEye
381 					|| perspType == params::perspFishEyeCut)
382 			{
383 				p2.x = p.x - 2.0 * (stereoEyeDistance / z - stereoInfiniteCorrection / 10.0) / fov;
384 			}
385 			else
386 			{
387 				p2.x = p.x - 2.0 * (stereoEyeDistance / z - stereoInfiniteCorrection / 10.0) / fov * 2.0;
388 			}
389 			p2.y = p.y;
390 			Draw3DBox(scale, fov, p2, z, cStereo::eyeRight);
391 		}
392 		else
393 		{
394 			Draw3DBox(scale, fov, CVector2<float>(p.x, p.y), z, cStereo::eyeNone);
395 		}
396 	}
397 	else if (clickMode == clickFlightSpeedControl)
398 	{
399 		// draw small cross
400 		image->AntiAliasedLine(screenPoint.x - 20, screenPoint.y - 20, screenPoint.x + 20,
401 			screenPoint.y + 20, -1, -1, sRGB8(255, 255, 255), sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
402 		image->AntiAliasedLine(screenPoint.x + 20, screenPoint.y - 20, screenPoint.x - 20,
403 			screenPoint.y + 20, -1, -1, sRGB8(255, 255, 255), sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
404 	}
405 	lastDepth = z;
406 }
407 
Draw3DBox(float scale,float fov,CVector2<float> p,float z,cStereo::enumEye eye) const408 void RenderedImage::Draw3DBox(
409 	float scale, float fov, CVector2<float> p, float z, cStereo::enumEye eye) const
410 {
411 	float sw = image->GetPreviewWidth();
412 	float sh = image->GetPreviewHeight();
413 
414 	float aspectRatio = sw / sh;
415 
416 	float boxWidth = 10.0f / sw * scale;
417 	float boxHeight = 10.0f / sw * scale;
418 	float boxDepth = 10.0f / sw * scale;
419 
420 	float boxDepth2 = boxHeight * z * fov;
421 
422 	float n = 3.0;
423 	int in = int(n);
424 
425 	sRGBFloat opacity;
426 	switch (eye)
427 	{
428 		case cStereo::eyeNone: opacity = sRGBFloat(0.8f, 0.8f, 0.8f); break;
429 		case cStereo::eyeLeft: opacity = sRGBFloat(0.8f, 0.0f, 0.0f); break;
430 		case cStereo::eyeRight: opacity = sRGBFloat(0.0f, 0.8f, 0.8f); break;
431 	}
432 
433 	unsigned char R, G, B;
434 	for (int iz = -in; iz <= in; iz++)
435 	{
436 		float yy1 = ((p.y + n * boxHeight) / (1.0f - boxDepth * iz * fov) + 0.5f) * sh;
437 		float yy2 = ((p.y - n * boxHeight) / (1.0f - boxDepth * iz * fov) + 0.5f) * sh;
438 		for (int ix = -in; ix <= in; ix++)
439 		{
440 			float xx1 = ((p.x + boxWidth * ix) / (1.0f - boxDepth * iz * fov) / aspectRatio + 0.5f) * sw;
441 			if (eye == cStereo::eyeNone)
442 			{
443 				R = uchar(128 + iz * 40);
444 				G = uchar(128 - iz * 40);
445 				B = 0;
446 			}
447 			else
448 			{
449 				R = uchar(128 + iz * 40);
450 				G = uchar(128 + iz * 40);
451 				B = uchar(128 + iz * 40);
452 			}
453 			if (iz == 0 && ix == 0)
454 			{
455 				R = G = B = 255;
456 				// opacity = 1.0;
457 			}
458 			image->AntiAliasedLine(xx1, yy1, xx1, yy2, z - iz * boxDepth2, z - iz * boxDepth2,
459 				sRGB8(R, G, B), opacity, 1.0f, 1);
460 		}
461 
462 		float xx1 = ((p.x + n * boxWidth) / (1.0f - boxDepth * iz * fov) / aspectRatio + 0.5f) * sw;
463 		float xx2 = ((p.x - n * boxWidth) / (1.0f - boxDepth * iz * fov) / aspectRatio + 0.5f) * sw;
464 		for (int iy = -in; iy <= in; iy++)
465 		{
466 			float yyn1 = ((p.y + boxWidth * iy) / (1.0f - boxDepth * iz * fov) + 0.5f) * sh;
467 
468 			if (eye == cStereo::eyeNone)
469 			{
470 				R = uchar(128 + iz * 40);
471 				G = uchar(128 - iz * 40);
472 				B = 0;
473 			}
474 			else
475 			{
476 				R = uchar(128 + iz * 40);
477 				G = uchar(128 + iz * 40);
478 				B = uchar(128 + iz * 40);
479 			}
480 
481 			if (iz == 0 && iy == 0)
482 			{
483 				R = G = B = 255;
484 				// opacity = 1.0;
485 			}
486 
487 			image->AntiAliasedLine(xx1, yyn1, xx2, yyn1, z - iz * boxDepth2, z - iz * boxDepth2,
488 				sRGB8(R, G, B), opacity, 1.0f, 1);
489 		}
490 
491 		if (iz < n)
492 		{
493 			for (int ix = -in; ix <= in; ix++)
494 			{
495 				for (int iy = -in; iy <= in; iy++)
496 				{
497 					float xxn1 =
498 						((p.x + boxWidth * ix) / (1.0f - boxDepth * iz * fov) / aspectRatio + 0.5f) * sw;
499 					float yyn1 = ((p.y + boxWidth * iy) / (1.0f - boxDepth * iz * fov) + 0.5f) * sh;
500 					float xxn2 =
501 						((p.x + boxWidth * ix) / (1.0f - boxDepth * (iz + 1) * fov) / aspectRatio + 0.5f) * sw;
502 					float yyn2 = ((p.y + boxWidth * iy) / (1.0f - boxDepth * (iz + 1) * fov) + 0.5f) * sh;
503 
504 					if (eye == cStereo::eyeNone)
505 					{
506 						R = uchar(128 + iz * 40);
507 						G = uchar(128 - iz * 40);
508 						B = 0;
509 					}
510 					else
511 					{
512 						R = uchar(128 + iz * 40);
513 						G = uchar(128 + iz * 40);
514 						B = uchar(128 + iz * 40);
515 					}
516 
517 					if (ix == 0 && iy == 0)
518 					{
519 						R = G = B = 255;
520 						// opacity = 1.0;
521 					}
522 
523 					image->AntiAliasedLine(xxn1, yyn1, xxn2, yyn2, z - iz * boxDepth2,
524 						z - (iz + 1) * boxDepth2, sRGB8(R, G, B), opacity, 1.0f, 1);
525 				}
526 			}
527 		}
528 		if (iz == 0)
529 		{
530 			CVector2<float> sPoint((p.x / aspectRatio + 0.5f) * sw, (p.y + 0.5f) * sh);
531 			image->AntiAliasedLine(sPoint.x - sw * 0.3f, sPoint.y, sPoint.x + sw * 0.3f, sPoint.y, z, z,
532 				sRGB8(255, 255, 255), opacity, 1.0f, 1);
533 			image->AntiAliasedLine(sPoint.x, sPoint.y - sh * 0.3f, sPoint.x, sPoint.y + sh * 0.3f, z, z,
534 				sRGB8(255, 255, 255), opacity, 1.0f, 1);
535 			if (anaglyphMode)
536 			{
537 				image->AntiAliasedLine(sPoint.x - sw * 0.05f, sPoint.y - sh * 0.05f, sPoint.x + sw * 0.05f,
538 					sPoint.y - sh * 0.05f, z, z, sRGB8(0, 0, 0), opacity, 1.0f, 1);
539 				image->AntiAliasedLine(sPoint.x + sw * 0.05f, sPoint.y - sh * 0.05f, sPoint.x + sw * 0.05f,
540 					sPoint.y + sh * 0.05f, z, z, sRGB8(0, 0, 0), opacity, 1.0f, 1);
541 				image->AntiAliasedLine(sPoint.x + sw * 0.05f, sPoint.y + sh * 0.05f, sPoint.x - sw * 0.05f,
542 					sPoint.y + sh * 0.05f, z, z, sRGB8(0, 0, 0), opacity, 1.0f, 1);
543 				image->AntiAliasedLine(sPoint.x - sw * 0.05f, sPoint.y + sh * 0.05f, sPoint.x - sw * 0.05f,
544 					sPoint.y - sh * 0.05f, z, z, sRGB8(0, 0, 0), opacity, 1.0f, 1);
545 			}
546 
547 			if (clickMode == clickPlaceLight)
548 			{
549 				float r = 1.5f * (boxWidth * n / aspectRatio);
550 				if (r > 1.0f) r = 1.0f;
551 				image->CircleBorder(
552 					sPoint.x, sPoint.y, z, r * sw, sRGB8(0, 100, 255), r * 0.1f * sw, opacity, 1);
553 			}
554 		}
555 	}
556 }
557 
mouseMoveEvent(QMouseEvent * event)558 void RenderedImage::mouseMoveEvent(QMouseEvent *event)
559 {
560 	CVector2<int> screenPoint(event->x(), event->y());
561 
562 	// remember last mouse position
563 	lastMousePosition = screenPoint;
564 
565 	CVector2<double> yawAndPitch;
566 	yawAndPitch.x = (double(lastMousePosition.x) / image->GetPreviewWidth() - 0.5) * 2.0;
567 	yawAndPitch.y = (double(lastMousePosition.y) / image->GetPreviewHeight() - 0.5) * 2.0;
568 	emit YawAndPitchChanged(yawAndPitch);
569 
570 	if (params)
571 	{
572 		if (cursorVisible && isFocus && redrawed)
573 		{
574 			update();
575 		}
576 	}
577 	else
578 	{
579 		if (cursorVisible)
580 			qCritical() << "RenderedImage::mouseMoveEvent(QMouseEvent * event): parameters not assigned";
581 	}
582 
583 	emit mouseMoved(screenPoint.x, screenPoint.y);
584 
585 	if (draggingInitStarted)
586 	{
587 		if (abs(screenPoint.x - dragStartPosition.x) > 1
588 				|| abs(screenPoint.y - dragStartPosition.y) > 1)
589 		{
590 			draggingInitStarted = false;
591 			draggingStarted = true;
592 			emit mouseDragStart(dragStartPosition.x, dragStartPosition.y, dragButtons);
593 		}
594 	}
595 
596 	if (draggingStarted)
597 	{
598 		int dx = screenPoint.x - dragStartPosition.x;
599 		int dy = screenPoint.y - dragStartPosition.y;
600 		emit mouseDragDelta(dx, dy);
601 	}
602 }
603 
mousePressEvent(QMouseEvent * event)604 void RenderedImage::mousePressEvent(QMouseEvent *event)
605 {
606 	if (enumClickMode(clickModeData.at(0).toInt()) == clickFlightSpeedControl)
607 	{
608 		if (event->button() == Qt::LeftButton)
609 		{
610 			emit SpeedChanged(1.1);
611 		}
612 		else if (event->button() == Qt::RightButton)
613 		{
614 			emit SpeedChanged(0.9);
615 		}
616 	}
617 	else
618 	{
619 		if (clickModesEnables)
620 		{
621 			draggingInitStarted = true;
622 			dragStartPosition = CVector2<int>(event->x(), event->y());
623 			dragButtons = event->buttons();
624 		}
625 	}
626 	buttonsPressed++;
627 }
628 
mouseReleaseEvent(QMouseEvent * event)629 void RenderedImage::mouseReleaseEvent(QMouseEvent *event)
630 {
631 	if (!draggingStarted && enumClickMode(clickModeData.at(0).toInt()) != clickFlightSpeedControl)
632 	{
633 		if (clickModesEnables)
634 		{
635 			emit singleClick(event->x(), event->y(), event->button());
636 		}
637 	}
638 
639 	if (buttonsPressed == 1)
640 	{
641 		draggingStarted = false;
642 		draggingInitStarted = false;
643 		emit mouseDragFinish();
644 	}
645 	buttonsPressed--;
646 
647 	// in case if some release event was missed
648 	if (event->buttons() == Qt::NoButton)
649 	{
650 		buttonsPressed = 0;
651 		draggingStarted = false;
652 		draggingInitStarted = false;
653 	}
654 }
655 
enterEvent(QEvent * event)656 void RenderedImage::enterEvent(QEvent *event)
657 {
658 	(void)event;
659 
660 	if (!isFocus)
661 	{
662 		setFocus();
663 		QApplication::setOverrideCursor(Qt::CrossCursor);
664 	}
665 	isFocus = true;
666 	timerRefreshImage->start();
667 }
668 
leaveEvent(QEvent * event)669 void RenderedImage::leaveEvent(QEvent *event)
670 {
671 	(void)event;
672 	isFocus = false;
673 	isOnObject = false;
674 	update();
675 	timerRefreshImage->stop();
676 	QApplication::restoreOverrideCursor();
677 	QApplication::restoreOverrideCursor();
678 }
679 
keyPressEvent(QKeyEvent * event)680 void RenderedImage::keyPressEvent(QKeyEvent *event)
681 {
682 	if (event->isAutoRepeat())
683 	{
684 		event->ignore();
685 	}
686 	else
687 	{
688 		if (enumClickMode(clickModeData.at(0).toInt()) == clickFlightSpeedControl)
689 		{
690 			Qt::Key key = Qt::Key(event->key());
691 			if (key == Qt::Key_Up)
692 			{
693 				keyArrows.y += 1;
694 				emit StrafeChanged(keyArrows);
695 			}
696 			else if (key == Qt::Key_Down)
697 			{
698 				keyArrows.y -= 1;
699 				emit StrafeChanged(keyArrows);
700 			}
701 			else if (key == Qt::Key_Left)
702 			{
703 				keyArrows.x -= 1;
704 				emit StrafeChanged(keyArrows);
705 			}
706 			else if (key == Qt::Key_Right)
707 			{
708 				keyArrows.x += 1;
709 				emit StrafeChanged(keyArrows);
710 			}
711 			else if (key == Qt::Key_Z)
712 			{
713 				flightRotationDirection = 1;
714 				emit RotationChanged(flightRotationDirection);
715 			}
716 			else if (key == Qt::Key_X)
717 			{
718 				flightRotationDirection = -1;
719 				emit RotationChanged(flightRotationDirection);
720 			}
721 			else if (key == Qt::Key_Space)
722 			{
723 				emit Pause();
724 			}
725 			else if (key == Qt::Key_Shift)
726 			{
727 				emit ShiftModeChanged(true);
728 			}
729 		}
730 		else
731 		{
732 			emit keyPress(event);
733 		}
734 	}
735 	event->ignore(); // pass pressed key event to parents
736 }
737 
keyReleaseEvent(QKeyEvent * event)738 void RenderedImage::keyReleaseEvent(QKeyEvent *event)
739 {
740 	if (event->isAutoRepeat())
741 	{
742 		event->ignore();
743 		emit keyRelease(event);
744 	}
745 	else
746 	{
747 		if (enumClickMode(clickModeData.at(0).toInt()) == clickFlightSpeedControl)
748 		{
749 			Qt::Key key = Qt::Key(event->key());
750 			if (key == Qt::Key_Up)
751 			{
752 				keyArrows.y -= 1;
753 				emit StrafeChanged(keyArrows);
754 			}
755 			else if (key == Qt::Key_Down)
756 			{
757 				keyArrows.y += 1;
758 				emit StrafeChanged(keyArrows);
759 			}
760 			else if (key == Qt::Key_Left)
761 			{
762 				keyArrows.x += 1;
763 				emit StrafeChanged(keyArrows);
764 			}
765 			else if (key == Qt::Key_Right)
766 			{
767 				keyArrows.x -= 1;
768 				emit StrafeChanged(keyArrows);
769 			}
770 			else if (key == Qt::Key_Z)
771 			{
772 				flightRotationDirection = 0;
773 				emit RotationChanged(flightRotationDirection);
774 			}
775 			else if (key == Qt::Key_X)
776 			{
777 				flightRotationDirection = 0;
778 				emit RotationChanged(flightRotationDirection);
779 			}
780 			else if (key == Qt::Key_Shift)
781 			{
782 				emit ShiftModeChanged(false);
783 			}
784 		}
785 		else
786 		{
787 			emit keyRelease(event);
788 		}
789 	}
790 	event->ignore(); // pass pressed key event to parents
791 }
792 
wheelEvent(QWheelEvent * event)793 void RenderedImage::wheelEvent(QWheelEvent *event)
794 {
795 	if (clickModesEnables || enumClickMode(clickModeData.at(0).toInt()) == clickFlightSpeedControl)
796 	{
797 		if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) != 0)
798 		{
799 			event->accept(); // do not propagate event to parent widgets - prevents from scrolling
800 
801 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
802 			emit mouseWheelRotatedWithKey(event->x(), event->y(), event->delta(), event->modifiers());
803 #else
804 			emit mouseWheelRotatedWithKey(int(event->position().x()), int(event->position().y()),
805 				event->angleDelta().y() + event->angleDelta().x(), event->modifiers());
806 			// with alt key there is modified delta.x
807 #endif
808 			if (params)
809 			{
810 				if (cursorVisible && isFocus && redrawed)
811 				{
812 					update();
813 				}
814 			}
815 			else
816 			{
817 				if (cursorVisible)
818 					qCritical()
819 						<< "RenderedImage::mouseMoveEvent(QMouseEvent * event): parameters not assigned";
820 			}
821 		}
822 	}
823 }
824 
DisplayCrosshair() const825 void RenderedImage::DisplayCrosshair() const
826 {
827 	// calculate crosshair center point according to sweet point
828 
829 	double sweetSpotHAngle = params->Get<double>("sweet_spot_horizontal_angle") / 180.0 * M_PI;
830 	double sweetSpotVAngle = params->Get<double>("sweet_spot_vertical_angle") / 180.0 * M_PI;
831 	params::enumPerspectiveType perspType =
832 		params::enumPerspectiveType(params->Get<int>("perspective_type"));
833 
834 	double fov = CalcFOV(params->Get<double>("fov"), perspType);
835 
836 	float sw = image->GetPreviewWidth();
837 	float sh = image->GetPreviewHeight();
838 
839 	double aspectRatio = sw / sh;
840 
841 	CVector2<float> crossShift;
842 
843 	switch (perspType)
844 	{
845 		case params::perspThreePoint:
846 			crossShift.y = tan(sweetSpotVAngle) / fov;
847 			crossShift.x = tan(-sweetSpotHAngle) / fov / cos(sweetSpotVAngle) / aspectRatio;
848 			break;
849 
850 		case params::perspFishEye:
851 		case params::perspFishEyeCut:
852 		{
853 			CVector3 forward(0.0, 0.0, 1.0);
854 			forward = forward.RotateAroundVectorByAngle(CVector3(0.0, 1.0, 0.0), -sweetSpotHAngle);
855 			forward = forward.RotateAroundVectorByAngle(CVector3(1.0, 0.0, 0.0), -sweetSpotVAngle);
856 			forward.Normalize();
857 			double r = sqrt(forward.x * forward.x + forward.y * forward.y);
858 			if (r > 0)
859 			{
860 				double r2 = asin(r) * 2.;
861 				crossShift.x = (forward.x / fov) * r2 / r / 2.0 / aspectRatio;
862 				crossShift.y = (forward.y / fov) * r2 / r / 2.0;
863 			}
864 			else
865 			{
866 				crossShift = CVector2<float>(0, 0);
867 			}
868 			break;
869 		}
870 
871 		case params::perspEquirectangular:
872 		{
873 			CVector3 forward(0.0, 0.0, 1.0);
874 			forward = forward.RotateAroundVectorByAngle(CVector3(0.0, 1.0, 0.0), -sweetSpotHAngle);
875 			forward = forward.RotateAroundVectorByAngle(CVector3(1.0, 0.0, 0.0), -sweetSpotVAngle);
876 			crossShift.x = asin(forward.x / cos(asin(forward.y))) / 0.5 / fov / aspectRatio;
877 			if (forward.z < 0 && crossShift.x > 0) crossShift.x = fov / aspectRatio - crossShift.x;
878 			if (forward.z < 0 && crossShift.x < 0) crossShift.x = -fov / aspectRatio - crossShift.x;
879 			crossShift.y = asin(forward.y) / 0.5 / fov;
880 			break;
881 		}
882 	}
883 
884 	CVector2<float> crossCenter;
885 	crossCenter.x = (sw * 0.5f) * (1.0f + 2.0f * crossShift.x);
886 	crossCenter.y = (sh * 0.5f) * (1.0f + 2.0f * crossShift.y);
887 
888 	if (params->Get<bool>("stereo_enabled")
889 			&& params->Get<bool>("stereo_mode") == cStereo::stereoLeftRight)
890 	{
891 		image->AntiAliasedLine(crossCenter.x / 2, 0, crossCenter.x / 2, sh, -1, -1,
892 			sRGB8(255, 255, 255), sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
893 		image->AntiAliasedLine(crossCenter.x * 1.5f, 0, crossCenter.x * 1.5f, sh, -1, -1,
894 			sRGB8(255, 255, 255), sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
895 		image->AntiAliasedLine(0, crossCenter.y, sw, crossCenter.y, -1, -1, sRGB8(255, 255, 255),
896 			sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
897 	}
898 	else
899 	{
900 		switch (gridType)
901 		{
902 			case gridTypeCrosshair:
903 				image->AntiAliasedLine(crossCenter.x, 0, crossCenter.x, sh, -1, -1, sRGB8(255, 255, 255),
904 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
905 				image->AntiAliasedLine(0, crossCenter.y, sw, crossCenter.y, -1, -1, sRGB8(255, 255, 255),
906 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
907 				break;
908 
909 			case gridTypeThirds:
910 				image->AntiAliasedLine(sw * 0.3333f, 0, sw * 0.3333f, sh, -1, -1, sRGB8(255, 255, 255),
911 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
912 				image->AntiAliasedLine(sw * 0.6666f, 0, sw * 0.6666f, sh, -1, -1, sRGB8(255, 255, 255),
913 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
914 				image->AntiAliasedLine(0, sh * 0.3333f, sw, sh * 0.3333f, -1, -1, sRGB8(255, 255, 255),
915 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
916 				image->AntiAliasedLine(0, sh * 0.6666f, sw, sh * 0.6666f, -1, -1, sRGB8(255, 255, 255),
917 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
918 				break;
919 
920 			case gridTypeGolden:
921 				float goldenRatio = (1.0f + sqrtf(5.0f)) / 2.0f;
922 				float ratio1 = goldenRatio - 1.0f;
923 				float ratio2 = 1.0f - ratio1;
924 				image->AntiAliasedLine(sw * ratio1, 0, sw * ratio1, sh, -1, -1, sRGB8(255, 255, 255),
925 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
926 				image->AntiAliasedLine(sw * ratio2, 0, sw * ratio2, sh, -1, -1, sRGB8(255, 255, 255),
927 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
928 				image->AntiAliasedLine(0, sh * ratio1, sw, sh * ratio1, -1, -1, sRGB8(255, 255, 255),
929 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
930 				image->AntiAliasedLine(0, sh * ratio2, sw, sh * ratio2, -1, -1, sRGB8(255, 255, 255),
931 					sRGBFloat(0.3f, 0.3f, 0.3f), 1.0f, 1);
932 				break;
933 		}
934 	}
935 }
936 
Compass(CVector3 rotation,QPointF center,double size)937 void RenderedImage::Compass(CVector3 rotation, QPointF center, double size)
938 {
939 	QPainter painter(this);
940 	painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
941 
942 	QPen penRed(Qt::red, 1.0, Qt::SolidLine);
943 	QPen penGreen(Qt::green, 1.0, Qt::SolidLine);
944 	QPen penBlue(Qt::blue, 1.0, Qt::SolidLine);
945 	QPen penYellow(Qt::yellow, 2.0, Qt::SolidLine);
946 	QPen penMagenta(Qt::magenta, 2.0, Qt::SolidLine);
947 	QPen penCyan(Qt::cyan, 2.0, Qt::SolidLine);
948 
949 	CRotationMatrix mRotInv;
950 	mRotInv.RotateY(-rotation.z);
951 	mRotInv.RotateX(-rotation.y);
952 	mRotInv.RotateZ(-rotation.x);
953 
954 	// Draw circles
955 	const int steps = 40;
956 	const double persp = 0.5;
957 	CVector3 circlePoint1[steps];
958 	CVector3 circlePoint2[steps];
959 	CVector3 circlePoint3[steps];
960 
961 	for (int i = 0; i < steps; i++)
962 	{
963 		double angle = i * 2.0 * M_PI / steps;
964 
965 		circlePoint1[i].x = cos(angle);
966 		circlePoint1[i].y = sin(angle);
967 		circlePoint1[i].z = 0.0;
968 		circlePoint2[i].x = 0;
969 		circlePoint2[i].y = sin(angle);
970 		circlePoint2[i].z = cos(angle);
971 		circlePoint3[i].x = cos(angle);
972 		circlePoint3[i].y = 0.0;
973 		circlePoint3[i].z = sin(angle);
974 	}
975 
976 	QPolygonF polygon1(steps);
977 	QPolygonF polygon2(steps);
978 	QPolygonF polygon3(steps);
979 
980 	for (int i = 0; i < steps; i++)
981 	{
982 		polygon1[i] = CalcPointPersp(circlePoint1[i], mRotInv, persp) * size + center;
983 		polygon2[i] = CalcPointPersp(circlePoint2[i], mRotInv, persp) * size + center;
984 		polygon3[i] = CalcPointPersp(circlePoint3[i], mRotInv, persp) * size + center;
985 	}
986 
987 	painter.setOpacity(0.5);
988 
989 	painter.setPen(penRed);
990 	painter.drawPolyline(polygon1);
991 	painter.setPen(penGreen);
992 	painter.drawPolyline(polygon2);
993 	painter.setPen(penBlue);
994 	painter.drawPolyline(polygon3);
995 
996 	// Draw arrows
997 	QPointF point1, point2;
998 	painter.setOpacity(0.5);
999 	QFont font = painter.font();
1000 	font.setBold(true);
1001 	painter.setFont(font);
1002 	// X axis
1003 	painter.setPen(penMagenta);
1004 	point1 = CalcPointPersp(CVector3(1.0, 0.0, 0.0), mRotInv, persp) * size + center;
1005 	point2 = CalcPointPersp(CVector3(-1.0, 0.0, 0.0), mRotInv, persp) * size + center;
1006 	painter.drawLine(point1, point2);
1007 
1008 	QStaticText textX("X");
1009 	point1 = CalcPointPersp(CVector3(1.2, 0.0, 0.0), mRotInv, persp) * size + center;
1010 	point2 =
1011 		QPointF(point1.x() - textX.size().width() * 0.5, point1.y() - textX.size().height() * 0.5);
1012 	painter.drawStaticText(point2, textX);
1013 
1014 	point1 = CalcPointPersp(CVector3(0.9, -0.05, 0.0), mRotInv, persp) * size + center;
1015 	point2 = CalcPointPersp(CVector3(1.0, 0.0, 0.0), mRotInv, persp) * size + center;
1016 	painter.drawLine(point1, point2);
1017 	point1 = CalcPointPersp(CVector3(0.9, 0.05, 0.0), mRotInv, persp) * size + center;
1018 	point2 = CalcPointPersp(CVector3(1.0, 0.0, 0.0), mRotInv, persp) * size + center;
1019 	painter.drawLine(point1, point2);
1020 	point1 = CalcPointPersp(CVector3(0.9, 0.0, 0.05), mRotInv, persp) * size + center;
1021 	point2 = CalcPointPersp(CVector3(1.0, 0.0, 0.0), mRotInv, persp) * size + center;
1022 	painter.drawLine(point1, point2);
1023 	point1 = CalcPointPersp(CVector3(0.9, 0.0, -0.05), mRotInv, persp) * size + center;
1024 	point2 = CalcPointPersp(CVector3(1.0, 0.0, 0.0), mRotInv, persp) * size + center;
1025 	painter.drawLine(point1, point2);
1026 
1027 	// Z axis
1028 	painter.setPen(penCyan);
1029 	point1 = CalcPointPersp(CVector3(0.0, 0.0, 1.0), mRotInv, persp) * size + center;
1030 	point2 = CalcPointPersp(CVector3(0.0, 0.0, -1.0), mRotInv, persp) * size + center;
1031 	painter.drawLine(point1, point2);
1032 
1033 	QStaticText textZ("Z");
1034 	point1 = CalcPointPersp(CVector3(0.0, 0.0, 1.2), mRotInv, persp) * size + center;
1035 	point2 =
1036 		QPointF(point1.x() - textZ.size().width() * 0.5, point1.y() - textZ.size().height() * 0.5);
1037 	painter.drawStaticText(point2, textZ);
1038 
1039 	point1 = CalcPointPersp(CVector3(0.05, 0.0, 0.9), mRotInv, persp) * size + center;
1040 	point2 = CalcPointPersp(CVector3(0.0, 0.0, 1.0), mRotInv, persp) * size + center;
1041 	painter.drawLine(point1, point2);
1042 	point1 = CalcPointPersp(CVector3(-0.05, 0.0, 0.9), mRotInv, persp) * size + center;
1043 	point2 = CalcPointPersp(CVector3(0.0, 0.0, 1.0), mRotInv, persp) * size + center;
1044 	painter.drawLine(point1, point2);
1045 	point1 = CalcPointPersp(CVector3(0.0, 0.05, 0.9), mRotInv, persp) * size + center;
1046 	point2 = CalcPointPersp(CVector3(0.0, 0.0, 1.0), mRotInv, persp) * size + center;
1047 	painter.drawLine(point1, point2);
1048 	point1 = CalcPointPersp(CVector3(0.0, -0.05, 0.9), mRotInv, persp) * size + center;
1049 	point2 = CalcPointPersp(CVector3(0.0, 0.0, 1.0), mRotInv, persp) * size + center;
1050 	painter.drawLine(point1, point2);
1051 
1052 	// Y axis
1053 	painter.setPen(penYellow);
1054 	point1 = CalcPointPersp(CVector3(0.0, 1.0, 0.0), mRotInv, persp) * size + center;
1055 	point2 = CalcPointPersp(CVector3(0.0, -1.0, 0.0), mRotInv, persp) * size + center;
1056 	painter.drawLine(point1, point2);
1057 
1058 	QStaticText textY("Y");
1059 	point1 = CalcPointPersp(CVector3(0.0, 1.2, 0.0), mRotInv, persp) * size + center;
1060 	point2 =
1061 		QPointF(point1.x() - textY.size().width() * 0.5, point1.y() - textY.size().height() * 0.5);
1062 	painter.drawStaticText(point2, textY);
1063 
1064 	point1 = CalcPointPersp(CVector3(0.05, 0.9, 0.0), mRotInv, persp) * size + center;
1065 	point2 = CalcPointPersp(CVector3(0.0, 1.0, 0.0), mRotInv, persp) * size + center;
1066 	painter.drawLine(point1, point2);
1067 	point1 = CalcPointPersp(CVector3(-0.05, 0.9, 0.0), mRotInv, persp) * size + center;
1068 	point2 = CalcPointPersp(CVector3(0.0, 1.0, 0.0), mRotInv, persp) * size + center;
1069 	painter.drawLine(point1, point2);
1070 	point1 = CalcPointPersp(CVector3(0.0, 0.9, 0.05), mRotInv, persp) * size + center;
1071 	point2 = CalcPointPersp(CVector3(0.0, 1.0, 0.0), mRotInv, persp) * size + center;
1072 	painter.drawLine(point1, point2);
1073 	point1 = CalcPointPersp(CVector3(0.0, 0.9, -0.05), mRotInv, persp) * size + center;
1074 	point2 = CalcPointPersp(CVector3(0.0, 1.0, 0.0), mRotInv, persp) * size + center;
1075 	painter.drawLine(point1, point2);
1076 }
1077 
CalcPointPersp(const CVector3 & point,const CRotationMatrix & rot,double persp)1078 QPointF RenderedImage::CalcPointPersp(
1079 	const CVector3 &point, const CRotationMatrix &rot, double persp)
1080 {
1081 	CVector3 vect;
1082 
1083 	vect = rot.RotateVector(point);
1084 	QPointF out(vect.x / (1.0 + vect.y * persp), -vect.z / (1.0 + vect.y * persp));
1085 	return out;
1086 }
1087 
setClickMode(QList<QVariant> _clickMode)1088 void RenderedImage::setClickMode(QList<QVariant> _clickMode)
1089 {
1090 	if (_clickMode.size() > 0)
1091 		clickModeData = _clickMode;
1092 	else
1093 		qWarning() << "_clickMode cannot be empty!";
1094 }
1095 
slotSetMinimumSize(int width,int height)1096 void RenderedImage::slotSetMinimumSize(int width, int height)
1097 {
1098 	setMinimumSize(width, height);
1099 }
1100 
SetGridType(enumGridType _gridType)1101 void RenderedImage::SetGridType(enumGridType _gridType)
1102 {
1103 	gridType = _gridType;
1104 	update();
1105 }
1106 
SetAnimationPath(const sAnimationPathData & _animationPath)1107 void RenderedImage::SetAnimationPath(const sAnimationPathData &_animationPath)
1108 {
1109 	animationPathData = _animationPath;
1110 }
1111 
DrawAnimationPath()1112 void RenderedImage::DrawAnimationPath()
1113 {
1114 	int numberOfKeyframes = animationPathData.numberOfKeyframes;
1115 	int numberOfFrames = animationPathData.numberOfFrames;
1116 
1117 	CVector3 camera = params->Get<CVector3>("camera");
1118 	CVector3 rotation = params->Get<CVector3>("camera_rotation");
1119 	params::enumPerspectiveType perspectiveType =
1120 		static_cast<params::enumPerspectiveType>(params->Get<int>("perspective_type"));
1121 	double fov = CalcFOV(params->Get<double>("fov"), perspectiveType);
1122 	int width = image->GetPreviewWidth();
1123 	int height = image->GetPreviewHeight();
1124 
1125 	CRotationMatrix mRotInv;
1126 	mRotInv.RotateY(-rotation.z / 180.0 * M_PI);
1127 	mRotInv.RotateX(-rotation.y / 180.0 * M_PI);
1128 	mRotInv.RotateZ(-rotation.x / 180.0 * M_PI);
1129 
1130 	int frameIndex = 0;
1131 	for (int key = 0; key < numberOfKeyframes; key++)
1132 	{
1133 		int numberOfSubframes = animationPathData.framesPeyKey.at(key);
1134 
1135 		for (int subframe = 0; subframe < numberOfSubframes; subframe++)
1136 		{
1137 			if (frameIndex >= numberOfFrames - 1) break;
1138 
1139 			CVector3 pointTarget1, pointTarget2, pointCamera1, pointCamera2;
1140 
1141 			double percent = double(frameIndex) / numberOfFrames;
1142 
1143 			if (animationPathData.targetPathEnable)
1144 			{
1145 				CVector3 target1 = animationPathData.animationPath[frameIndex].target;
1146 				CVector3 target2 = animationPathData.animationPath[frameIndex + 1].target;
1147 
1148 				pointTarget1 =
1149 					InvProjection3D(target1, camera, mRotInv, perspectiveType, fov, width, height);
1150 				pointTarget2 =
1151 					InvProjection3D(target2, camera, mRotInv, perspectiveType, fov, width, height);
1152 
1153 				sRGB8 color(255 - percent * 128, 0, percent * 255);
1154 				if (pointTarget1.z > 0)
1155 				{
1156 					if (subframe == 0)
1157 					{
1158 						image->CircleBorder(pointTarget1.x, pointTarget1.y, pointTarget1.z, 5.0, color, 2.0,
1159 							sRGBFloat(1.0, 1.0, 1.0), 1);
1160 					}
1161 				}
1162 				if (pointTarget1.z > 0 && pointTarget2.z > 0)
1163 				{
1164 					image->AntiAliasedLine(pointTarget1.x, pointTarget1.y, pointTarget2.x, pointTarget2.y,
1165 						pointTarget1.z, pointTarget2.z, color, sRGBFloat(1.0, 1.0, 1.0), 1.0f, 1);
1166 				}
1167 			}
1168 
1169 			if (animationPathData.cameraPathEnable)
1170 			{
1171 				CVector3 camera1 = animationPathData.animationPath[frameIndex].camera;
1172 				CVector3 camera2 = animationPathData.animationPath[frameIndex + 1].camera;
1173 				pointCamera1 =
1174 					InvProjection3D(camera1, camera, mRotInv, perspectiveType, fov, width, height);
1175 				pointCamera2 =
1176 					InvProjection3D(camera2, camera, mRotInv, perspectiveType, fov, width, height);
1177 
1178 				sRGB8 color(0, 255 - percent * 128, percent * 255);
1179 				if (pointCamera1.z > 0)
1180 				{
1181 					if (subframe == 0)
1182 					{
1183 						image->CircleBorder(pointCamera1.x, pointCamera1.y, pointCamera1.z, 5.0, color, 2.0,
1184 							sRGBFloat(1.0, 1.0, 1.0), 1);
1185 					}
1186 				}
1187 
1188 				if (pointCamera1.z > 0 && pointCamera2.z > 0)
1189 				{
1190 					image->AntiAliasedLine(pointCamera1.x, pointCamera1.y, pointCamera2.x, pointCamera2.y,
1191 						pointCamera1.z, pointCamera2.z, color, sRGBFloat(1.0, 1.0, 1.0), 1.0f, 1);
1192 				}
1193 			}
1194 
1195 			if (animationPathData.cameraPathEnable && animationPathData.targetPathEnable)
1196 			{
1197 				if (pointCamera1.z > 0 && pointTarget1.z > 0)
1198 				{
1199 					if (subframe % (numberOfSubframes / 4) == 0)
1200 					{
1201 						image->AntiAliasedLine(pointCamera1.x, pointCamera1.y, pointTarget1.x, pointTarget1.y,
1202 							pointCamera1.z, pointTarget1.z, sRGB8(255, 255, 0), sRGBFloat(0.2f, 0.2f, 0.2f), 1.0f,
1203 							1);
1204 					}
1205 				}
1206 			}
1207 
1208 			// lights
1209 			for (int i = 0; i < 4; i++)
1210 			{
1211 				if (animationPathData.lightPathEnable[i])
1212 				{
1213 					CVector3 target1 = animationPathData.animationPath[frameIndex].lights[i];
1214 					CVector3 target2 = animationPathData.animationPath[frameIndex + 1].lights[i];
1215 
1216 					pointTarget1 =
1217 						InvProjection3D(target1, camera, mRotInv, perspectiveType, fov, width, height);
1218 					pointTarget2 =
1219 						InvProjection3D(target2, camera, mRotInv, perspectiveType, fov, width, height);
1220 
1221 					sRGB8 color = animationPathData.animationPath[frameIndex].lightColor[i];
1222 
1223 					if (subframe % 8 >= 4)
1224 					{
1225 						color.R = 255 - color.R;
1226 						color.G = 255 - color.G;
1227 						color.B = 255 - color.B;
1228 					}
1229 					if (pointTarget1.z > 0)
1230 					{
1231 						if (subframe == 0)
1232 						{
1233 							image->CircleBorder(pointTarget1.x, pointTarget1.y, pointTarget1.z, 5.0, color, 2.0,
1234 								sRGBFloat(1.0, 1.0, 1.0), 1);
1235 						}
1236 					}
1237 					if (pointTarget1.z > 0 && pointTarget2.z > 0)
1238 					{
1239 						image->AntiAliasedLine(pointTarget1.x, pointTarget1.y, pointTarget2.x, pointTarget2.y,
1240 							pointTarget1.z, pointTarget2.z, color, sRGBFloat(1.0, 1.0, 1.0), 1.0f, 1);
1241 					}
1242 				}
1243 			}
1244 
1245 			frameIndex++;
1246 		}
1247 	}
1248 }
1249 
showRenderedTilesList(QList<sRenderedTileData> listOfRenderedTiles)1250 void RenderedImage::showRenderedTilesList(QList<sRenderedTileData> listOfRenderedTiles)
1251 {
1252 	listOfRenderedTilesData.append(listOfRenderedTiles);
1253 }
1254 
PaintLastRenderedTilesInfo()1255 void RenderedImage::PaintLastRenderedTilesInfo()
1256 {
1257 	QPainter painter(this);
1258 	painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
1259 
1260 	QPen penRed(Qt::red, 1.0, Qt::SolidLine);
1261 	QPen penGreen(Qt::green, 1.0, Qt::SolidLine);
1262 	painter.setOpacity(0.5);
1263 
1264 	QList<QPair<int, int>> listOfPaintedTiles;
1265 
1266 	for (sRenderedTileData &tile : listOfRenderedTilesData)
1267 	{
1268 		if (!listOfPaintedTiles.contains(QPair<int, int>(tile.x, tile.y)))
1269 		{
1270 			listOfPaintedTiles.append(QPair<int, int>(tile.x, tile.y));
1271 
1272 			QRect r(tile.x * image->GetPreviewScale(), tile.y * image->GetPreviewScale(),
1273 				tile.width * image->GetPreviewScale(), tile.height * image->GetPreviewScale());
1274 
1275 			painter.setOpacity(0.5);
1276 			painter.setPen(penRed);
1277 
1278 			painter.drawLine(r.x(), r.y(), r.x() + r.width() / 4, r.y());
1279 			painter.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() / 4);
1280 			painter.drawLine(r.right(), r.bottom(), r.right() - r.width() / 4, r.bottom());
1281 			painter.drawLine(r.right(), r.bottom(), r.right(), r.bottom() - r.height() / 4);
1282 
1283 			painter.setPen(penGreen);
1284 
1285 			painter.drawLine(r.right(), r.y(), r.right() - r.width() / 4, r.y());
1286 			painter.drawLine(r.right(), r.y(), r.right(), r.y() + r.height() / 4);
1287 			painter.drawLine(r.left(), r.bottom(), r.left() + r.width() / 4, r.bottom());
1288 			painter.drawLine(r.left(), r.bottom(), r.left(), r.bottom() - r.height() / 4);
1289 
1290 			QPoint center = r.center();
1291 
1292 			painter.setOpacity(1.0);
1293 			if (tile.noiseLevel > 0)
1294 			{
1295 				QStaticText text(QString("%1").arg(tile.noiseLevel * 100.0, 0, 'f', 1));
1296 				QPointF point =
1297 					QPointF(center.x() - text.size().width() * 0.5, center.y() - text.size().height() * 0.5);
1298 				painter.drawStaticText(point, text);
1299 			}
1300 		}
1301 	}
1302 	listOfRenderedTilesData.clear();
1303 }
1304 
DisplayAllLights()1305 void RenderedImage::DisplayAllLights()
1306 {
1307 	CVector3 camera = params->Get<CVector3>("camera");
1308 	CVector3 rotation = params->Get<CVector3>("camera_rotation");
1309 	params::enumPerspectiveType perspectiveType =
1310 		static_cast<params::enumPerspectiveType>(params->Get<int>("perspective_type"));
1311 	double fov = CalcFOV(params->Get<double>("fov"), perspectiveType);
1312 	int width = image->GetPreviewWidth();
1313 	int height = image->GetPreviewHeight();
1314 
1315 	CRotationMatrix mRotInv;
1316 	mRotInv.RotateY(-rotation.z / 180.0 * M_PI);
1317 	mRotInv.RotateX(-rotation.y / 180.0 * M_PI);
1318 	mRotInv.RotateZ(-rotation.x / 180.0 * M_PI);
1319 
1320 	QList<QString> listOfParameters = params->GetListOfParameters();
1321 	for (auto &parameterName : listOfParameters)
1322 	{
1323 		const int lengthOfPrefix = 5;
1324 		if (parameterName.leftRef(lengthOfPrefix) == "light")
1325 		{
1326 			int positionOfDash = parameterName.indexOf('_');
1327 			int lightIndex =
1328 				parameterName.midRef(lengthOfPrefix, positionOfDash - lengthOfPrefix).toInt();
1329 			if (parameterName.midRef(positionOfDash + 1) == "is_defined")
1330 			{
1331 				const cLight light(lightIndex, params, false, true, false);
1332 
1333 				if (light.enabled)
1334 				{
1335 					bool bold = lightIndex == currentLightIndex;
1336 					double thickness = (bold) ? 2.0 : 1.0;
1337 
1338 					if (light.type != cLight::lightDirectional)
1339 					{
1340 						CVector3 lightCenter =
1341 							InvProjection3D(light.position, camera, mRotInv, perspectiveType, fov, width, height);
1342 
1343 						sRGB8 color = toRGB8(light.color);
1344 
1345 						image->CircleBorder(lightCenter.x, lightCenter.y, lightCenter.z, 10.0, color,
1346 							thickness * 4.0, sRGBFloat(1.0, 1.0, 1.0), 1);
1347 
1348 						if (light.type == cLight::lightPoint)
1349 						{
1350 							double visibleSize =
1351 								sqrt(light.intensity) * light.size / lightCenter.z / fov * height;
1352 
1353 							image->CircleBorder(lightCenter.x, lightCenter.y, lightCenter.z, visibleSize, color,
1354 								thickness * 2.0, sRGBFloat(0.5, 0.5, 0.5), 1);
1355 
1356 							double sizeFactor = sqrt(light.intensity) * light.size * 20.0;
1357 
1358 							line3D(light.position - CVector3(sizeFactor, 0.0, 0.0),
1359 								light.position + CVector3(sizeFactor, 0.0, 0.0), camera, mRotInv, perspectiveType,
1360 								fov, width, height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 20, 1);
1361 
1362 							line3D(light.position - CVector3(0.0, sizeFactor, 0.0),
1363 								light.position + CVector3(0.0, sizeFactor, 0.0), camera, mRotInv, perspectiveType,
1364 								fov, width, height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 20, 1);
1365 
1366 							line3D(light.position - CVector3(0.0, 0.0, sizeFactor),
1367 								light.position + CVector3(0.0, 0.0, sizeFactor), camera, mRotInv, perspectiveType,
1368 								fov, width, height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 20, 1);
1369 						}
1370 
1371 						if (light.type == cLight::lightConical)
1372 						{
1373 							double sizeFactor = sqrt(light.intensity) * light.size * 2.0;
1374 							for (int s = 0; s < 2; s++)
1375 							{
1376 
1377 								double coneRatio =
1378 									sin((s == 0) ? light.coneAngle : light.coneAngle + light.coneSoftAngle);
1379 
1380 								sRGBFloat opacity =
1381 									((s == 0) ? sRGBFloat(0.7, 0.7, 0.7) : sRGBFloat(0.2, 0.2, 0.2));
1382 
1383 								for (int i = 0; i < 8; i++)
1384 								{
1385 									double r1 = (i + 1) * light.size * coneRatio * sizeFactor;
1386 									double r2 = i * light.size * coneRatio * sizeFactor;
1387 
1388 									CVector3 previousPoint;
1389 
1390 									for (int j = 0; j <= 16; j++)
1391 									{
1392 										double angle = j / 16.0 * 2.0 * M_PI;
1393 
1394 										CVector3 dx1 = r1 * cos(angle) * light.lightRightVector;
1395 										CVector3 dy1 = r1 * sin(angle) * light.lightTopVector;
1396 										CVector3 dz1 =
1397 											(-1.0) * (light.size * (i + 1) * sizeFactor) * light.lightDirection;
1398 
1399 										CVector3 point1 = light.position + dx1 + dy1 + dz1;
1400 
1401 										// draw lines
1402 										if (j % 4 == 0)
1403 										{
1404 											CVector3 dx2 = r2 * cos(angle) * light.lightRightVector;
1405 											CVector3 dy2 = r2 * sin(angle) * light.lightTopVector;
1406 											CVector3 dz2 = (-1.0) * (light.size * i * sizeFactor) * light.lightDirection;
1407 
1408 											CVector3 point2 = light.position + dx2 + dy2 + dz2;
1409 
1410 											line3D(point1, point2, camera, mRotInv, perspectiveType, fov, width, height,
1411 												color, thickness, opacity, 10, 1);
1412 										}
1413 
1414 										// draw circles
1415 										if (j > 0)
1416 										{
1417 											line3D(point1, previousPoint, camera, mRotInv, perspectiveType, fov, width,
1418 												height, color, thickness, opacity, 10, 1);
1419 										}
1420 
1421 										previousPoint = point1;
1422 									} // for j
1423 								}		// for i
1424 							}			// for s
1425 						}				// if conical
1426 
1427 						if (light.type == cLight::lightProjection)
1428 						{
1429 							double sizeFactor = sqrt(light.intensity) * light.size * 2.0;
1430 
1431 							for (int i = 1; i <= 8; i++)
1432 							{
1433 								double w = i * light.size * light.projectionHorizontalRatio * sizeFactor * 0.5;
1434 								double h = i * light.size * light.projectionVerticalRatio * sizeFactor * 0.5;
1435 
1436 								CVector3 dx = w * light.lightRightVector;
1437 								CVector3 dy = h * light.lightTopVector;
1438 								CVector3 dz = (-1.0) * light.size * i * sizeFactor * light.lightDirection;
1439 
1440 								CVector3 point1 = light.position + dx + dy + dz;
1441 								CVector3 point2 = light.position - dx + dy + dz;
1442 								CVector3 point3 = light.position - dx - dy + dz;
1443 								CVector3 point4 = light.position + dx - dy + dz;
1444 
1445 								line3D(point1, point2, camera, mRotInv, perspectiveType, fov, width, height, color,
1446 									thickness, sRGBFloat(0.7, 0.7, 0.7), 10, 1);
1447 								line3D(point2, point3, camera, mRotInv, perspectiveType, fov, width, height, color,
1448 									thickness, sRGBFloat(0.7, 0.7, 0.7), 10, 1);
1449 								line3D(point3, point4, camera, mRotInv, perspectiveType, fov, width, height, color,
1450 									thickness, sRGBFloat(0.7, 0.7, 0.7), 10, 1);
1451 								line3D(point4, point1, camera, mRotInv, perspectiveType, fov, width, height, color,
1452 									thickness, sRGBFloat(0.7, 0.7, 0.7), 10, 1);
1453 
1454 								if (i == 8)
1455 								{
1456 									line3D(point1, light.position, camera, mRotInv, perspectiveType, fov, width,
1457 										height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 100, 1);
1458 									line3D(point2, light.position, camera, mRotInv, perspectiveType, fov, width,
1459 										height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 100, 1);
1460 									line3D(point3, light.position, camera, mRotInv, perspectiveType, fov, width,
1461 										height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 100, 1);
1462 									line3D(point4, light.position, camera, mRotInv, perspectiveType, fov, width,
1463 										height, color, thickness, sRGBFloat(0.7, 0.7, 0.7), 100, 1);
1464 								}
1465 
1466 							} // for i
1467 						}		// if projection
1468 					}			// if not directional
1469 				}				// if enabled
1470 			}					// if is defined
1471 		}						// if parameter is light
1472 	}							// for parameterName
1473 }
1474 
line3D(const CVector3 & p1,const CVector3 & p2,const CVector3 camera,const CRotationMatrix & mRotInv,params::enumPerspectiveType perspectiveType,double fov,double imgWidth,double imgHeight,sRGB8 color,double thickness,sRGBFloat opacity,int numberOfSegments,int layer)1475 void RenderedImage::line3D(const CVector3 &p1, const CVector3 &p2, const CVector3 camera,
1476 	const CRotationMatrix &mRotInv, params::enumPerspectiveType perspectiveType, double fov,
1477 	double imgWidth, double imgHeight, sRGB8 color, double thickness, sRGBFloat opacity,
1478 	int numberOfSegments, int layer)
1479 {
1480 	for (int i = 0; i < numberOfSegments; i++)
1481 	{
1482 		double k1 = double(i) / numberOfSegments;
1483 		double kn1 = 1.0 - k1;
1484 
1485 		double k2 = double(i + 1) / numberOfSegments;
1486 		double kn2 = 1.0 - k2;
1487 
1488 		CVector3 p1Projected = InvProjection3D(
1489 			p1 * kn1 + p2 * k1, camera, mRotInv, perspectiveType, fov, imgWidth, imgHeight);
1490 
1491 		CVector3 p2Projected = InvProjection3D(
1492 			p1 * kn2 + p2 * k2, camera, mRotInv, perspectiveType, fov, imgWidth, imgHeight);
1493 
1494 		image->AntiAliasedLine(p1Projected.x, p1Projected.y, p2Projected.x, p2Projected.y,
1495 			p1Projected.z, p2Projected.z, color, opacity, thickness, layer);
1496 	}
1497 }
1498