1 #include "LDrawModelViewer.h"
2 #include <TCFoundation/TCMacros.h>
3 #include <TCFoundation/TCAutoreleasePool.h>
4 #include <TCFoundation/mystring.h>
5 #include <TCFoundation/TCImage.h>
6 #include <TCFoundation/TCJpegOptions.h>
7 #include <TCFoundation/TCAlertManager.h>
8 #include <TCFoundation/TCProgressAlert.h>
9 #include <TCFoundation/TCLocalStrings.h>
10 #include <TCFoundation/TCUserDefaults.h>
11 #include <TCFoundation/TCWebClient.h>
12 #include <LDLoader/LDLMainModel.h>
13 #include <LDLoader/LDLError.h>
14 #include <LDLoader/LDLFindFileAlert.h>
15 #include <LDLoader/LDLPalette.h>
16 #include <LDLoader/LDLAutoCamera.h>
17 #include <LDLoader/LDLModelLine.h>
18 #include <LDLoader/LDLConditionalLineLine.h>
19 #include <LDExporter/LDPovExporter.h>
20 #include <LDExporter/LDStlExporter.h>
21 #ifdef EXPORT_3DS
22 #include <LDExporter/LD3dsExporter.h>
23 #endif // EXPORT_3DS
24 #ifdef EXPORT_LDR
25 #include <LDExporter/LDLdrExporter.h>
26 #endif // EXPORT_LDR
27 #include "LDInputHandler.h"
28 #include "LDModelParser.h"
29 #include "LDPreferences.h"
30 #include "LDPartsList.h"
31 #include "LDViewPoint.h"
32 #include "LDUserDefaultsKeys.h"
33 #include <TRE/TREMainModel.h>
34 #include <TRE/TREGL.h>
35 #include <time.h>
36 #include <gl2ps/gl2ps.h>
37 
38 #ifndef USE_STD_CHRONO
39 #ifdef COCOA
40 #include <Foundation/Foundation.h>
41 #define FRAME_TIME ((NSDate *&)frameTime)
42 #endif // COCOA
43 #endif // !USE_STD_CHRONO
44 
45 #ifdef USE_STD_CHRONO
46 //#define TIME_MODEL_LOAD
47 #endif // USE_STD_CHRONO
48 
49 #ifdef TIME_MODEL_LOAD
50 #include <iostream>
51 #include <ctime>
52 #endif // TIME_MODEL_LOAD
53 
54 #ifdef WIN32
55 #if defined(_MSC_VER) && _MSC_VER >= 1400 && defined(_DEBUG)
56 #define new DEBUG_CLIENTBLOCK
57 #endif // _DEBUG
58 #endif // WIN32
59 
60 #if (defined _AIX) && !defined (fmodf)
fmodf(float x,float y)61 float fmodf(float x, float y)
62 {
63 	return (x-((int)(x/y))*y);
64 }
65 #endif
66 
67 #define FONT_CHAR_WIDTH 8
68 #define FONT_IMAGE_WIDTH 128
69 #define FONT_IMAGE_HEIGHT 256
70 #define FONT_NUM_CHARACTERS 256
71 #define DEF_DISTANCE_MULT 1.0f
72 
73 LDrawModelViewer::StandardSizeList LDrawModelViewer::standardSizes;
74 std::string LDrawModelViewer::sm_appVersion;
75 std::string LDrawModelViewer::sm_appCopyright;
76 
LDrawModelViewer(TCFloat width,TCFloat height)77 LDrawModelViewer::LDrawModelViewer(TCFloat width, TCFloat height)
78 	:mainTREModel(NULL),
79 	mainModel(NULL),
80 	whiteLightDirModel(NULL),
81 	blueLightDirModel(NULL),
82 	highlightModel(NULL),
83 	filename(NULL),
84 	programPath(NULL),
85 	width(width),
86 	height(height),
87 	scaleFactor(1.0f),
88 	pixelAspectRatio(1.0f),
89 	cullBackFaces(0),
90 	viewMode(VMExamine),
91 	examineMode(EMFree),
92 	xRotate(0.0f),
93 	yRotate(0.0f),
94 	zRotate(0.0f),
95 	rotationSpeed(0.0f),
96 	cameraXRotate(0.0f),
97 	cameraYRotate(0.0f),
98 	cameraZRotate(0.0f),
99 	zoomSpeed(0.0f),
100 	xPan(0.0f),
101 	yPan(0.0f),
102 	rotationMatrix(NULL),
103 	defaultRotationMatrix(NULL),
104 	defaultLatitude(30.0f),
105 	defaultLongitude(45.0f),
106 	examineLatitude(defaultLatitude),
107 	examineLongitude(defaultLongitude),
108 	clipAmount(0.0f),
109 	nextClipAmount(-1.0f),
110 	nextDistance(-1.0f),
111 	highlightLineWidth(1.0f),
112 	wireframeLineWidth(1.0f),
113 	anisoLevel(1.0f),
114 	clipZoom(false),
115 	fontListBase(0),
116 	backgroundR(0.0f),
117 	backgroundG(0.0f),
118 	backgroundB(0.0f),
119 	backgroundA(1.0f),
120 	defaultR(153),
121 	defaultG(153),
122 	defaultB(153),
123 	defaultColorNumber(-1),
124 	xTile(0),
125 	yTile(0),
126 	numXTiles(1),
127 	numYTiles(1),
128 	stereoMode(LDVStereoNone),
129 	stereoEyeSpacing(50.0f),
130 	cutawayMode(LDVCutawayNormal),
131 	cutawayAlpha(1.0f),
132 	cutawayLineWidth(1.0f),
133 	zoomMax(1.99f),
134 	curveQuality(2),
135 	textureFilterType(GL_LINEAR_MIPMAP_LINEAR),
136 	distanceMultiplier(DEF_DISTANCE_MULT),
137 	fontImage1x(NULL),
138 	fontImage2x(NULL),
139 	aspectRatio(1.0f),
140 	currentFov(45.0f),
141 	fov(45.0f),
142 	extraSearchDirs(NULL),
143 	seamWidth(0.5f),
144 	memoryUsage(2),
145 	lightVector(0.0f, 0.0f, 1.0f),
146 	preferences(NULL),
147 	mouseMode(LDVMouseNone),
148 	inputHandler(NULL),
149 	step(-1),
150 	commandLineStep(-1),
151 	mpdChildIndex(0),
152 	exporter(NULL),
153 	exportType(ETPov),
154 	highlightR(160),
155 	highlightG(224),
156 	highlightB(255),
157 	cameraData(NULL)
158 {
159 #ifdef _LEAK_DEBUG
160 	strcpy(className, "LDrawModelViewer");
161 #endif
162 	flags.qualityLighting = false;
163 	flags.showsHighlightLines = false;
164 	flags.qualityStuds = false;
165 	flags.usesFlatShading = false;
166 	flags.usesSpecular = true;
167 	flags.drawWireframe = false;
168 	flags.useWireframeFog = false;
169 	flags.usePolygonOffset = true;
170 	flags.useLighting = true;
171 	flags.subduedLighting = false;
172 	flags.allowPrimitiveSubstitution = true;
173 	flags.useStipple = false;
174 	flags.fileIsPart = false;
175 	flags.sortTransparent = true;
176 	flags.needsSetup = true;
177 	flags.needsTextureSetup = true;
178 	flags.needsMaterialSetup = true;
179 	flags.needsLightingSetup = true;
180 	flags.needsReload = false;
181 	flags.needsReparse = false;
182 	flags.needsRecompile = false;
183 	flags.needsResize = true;
184 	flags.needsCalcSize = false;
185 	flags.needsRotationMatrixSetup = true;
186 	flags.needsViewReset = true;
187 	flags.needsResetStep = false;
188 	flags.needsResetMpd = false;
189 	flags.paused = false;
190 	flags.slowClear = false;
191 	flags.blackHighlights = false;
192 	flags.textureStuds = true;
193 	flags.oneLight = false;
194 	flags.drawConditionalHighlights = false;
195 	flags.showAllConditionalLines = false;
196 	flags.showConditionalControlPoints = false;
197 	flags.performSmoothing = true;
198 	flags.lineSmoothing = false;
199 	flags.constrainZoom = true;
200 	flags.edgesOnly = false;
201 	flags.hiResPrimitives = false;
202 	flags.processLDConfig = true;
203 	flags.skipValidation = false;
204 	flags.autoCenter = true;
205 	flags.forceZoomToFit = false;
206 	flags.defaultTrans = false;
207 	flags.bfc = true;
208 	flags.redBackFaces = false;
209 	flags.greenFrontFaces = false;
210 	flags.blueNeutralFaces = false;
211 	flags.defaultLightVector = true;
212 	flags.overrideModelCenter = false;
213 	flags.overrideModelSize = false;
214 	flags.overrideDefaultDistance = false;
215 	flags.checkPartTracker = true;
216 	flags.showLight = false;
217 	flags.drawLightDats = true;
218 	flags.optionalStandardLight = true;
219 	flags.noLightGeom = false;
220 	flags.updating = false;
221 	flags.saveAlpha = false;
222 	flags.showAxes = false;
223 	flags.axesAtOrigin = true;
224 	flags.showBoundingBox = false;
225 	flags.boundingBoxesOnly = false;
226 	flags.obi = false;
227 	flags.gl2ps = false;
228 	flags.povCameraAspect = TCUserDefaults::boolForKey(POV_CAMERA_ASPECT_KEY,
229 		false, false);
230 	flags.animating = false;
231 	flags.randomColors = false;
232 	flags.noUI = false;
233 	flags.keepRightSideUp = false;
234 	flags.texmaps = true;
235 	flags.useStrips = true;
236 	TCAlertManager::registerHandler(LDLFindFileAlert::alertClass(), this,
237 		(TCAlertCallback)&LDrawModelViewer::findFileAlertCallback);
238 	// Set 4:4:4 as the default sub-sample pattern for JPEG images.
239 	TCJpegOptions::setDefaultSubSampling(TCJpegOptions::SS444);
240 #ifndef USE_STD_CHRONO
241 #ifdef WIN32
242 	if (!QueryPerformanceFrequency(&hrpcFrequency))
243 	{
244 		hrpcFrequency.QuadPart = 0;
245 	}
246 #endif // WIN32
247 #ifdef _QT
248 	qtime.start();
249 #endif
250 #ifdef COCOA
251 	FRAME_TIME = nil;
252 #endif // COCOA
253 #endif // !USE_STD_CHRONO
254 	updateFrameTime(true);
255 }
256 
~LDrawModelViewer(void)257 LDrawModelViewer::~LDrawModelViewer(void)
258 {
259 }
260 
dealloc(void)261 void LDrawModelViewer::dealloc(void)
262 {
263 #ifndef USE_STD_CHRONO
264 #ifdef COCOA
265 	[FRAME_TIME release];
266 #endif // COCOA
267 #endif // !USE_STD_CHRONO
268 	TCAlertManager::unregisterHandler(this);
269 	TCObject::release(inputHandler);
270 	TCObject::release(mainTREModel);
271 	TCObject::release(mainModel);
272 	TCObject::release(whiteLightDirModel);
273 	TCObject::release(blueLightDirModel);
274 	TCObject::release(highlightModel);
275 	TCObject::release(exporter);
276 	mainTREModel = NULL;
277 	delete[] filename;
278 	filename = NULL;
279 	delete[] programPath;
280 	programPath = NULL;
281 	delete[] rotationMatrix;
282 	rotationMatrix = NULL;
283 	delete[] defaultRotationMatrix;
284 	defaultRotationMatrix = NULL;
285 	TCObject::release(fontImage1x);
286 	fontImage1x = NULL;
287 	TCObject::release(fontImage2x);
288 	fontImage2x = NULL;
289 	TCObject::release(extraSearchDirs);
290 	extraSearchDirs = NULL;
291 	delete cameraData;
292 	cameraData = NULL;
293 	TCObject::dealloc();
294 }
295 
getInputHandler(void)296 LDInputHandler *LDrawModelViewer::getInputHandler(void)
297 {
298 	if (!inputHandler)
299 	{
300 		inputHandler = new LDInputHandler(this);
301 	}
302 	return inputHandler;
303 }
304 
setFilename(const char * value)305 void LDrawModelViewer::setFilename(const char* value)
306 {
307 	delete[] filename;
308 	filename = copyString(value);
309 	mpdName = "";
310 	if (filename != NULL)
311 	{
312 		char *mpdSpot = NULL;
313 
314 #ifdef WIN32
315 		if (strlen(filename) > 2)
316 		{
317 			mpdSpot = strchr(&filename[2], ':');
318 		}
319 #else // WIN32
320 		mpdSpot = strchr(filename, ':');
321 #endif // WIN32
322 		if (mpdSpot != NULL)
323 		{
324 			mpdName = &mpdSpot[1];
325 			mpdSpot[0] = 0;
326 		}
327 	}
328 	flags.needsResetStep = true;
329 	flags.needsResetMpd = true;
330 	highlightPaths.clear();
331 }
332 
setProgramPath(const char * value)333 void LDrawModelViewer::setProgramPath(const char *value)
334 {
335 	delete[] programPath;
336 	programPath = copyString(value);
337 	stripTrailingPathSeparators(programPath);
338 }
339 
setFileIsPart(bool value)340 void LDrawModelViewer::setFileIsPart(bool value)
341 {
342 	flags.fileIsPart = value;
343 }
344 
applyTile(void)345 void LDrawModelViewer::applyTile(void)
346 {
347 	//if (1 || numXTiles > 1 || numYTiles > 1)
348 	{
349 		GLint tileLeft;
350 		GLint tileBottom;
351 		GLfloat xScale, yScale;
352 		GLfloat xOffset, yOffset;
353 
354 		tileLeft = (int)(xTile * scale(width));
355 		tileBottom = (int)((numYTiles - yTile - 1) * scale(height));
356 		xScale = (GLfloat)((width * numXTiles) / width);
357 		yScale = (GLfloat)((height * numYTiles) / height);
358 		xOffset = (-2.0f * tileLeft) / (scale(width) * numXTiles) +
359 			(1 - 1.0f / numXTiles);
360 		yOffset = (-2.0f * tileBottom) / (scale(height) * numYTiles) +
361 			(1 - 1.0f / numYTiles);
362 		glScalef(xScale, yScale, 1.0f);
363 		treGlTranslatef(xOffset, yOffset, 0.0f);
364 	}
365 }
366 
getStereoWidthModifier(void)367 TCFloat LDrawModelViewer::getStereoWidthModifier(void)
368 {
369 	if (stereoMode == LDVStereoCrossEyed || stereoMode == LDVStereoParallel)
370 	{
371 		return 2.0f;
372 	}
373 	else
374 	{
375 		return 1.0f;
376 	}
377 }
378 
setFieldOfView(double lfov,TCFloat nClip,TCFloat fClip)379 void LDrawModelViewer::setFieldOfView(double lfov, TCFloat nClip, TCFloat fClip)
380 {
381 	GLdouble aspectWidth, aspectHeight;
382 
383 //	printf("LDrawModelViewer::setFieldOfView(%.5f, %.5f, %.5f)\n", lfov, nClip,
384 //		fClip);
385 	glMatrixMode(GL_PROJECTION);
386 	glLoadIdentity();
387 	applyTile();
388 	aspectWidth = width * numXTiles / getStereoWidthModifier();
389 	aspectHeight = height * numYTiles * pixelAspectRatio;
390 #ifdef __clang__
391 #pragma clang diagnostic push
392 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
393 #endif
394 	gluPerspective(lfov, aspectWidth / aspectHeight, nClip, fClip);
395 #ifdef __clang__
396 #pragma clang diagnostic pop
397 #endif
398 	glMatrixMode(GL_MODELVIEW);
399 }
400 
perspectiveView(void)401 void LDrawModelViewer::perspectiveView(void)
402 {
403 	perspectiveView(true);
404 }
405 
setFov(TCFloat value)406 void LDrawModelViewer::setFov(TCFloat value)
407 {
408 	if (value != fov)
409 	{
410 		fov = value;
411 		flags.needsViewReset = true;
412 	}
413 }
414 
getHFov(void)415 TCFloat LDrawModelViewer::getHFov(void)
416 {
417 	TCFloat actualWidth = width / getStereoWidthModifier();
418 
419 	return (TCFloat)(2.0 * rad2deg(atan(tan(deg2rad(fov / 2.0)) *
420 		(actualWidth * numXTiles) / height * numYTiles)));
421 }
422 
updateCurrentFov(void)423 void LDrawModelViewer::updateCurrentFov(void)
424 {
425 	TCFloat actualWidth = width / getStereoWidthModifier();
426 
427 	currentFov = TCUserDefaults::floatForKey(HFOV_KEY, -1, false);
428 	if (currentFov == -1)
429 	{
430 		currentFov = fov;
431 		if (actualWidth * numXTiles < height * numYTiles)
432 		{
433 			// When the window is taller than it is wide, we want our current
434 			// FOV to be the horizontal FOV, so we need to calculate the
435 			// vertical FOV.
436 			//
437 			// From Lars Hassing:
438 			// Vertical FOV = 2*atan(tan(hfov/2)/(width/height))
439 			currentFov = (TCFloat)(2.0 * rad2deg(atan(tan(deg2rad(fov / 2.0)) *
440 				height * numYTiles / (actualWidth * numXTiles))));
441 
442 			if (currentFov > 179.0f)
443 			{
444 				currentFov = 179.0f;
445 			}
446 			aspectRatio = height / actualWidth;
447 		}
448 		else
449 		{
450 			aspectRatio = actualWidth / height;
451 		}
452 	}
453 	else
454 	{
455 		fov = (TCFloat)(2.0 * rad2deg(atan(tan(deg2rad(currentFov / 2.0)) *
456 			height * numYTiles / (actualWidth * numXTiles))));
457 		if (actualWidth * numXTiles > height * numYTiles)
458 		{
459 			currentFov = fov;
460 			aspectRatio = actualWidth / height;
461 		}
462 		else
463 		{
464 			aspectRatio = height / actualWidth;
465 		}
466 	}
467 }
468 
getClipRadius(void)469 TCFloat LDrawModelViewer::getClipRadius(void)
470 {
471 	TCFloat clipRadius;
472 
473 	if (flags.autoCenter)
474 	{
475 		clipRadius = clipSize / 1.45f;
476 	}
477 	else
478 	{
479 		// If we aren't centered, then just double the clip radius, and that
480 		// guarantees everything will fit.  Remember that the near clip plane
481 		// has a minimum distance, so even if it ends up initially behind the
482 		// camera, we clamp it to be in front.
483 		clipRadius = clipSize;
484 	}
485 	return clipRadius;
486 }
487 
getZDistance(void)488 TCFloat LDrawModelViewer::getZDistance(void)
489 {
490 	if (flags.autoCenter)
491 	{
492 		TCFloat inverseMatrix[16];
493 		TCVector vector = camera.getPosition();
494 
495 		camera.getFacing().getInverseMatrix(inverseMatrix);
496 		vector = vector.transformPoint(inverseMatrix);
497 		return vector[2];
498 	}
499 	else
500 	{
501 		return (center - camera.getPosition()).length();
502 	}
503 }
504 
perspectiveView(bool resetViewport)505 void LDrawModelViewer::perspectiveView(bool resetViewport)
506 {
507 	TCFloat nClip;
508 	TCFloat fClip;
509 	TCFloat clipRadius = getClipRadius();
510 	int actualWidth = (int)scale(width) / (int)getStereoWidthModifier();
511 	TCFloat zDistance = getZDistance();
512 	//TCFloat aspectAdjust = (TCFloat)tan(1.0f);
513 
514 	if (flags.forceZoomToFit)
515 	{
516 		zoomToFit();
517 	}
518 	updateCurrentFov();
519 	if (resetViewport)
520 	{
521 		glViewport(0, 0, actualWidth, (GLsizei)scale(height));
522 		flags.needsResize = false;
523 	}
524 	//aspectRatio = (TCFloat)(1.0f / tan(1.0f / aspectRatio)) * aspectAdjust;
525 	aspectRatio = 1.0f;
526 	nClip = zDistance - clipRadius * aspectRatio + clipAmount * aspectRatio *
527 		clipRadius;
528 	if (nClip < clipSize / 1000.0f)
529 	{
530 		nClip = clipSize / 1000.0f;
531 	}
532 	fClip = zDistance + clipRadius * aspectRatio;
533 	setFieldOfView(currentFov, nClip, fClip);
534 	glLoadIdentity();
535 	glFogi(GL_FOG_MODE, GL_LINEAR);
536 	glFogf(GL_FOG_START, (GLfloat)nClip);
537 	glFogf(GL_FOG_END, (GLfloat)fClip);
538 }
539 
perspectiveViewToClipPlane(void)540 void LDrawModelViewer::perspectiveViewToClipPlane(void)
541 {
542 	TCFloat nClip;
543 	TCFloat fClip;
544 	TCFloat zDistance = getZDistance();
545 //	TCFloat zDistance = (camera.getPosition()).length();
546 	TCFloat clipRadius = getClipRadius();
547 //	TCFloat distance = (camera.getPosition() - center).length();
548 
549 	nClip = zDistance - clipSize / 2.0f;
550 //	fClip = distance - size * aspectRatio / 2.0f + clipAmount * aspectRatio *
551 //		size;
552 	fClip = zDistance - clipRadius * aspectRatio + clipAmount * aspectRatio *
553 		clipRadius;
554 	if (fClip < clipSize / 1000.0f)
555 	{
556 		fClip = clipSize / 1000.0f;
557 	}
558 	if (nClip < clipSize / 1000.0f)
559 	{
560 		nClip = clipSize / 1000.0f;
561 	}
562 	setFieldOfView(currentFov, nClip, fClip);
563 	glLoadIdentity();
564 	glFogi(GL_FOG_MODE, GL_LINEAR);
565 	glFogf(GL_FOG_START, (GLfloat)nClip);
566 	glFogf(GL_FOG_END, (GLfloat)fClip);
567 }
568 
569 /*
570 void LDrawModelViewer::setProgressCallback(LDMProgressCallback callback,
571 										   void* userData)
572 {
573 	progressCallback = callback;
574 	progressUserData = userData;
575 }
576 
577 void LDrawModelViewer::setErrorCallback(LDMErrorCallback callback,
578 										void* userData)
579 {
580 	errorCallback = callback;
581 	errorUserData = userData;
582 }
583 */
584 
skipCameraPositioning(void)585 bool LDrawModelViewer::skipCameraPositioning(void)
586 {
587 	return defaultRotationMatrix && (defaultRotationMatrix[12] != 0.0f ||
588 		defaultRotationMatrix[13] != 0.0f || defaultRotationMatrix[14] != 0.0f);
589 }
590 
calcDefaultDistance(void)591 TCFloat LDrawModelViewer::calcDefaultDistance(void)
592 {
593 	if (flags.overrideDefaultDistance)
594 	{
595 		return defaultDistance;
596 	}
597 	// Note that the margin is on all sides, so it's on two edges per axis,
598 	// which is why we multiply by 2.
599 	TCFloat margin = getWideLineMargin() * 2.0f;
600 	TCFloat marginAdjust = 1.0f;
601 
602 	if (margin != 0.0f)
603 	{
604 		int actualWidth = (int)scale(width) / (int)getStereoWidthModifier() *
605 			numXTiles;
606 		int actualHeight = (int)scale(height) * numYTiles;
607 
608 		if (actualWidth < actualHeight)
609 		{
610 			marginAdjust = (actualHeight + margin) / actualHeight;
611 		}
612 		else
613 		{
614 			marginAdjust = (actualWidth + margin) / actualWidth;
615 		}
616 	}
617 	return (TCFloat)(size / 2.0 / sin(deg2rad(fov / 2.0))) * distanceMultiplier
618 		* marginAdjust;
619 /*
620 	double angle1 = deg2rad(90.0f - (currentFov / 2.0));
621 	double angle2 = deg2rad(currentFov / 4.0);
622 	double radius = size / 2.0;
623 
624 	return (TCFloat)(radius * tan(angle1) + radius * tan(angle2)) *
625 		distanceMultiplier;
626 */
627 }
628 
resetView(LDVAngle viewAngle)629 void LDrawModelViewer::resetView(LDVAngle viewAngle)
630 {
631 	flags.needsViewReset = false;
632 	flags.autoCenter = true;
633 	if (!mainTREModel)
634 	{
635 		return;
636 	}
637 	if (clipAmount != 0.0f)
638 	{
639 		clipAmount = 0.0f;
640 		perspectiveView();
641 	}
642 	defaultDistance = calcDefaultDistance();
643 	if (!skipCameraPositioning())
644 	{
645 		// If the user specifies a rotation matrix that includes a translation,
646 		// then don't move the camera.
647 		camera.setPosition(TCVector(0.0f, 0.0f, defaultDistance));
648 	}
649 	camera.setFacing(LDLFacing());
650 	if (!rotationMatrix)
651 	{
652 		rotationMatrix = new TCFloat[16];
653 	}
654 	switch (viewAngle)
655 	{
656 	case LDVAngleDefault:
657 		setupDefaultViewAngle();
658 		break;
659 	case LDVAngleFront:
660 		setupFrontViewAngle();
661 		break;
662 	case LDVAngleBack:
663 		setupBackViewAngle();
664 		break;
665 	case LDVAngleLeft:
666 		setupLeftViewAngle();
667 		break;
668 	case LDVAngleRight:
669 		setupRightViewAngle();
670 		break;
671 	case LDVAngleTop:
672 		setupTopViewAngle();
673 		break;
674 	case LDVAngleBottom:
675 		setupBottomViewAngle();
676 		break;
677 	case LDVAngleIso:
678 		setupIsoViewAngle();
679 		break;
680 	}
681 	flags.needsRotationMatrixSetup = true;
682 	xPan = 0.0f;
683 	yPan = 0.0f;
684 	perspectiveView(true);
685 }
686 
setModelCenter(const TCVector & value)687 void LDrawModelViewer::setModelCenter(const TCVector &value)
688 {
689 	if (value)
690 	{
691 		flags.overrideModelCenter = true;
692 		center = value;
693 		flags.needsSetup = true;
694 	}
695 }
696 
setPovCameraAspect(bool value,bool saveSetting)697 void LDrawModelViewer::setPovCameraAspect(bool value, bool saveSetting)
698 {
699 	if (value != flags.povCameraAspect)
700 	{
701 		flags.povCameraAspect = value;
702 		if (saveSetting)
703 		{
704 			TCUserDefaults::setBoolForKey(value, POV_CAMERA_ASPECT_KEY, false);
705 		}
706 	}
707 }
708 
setModelSize(const TCFloat value)709 void LDrawModelViewer::setModelSize(const TCFloat value)
710 {
711 	if (value)
712 	{
713 		flags.overrideModelSize = true;
714 		size = value;
715 		clipSize = value;
716 		flags.needsSetup = true;
717 	}
718 }
719 
setDefaultDistance(TCFloat value)720 void LDrawModelViewer::setDefaultDistance(TCFloat value)
721 {
722 	if (value > 0)
723 	{
724 		defaultDistance = value;
725 		flags.overrideDefaultDistance = true;
726 	}
727 }
728 
setDefaultRotationMatrix(const TCFloat * value)729 void LDrawModelViewer::setDefaultRotationMatrix(const TCFloat *value)
730 {
731 	if (value)
732 	{
733 		if (!defaultRotationMatrix || memcmp(defaultRotationMatrix, value,
734 			16 * sizeof(TCFloat)) != 0)
735 		{
736 			delete[] defaultRotationMatrix;
737 			defaultRotationMatrix = new TCFloat[16];
738 			memcpy(defaultRotationMatrix, value, 16 * sizeof(TCFloat));
739 			flags.needsSetup = true;
740 		}
741 	}
742 	else if (defaultRotationMatrix)
743 	{
744 		delete[] defaultRotationMatrix;
745 		defaultRotationMatrix = NULL;
746 		flags.needsSetup = true;
747 	}
748 }
749 
setDefaultLatLong(TCFloat latitude,TCFloat longitude)750 void LDrawModelViewer::setDefaultLatLong(TCFloat latitude, TCFloat longitude)
751 {
752 	if (defaultLatitude != latitude || defaultLongitude != longitude)
753 	{
754 		defaultLatitude = latitude;
755 		defaultLongitude = longitude;
756 		flags.needsSetup = true;
757 	}
758 }
759 
setupDefaultViewAngle(void)760 void LDrawModelViewer::setupDefaultViewAngle(void)
761 {
762 	if (viewMode == VMExamine && examineMode == EMLatLong)
763 	{
764 		examineLatitude = defaultLatitude;
765 		examineLongitude = defaultLongitude;
766 	}
767 	else if (defaultRotationMatrix)
768 	{
769 		memcpy(rotationMatrix, defaultRotationMatrix, 16 * sizeof(TCFloat));
770 	}
771 	else
772 	{
773 		setupIsoViewAngle();
774 	}
775 }
776 
setupIsoViewAngle(void)777 void LDrawModelViewer::setupIsoViewAngle(void)
778 {
779 	rotationMatrix[0] = (TCFloat)(sqrt(2.0) / 2.0);
780 	rotationMatrix[1] = (TCFloat)(sqrt(2.0) / 4.0);
781 	rotationMatrix[2] = (TCFloat)(-sqrt(1.5) / 2.0);
782 	rotationMatrix[3] = 0.0f;
783 	rotationMatrix[4] = 0.0f;
784 	rotationMatrix[5] = (TCFloat)(sin(M_PI / 3.0));
785 	rotationMatrix[6] = 0.5f;
786 	rotationMatrix[7] = 0.0f;
787 	rotationMatrix[8] = (TCFloat)(sqrt(2.0) / 2.0);
788 	rotationMatrix[9] = (TCFloat)(-sqrt(2.0) / 4.0);
789 	rotationMatrix[10] = (TCFloat)(sqrt(1.5) / 2.0);
790 	rotationMatrix[11] = 0.0f;
791 	rotationMatrix[12] = 0.0f;
792 	rotationMatrix[13] = 0.0f;
793 	rotationMatrix[14] = 0.0f;
794 	rotationMatrix[15] = 1.0f;
795 	examineLatitude = 30.0f;
796 	examineLongitude = 45.0f;
797 }
798 
setupFrontViewAngle(void)799 void LDrawModelViewer::setupFrontViewAngle(void)
800 {
801 	rotationMatrix[0] = 1.0f;
802 	rotationMatrix[1] = 0.0f;
803 	rotationMatrix[2] = 0.0f;
804 	rotationMatrix[3] = 0.0f;
805 
806 	rotationMatrix[4] = 0.0f;
807 	rotationMatrix[5] = 1.0f;
808 	rotationMatrix[6] = 0.0f;
809 	rotationMatrix[7] = 0.0f;
810 
811 	rotationMatrix[8] = 0.0f;
812 	rotationMatrix[9] = 0.0f;
813 	rotationMatrix[10] = 1.0f;
814 	rotationMatrix[11] = 0.0f;
815 
816 	rotationMatrix[12] = 0.0f;
817 	rotationMatrix[13] = 0.0f;
818 	rotationMatrix[14] = 0.0f;
819 	rotationMatrix[15] = 1.0f;
820 	examineLatitude = 0.0f;
821 	examineLongitude = 0.0f;
822 }
823 
setupBackViewAngle(void)824 void LDrawModelViewer::setupBackViewAngle(void)
825 {
826 	rotationMatrix[0] = -1.0f;
827 	rotationMatrix[1] = 0.0f;
828 	rotationMatrix[2] = 0.0f;
829 	rotationMatrix[3] = 0.0f;
830 
831 	rotationMatrix[4] = 0.0f;
832 	rotationMatrix[5] = 1.0f;
833 	rotationMatrix[6] = 0.0f;
834 	rotationMatrix[7] = 0.0f;
835 
836 	rotationMatrix[8] = 0.0f;
837 	rotationMatrix[9] = 0.0f;
838 	rotationMatrix[10] = -1.0f;
839 	rotationMatrix[11] = 0.0f;
840 
841 	rotationMatrix[12] = 0.0f;
842 	rotationMatrix[13] = 0.0f;
843 	rotationMatrix[14] = 0.0f;
844 	rotationMatrix[15] = 1.0f;
845 	examineLatitude = 0.0f;
846 	examineLongitude = 180.0f;
847 }
848 
setupLeftViewAngle(void)849 void LDrawModelViewer::setupLeftViewAngle(void)
850 {
851 	rotationMatrix[0] = 0.0f;
852 	rotationMatrix[1] = 0.0f;
853 	rotationMatrix[2] = -1.0f;
854 	rotationMatrix[3] = 0.0f;
855 
856 	rotationMatrix[4] = 0.0f;
857 	rotationMatrix[5] = 1.0f;
858 	rotationMatrix[6] = 0.0f;
859 	rotationMatrix[7] = 0.0f;
860 
861 	rotationMatrix[8] = 1.0f;
862 	rotationMatrix[9] = 0.0f;
863 	rotationMatrix[10] = 0.0f;
864 	rotationMatrix[11] = 0.0f;
865 
866 	rotationMatrix[12] = 0.0f;
867 	rotationMatrix[13] = 0.0f;
868 	rotationMatrix[14] = 0.0f;
869 	rotationMatrix[15] = 1.0f;
870 	examineLatitude = 0.0f;
871 	examineLongitude = 90.0f;
872 }
873 
setupRightViewAngle(void)874 void LDrawModelViewer::setupRightViewAngle(void)
875 {
876 	rotationMatrix[0] = 0.0f;
877 	rotationMatrix[1] = 0.0f;
878 	rotationMatrix[2] = 1.0f;
879 	rotationMatrix[3] = 0.0f;
880 
881 	rotationMatrix[4] = 0.0f;
882 	rotationMatrix[5] = 1.0f;
883 	rotationMatrix[6] = 0.0f;
884 	rotationMatrix[7] = 0.0f;
885 
886 	rotationMatrix[8] = -1.0f;
887 	rotationMatrix[9] = 0.0f;
888 	rotationMatrix[10] = 0.0f;
889 	rotationMatrix[11] = 0.0f;
890 
891 	rotationMatrix[12] = 0.0f;
892 	rotationMatrix[13] = 0.0f;
893 	rotationMatrix[14] = 0.0f;
894 	rotationMatrix[15] = 1.0f;
895 	examineLatitude = 0.0f;
896 	examineLongitude = -90.0f;
897 }
898 
setupTopViewAngle(void)899 void LDrawModelViewer::setupTopViewAngle(void)
900 {
901 	rotationMatrix[0] = 1.0f;
902 	rotationMatrix[1] = 0.0f;
903 	rotationMatrix[2] = 0.0f;
904 	rotationMatrix[3] = 0.0f;
905 
906 	rotationMatrix[4] = 0.0f;
907 	rotationMatrix[5] = 0.0f;
908 	rotationMatrix[6] = 1.0f;
909 	rotationMatrix[7] = 0.0f;
910 
911 	rotationMatrix[8] = 0.0f;
912 	rotationMatrix[9] = -1.0f;
913 	rotationMatrix[10] = 0.0f;
914 	rotationMatrix[11] = 0.0f;
915 
916 	rotationMatrix[12] = 0.0f;
917 	rotationMatrix[13] = 0.0f;
918 	rotationMatrix[14] = 0.0f;
919 	rotationMatrix[15] = 1.0f;
920 	examineLatitude = 90.0f;
921 	examineLongitude = 0.0f;
922 }
923 
setupBottomViewAngle(void)924 void LDrawModelViewer::setupBottomViewAngle(void)
925 {
926 	rotationMatrix[0] = 1.0f;
927 	rotationMatrix[1] = 0.0f;
928 	rotationMatrix[2] = 0.0f;
929 	rotationMatrix[3] = 0.0f;
930 
931 	rotationMatrix[4] = 0.0f;
932 	rotationMatrix[5] = 0.0f;
933 	rotationMatrix[6] = -1.0f;
934 	rotationMatrix[7] = 0.0f;
935 
936 	rotationMatrix[8] = 0.0f;
937 	rotationMatrix[9] = 1.0f;
938 	rotationMatrix[10] = 0.0f;
939 	rotationMatrix[11] = 0.0f;
940 
941 	rotationMatrix[12] = 0.0f;
942 	rotationMatrix[13] = 0.0f;
943 	rotationMatrix[14] = 0.0f;
944 	rotationMatrix[15] = 1.0f;
945 	examineLatitude = -90.0f;
946 	examineLongitude = 0.0f;
947 }
948 
949 /*
950 void LDrawModelViewer::ldlErrorCallback(LDLError *error)
951 {
952 //	static int errorCount = 0;
953 
954 	if (error)
955 	{
956 		TCStringArray *extraInfo = error->getExtraInfo();
957 
958 		if (getDebugLevel() > 0)
959 		{
960 			printf("Error on line %d in: %s\n", error->getLineNumber(),
961 				error->getFilename());
962 			indentPrintf(4, "%s\n", error->getMessage());
963 			indentPrintf(4, "%s\n", error->getFileLine());
964 		}
965 		if (extraInfo)
966 		{
967 			int i;
968 			int count = extraInfo->getCount();
969 
970 			for (i = 0; i < count; i++)
971 			{
972 				indentPrintf(4, "%s\n", (*extraInfo)[i]);
973 			}
974 		}
975 	}
976 }
977 
978 void LDrawModelViewer::progressAlertCallback(TCProgressAlert *alert)
979 {
980 	if (alert)
981 	{
982 		printf("Progress message from %s:\n%s (%f)\n", alert->getSource(),
983 			alert->getMessage(), alert->getProgress());
984 	}
985 }
986 */
987 
haveLightDats(void) const988 bool LDrawModelViewer::haveLightDats(void) const
989 {
990 	return flags.drawLightDats && mainTREModel &&
991 		mainTREModel->getLightLocations().size() > 0;
992 }
993 
haveStandardLight(void)994 bool LDrawModelViewer::haveStandardLight(void)
995 {
996 	return flags.useLighting && (!haveLightDats() || !flags.optionalStandardLight);
997 }
998 
forceOneLight(void) const999 bool LDrawModelViewer::forceOneLight(void) const
1000 {
1001 	return flags.oneLight || flags.usesSpecular || !flags.defaultLightVector ||
1002 		(flags.bfc && (flags.redBackFaces | flags.greenFrontFaces |
1003 		flags.blueNeutralFaces)) || haveLightDats();
1004 }
1005 
loadModel(bool resetViewpoint)1006 int LDrawModelViewer::loadModel(bool resetViewpoint)
1007 {
1008 	int retValue = 0;
1009 
1010 	debugLog1s(LOGFILE_KEY, "Load Started for %s.\n", filename);
1011 	TCAlertManager::sendAlert(loadAlertClass(), this, _UC("ModelLoading"));
1012 	if (filename && filename[0])
1013 	{
1014 		if (clipAmount != 0.0f && resetViewpoint)
1015 		{
1016 			clipAmount = 0.0f;
1017 			perspectiveView();
1018 		}
1019 		if (loadLDLModel() && parseModel())
1020 		{
1021 			retValue = 1;
1022 			if (resetViewpoint)
1023 			{
1024 				resetView();
1025 			}
1026 		}
1027 	}
1028 	// This shouldn't be necessary, but something is occasionally setting the
1029 	// needsReload flag during loading.
1030 	flags.needsReload = false;
1031 	flags.needsLightingSetup = true;
1032 	if (retValue)
1033 	{
1034 		debugLog1s(LOGFILE_KEY, "Successfully loaded %s.\n", filename);
1035 		TCAlertManager::sendAlert(loadAlertClass(), this, _UC("ModelLoaded"));
1036 	}
1037 	else
1038 	{
1039 		TCObject::release(mainModel);
1040 		mainModel = NULL;
1041 		releaseTREModels();
1042 		step = -1;
1043 		TCAlertManager::sendAlert(loadAlertClass(), this,
1044 			_UC("ModelLoadCanceled"));
1045 		requestRedraw();
1046 		debugLog1s(LOGFILE_KEY, "Failed to load %s.\n", filename);
1047 	}
1048 	return retValue;
1049 }
1050 
releaseTREModels(void)1051 void LDrawModelViewer::releaseTREModels(void)
1052 {
1053 	TCObject::release(mainTREModel);
1054 	mainTREModel = NULL;
1055 	TCObject::release(whiteLightDirModel);
1056 	whiteLightDirModel = NULL;
1057 	TCObject::release(blueLightDirModel);
1058 	blueLightDirModel = NULL;
1059 	TCObject::release(highlightModel);
1060 	highlightModel = NULL;
1061 }
1062 
calcSize(void)1063 bool LDrawModelViewer::calcSize(void)
1064 {
1065 	bool abort = false;
1066 	LDLModel *curModel = getCurModel();
1067 
1068 	TCProgressAlert::send("LDrawModelViewer", ls(_UC("CalculatingSizeStatus")),
1069 		0.0f, &abort, this);
1070 	if (!abort)
1071 	{
1072 		curModel->getBoundingBox(boundingMin, boundingMax);
1073 		TCProgressAlert::send("LDrawModelViewer",
1074 			ls(_UC("CalculatingSizeStatus")), 0.5f, &abort, this);
1075 	}
1076 	if (!abort)
1077 	{
1078 		if (!flags.overrideModelCenter)
1079 		{
1080 			if (!getRotationCenter(center))
1081 			{
1082 				center = (boundingMin + boundingMax) / 2.0f;
1083 			}
1084 		}
1085 		if (!flags.overrideModelSize)
1086 		{
1087 			size = curModel->getMaxRadius(center, true) * 2.0f;
1088 			if (mainModel->getBBoxIgnoreUsed())
1089 			{
1090 				clipSize = curModel->getMaxRadius(center, false) * 2.0f;
1091 				if (size == 0.0)
1092 				{
1093 					size = clipSize;
1094 				}
1095 			}
1096 			else
1097 			{
1098 				clipSize = size;
1099 			}
1100 		}
1101 		TCProgressAlert::send("LDrawModelViewer",
1102 			ls(_UC("CalculatingSizeStatus")), 1.0f, &abort, this);
1103 		flags.needsCalcSize = false;
1104 	}
1105 	return !abort;
1106 }
1107 
loadLDLModel(void)1108 bool LDrawModelViewer::loadLDLModel(void)
1109 {
1110 	TCObject::release(mainModel);
1111 	mainModel = new LDLMainModel;
1112 	mainModel->setAlertSender(this);
1113 
1114 	// First, release the current TREModels, if they exist.
1115 	releaseTREModels();
1116 	mainModel->setLDConfig(m_ldConfig);
1117 	mainModel->setLowResStuds(!flags.qualityStuds);
1118 	mainModel->setGreenFrontFaces(flags.bfc && flags.greenFrontFaces);
1119 	mainModel->setRedBackFaces(flags.bfc && flags.redBackFaces);
1120 	mainModel->setBlueNeutralFaces(flags.bfc && flags.blueNeutralFaces);
1121 	mainModel->setBlackEdgeLines(flags.blackHighlights);
1122 	mainModel->setExtraSearchDirs(extraSearchDirs);
1123 	mainModel->setProcessLDConfig(flags.processLDConfig);
1124 	mainModel->setSkipValidation(flags.skipValidation);
1125 	mainModel->setBoundingBoxesOnly(flags.boundingBoxesOnly);
1126 	mainModel->setSeamWidth(seamWidth);
1127 	mainModel->setCheckPartTracker(flags.checkPartTracker);
1128 	mainModel->setTexmaps(flags.texmaps);
1129 	if (flags.needsResetMpd)
1130 	{
1131 		mpdChildIndex = 0;
1132 		flags.needsResetMpd = false;
1133 	}
1134 #ifdef TIME_MODEL_LOAD
1135 	auto start = std::chrono::high_resolution_clock::now();
1136 #endif // TIME_MODEL_LOAD
1137 	if (mainModel->load(filename))
1138 	{
1139 #ifdef TIME_MODEL_LOAD
1140 		auto end = std::chrono::high_resolution_clock::now();
1141 		std::chrono::duration<double> elapsed_seconds = end-start;
1142 		std::cout << "\n\nModel load of " << filename << " took " <<
1143 			elapsed_seconds.count() << "s\n\n\n";
1144 #endif // TIME_MODEL_LOAD
1145 		return calcSize();
1146 	}
1147 	else
1148 	{
1149 		return false;
1150 	}
1151 }
1152 
parseModel(void)1153 bool LDrawModelViewer::parseModel(void)
1154 {
1155 	LDModelParser *modelParser = NULL;
1156 	bool retValue = false;
1157 	LDLModel *model = NULL;
1158 
1159 	if (!mainModel)
1160 	{
1161 		return false;
1162 	}
1163 	if (flags.randomColors)
1164 	{
1165 		// Even though they're "random", make them the same each time the model
1166 		// is loaded.
1167 		srand((unsigned)time(NULL));
1168 	}
1169 	// Note: this is the only mainModel setting that's applied here in
1170 	// parseModel, but it needs to be here to make it so that a reload isn't
1171 	// required when you change the setting.  The setting doesn't affect the
1172 	// loading of the model at all, just how it reports colors while parsing.
1173 	mainModel->setRandomColors(flags.randomColors);
1174 	TCAlertManager::sendAlert(loadAlertClass(), this, _UC("ModelParsing"));
1175 	releaseTREModels();
1176 	if (flags.needsCalcSize && !calcSize())
1177 	{
1178 		return false;
1179 	}
1180 	modelParser = new LDModelParser(this);
1181 	modelParser->setAlertSender(this);
1182 	modelParser->setTexmapsFlag(getTexmaps());
1183 	model = getCurModel();
1184 	if (modelParser->parseMainModel(model))
1185 	{
1186 		mainTREModel = modelParser->getMainTREModel();
1187 		mainTREModel->setTexturesAfterTransparentFlag(getTexturesAfterTransparent());
1188 		mainTREModel->setTextureOffsetFactor(getTextureOffsetFactor());
1189 		mainTREModel->retain();
1190 		flags.needsRecompile = false;
1191 		flags.needsReparse = false;
1192 		retValue = true;
1193 		initLightDirModels();
1194 		TCProgressAlert::send("LDrawModelViewer", ls(_UC("Done")), 2.0f, this);
1195 		if (commandLineStep > 0)
1196 		{
1197 			setStep(commandLineStep);
1198 			flags.needsResetStep = false;
1199 			commandLineStep = -1;
1200 		}
1201 		if (flags.needsResetStep)
1202 		{
1203 			setStep(getNumSteps());
1204 			flags.needsResetStep = false;
1205 		}
1206 		else
1207 		{
1208 			mainTREModel->setStep(step);
1209 		}
1210 	}
1211 	modelParser->release();
1212 	highlightPathsChanged();
1213 	if (retValue)
1214 	{
1215 		TCAlertManager::sendAlert(loadAlertClass(), this, _UC("ModelParsed"));
1216 	}
1217 	else
1218 	{
1219 		TCAlertManager::sendAlert(loadAlertClass(), this,
1220 			_UC("ModelParseCanceled"));
1221 	}
1222 	return retValue;
1223 }
1224 
setShowsHighlightLines(bool value)1225 void LDrawModelViewer::setShowsHighlightLines(bool value)
1226 {
1227 	if (value != flags.showsHighlightLines)
1228 	{
1229 		flags.showsHighlightLines = value;
1230 		flags.needsReparse = true;
1231 	}
1232 }
1233 
setDrawConditionalHighlights(bool value)1234 void LDrawModelViewer::setDrawConditionalHighlights(bool value)
1235 {
1236 	if (flags.drawConditionalHighlights != value)
1237 	{
1238 		flags.drawConditionalHighlights = value;
1239 		if (flags.showsHighlightLines)
1240 		{
1241 			flags.needsReparse = true;
1242 		}
1243 	}
1244 }
1245 
setPerformSmoothing(bool value)1246 void LDrawModelViewer::setPerformSmoothing(bool value)
1247 {
1248 	if (flags.performSmoothing != value)
1249 	{
1250 		flags.performSmoothing = value;
1251 		flags.needsReparse = true;
1252 	}
1253 }
1254 
setForceZoomToFit(bool value)1255 void LDrawModelViewer::setForceZoomToFit(bool value)
1256 {
1257 	if (flags.forceZoomToFit != value)
1258 	{
1259 		flags.forceZoomToFit = value;
1260 	}
1261 }
1262 
setSaveAlpha(bool value)1263 void LDrawModelViewer::setSaveAlpha(bool value)
1264 {
1265 	if (flags.saveAlpha != value)
1266 	{
1267 		flags.saveAlpha = value;
1268 	}
1269 }
1270 
setSkipValidation(bool value)1271 void LDrawModelViewer::setSkipValidation(bool value)
1272 {
1273 	if (flags.skipValidation != value)
1274 	{
1275 		flags.skipValidation = value;
1276 		if (!value)
1277 		{
1278 			// If they turn the flag on, there's no point reloading.  If, on the
1279 			// other hand, they turn the flag off, we need to reload so that we
1280 			// can work around any problems the model might have.
1281 			flags.needsReload = true;
1282 		}
1283 	}
1284 }
1285 
setProcessLDConfig(bool value)1286 void LDrawModelViewer::setProcessLDConfig(bool value)
1287 {
1288 	if (flags.processLDConfig != value)
1289 	{
1290 		flags.processLDConfig = value;
1291 		flags.needsReload = true;
1292 	}
1293 }
1294 
setMemoryUsage(int value)1295 void LDrawModelViewer::setMemoryUsage(int value)
1296 {
1297 	if (value < 0 || value > 2)
1298 	{
1299 		value = 2;
1300 	}
1301 	if (value != memoryUsage)
1302 	{
1303 		memoryUsage = value;
1304 		flags.needsReparse = true;
1305 	}
1306 }
1307 
setLineSmoothing(bool value)1308 void LDrawModelViewer::setLineSmoothing(bool value)
1309 {
1310 	if (flags.lineSmoothing != value)
1311 	{
1312 		flags.lineSmoothing = value;
1313 		flags.needsReparse = true;
1314 	}
1315 }
1316 
setSubduedLighting(bool value)1317 void LDrawModelViewer::setSubduedLighting(bool value)
1318 {
1319 	if (value != flags.subduedLighting)
1320 	{
1321 		flags.subduedLighting = value;
1322 		flags.needsLightingSetup = true;
1323 	}
1324 }
1325 
recompile(void)1326 bool LDrawModelViewer::recompile(void)
1327 {
1328 	if (mainTREModel)
1329 	{
1330 		if (whiteLightDirModel != NULL)
1331 		{
1332 			// It crashes after an FSAA change if we simply recompile here.
1333 			// I suspect it's related to the VBO/VAR extensions.  Deleting the
1334 			// lightDirModel objects and recreating them causes the vertex
1335 			// buffers to be reloaded from scratch.
1336 			whiteLightDirModel->release();
1337 			whiteLightDirModel = NULL;
1338 			blueLightDirModel->release();
1339 			blueLightDirModel = NULL;
1340 			initLightDirModels();
1341 		}
1342 		if (highlightModel != NULL)
1343 		{
1344 			// See comment above about crash.
1345 			highlightModel->release();
1346 			highlightModel = NULL;
1347 			highlightPathsChanged();
1348 		}
1349 		mainTREModel->recompile();
1350 		flags.needsRecompile = false;
1351 		TCProgressAlert::send("LDrawModelViewer", ls(_UC("Done")), 2.0f, this);
1352 	}
1353 	return true;
1354 }
1355 
uncompile(void)1356 void LDrawModelViewer::uncompile(void)
1357 {
1358 	if (fontListBase)
1359 	{
1360 		glDeleteLists(fontListBase, 128);
1361 		fontListBase = 0;
1362 	}
1363 	if (mainTREModel)
1364 	{
1365 		mainTREModel->uncompile();
1366 	}
1367 	if (whiteLightDirModel)
1368 	{
1369 		whiteLightDirModel->uncompile();
1370 	}
1371 	if (blueLightDirModel)
1372 	{
1373 		blueLightDirModel->uncompile();
1374 	}
1375 	if (highlightModel)
1376 	{
1377 		highlightModel->uncompile();
1378 	}
1379 }
1380 
reparse(void)1381 void LDrawModelViewer::reparse(void)
1382 {
1383 	if (mainModel)
1384 	{
1385 		parseModel();
1386 	}
1387 	flags.needsReparse = false;
1388 }
1389 
reload(void)1390 void LDrawModelViewer::reload(void)
1391 {
1392 	if (filename)
1393 	{
1394 		bool lastStep = step == getNumSteps() - 1;
1395 
1396 		loadModel(false);
1397 		if (lastStep)
1398 		{
1399 			setStep(getNumSteps());
1400 		}
1401 	}
1402 	flags.needsReload = false;
1403 }
1404 
setObi(bool value)1405 void LDrawModelViewer::setObi(bool value)
1406 {
1407 	if (value != flags.obi)
1408 	{
1409 		flags.obi = value;
1410 		flags.needsReparse = true;
1411 	}
1412 }
1413 
setGl2ps(bool value)1414 void LDrawModelViewer::setGl2ps(bool value)
1415 {
1416 	if (value != flags.gl2ps)
1417 	{
1418 		flags.gl2ps = value;
1419 		if (mainTREModel)
1420 		{
1421 			mainTREModel->setGl2psFlag(value);
1422 		}
1423 	}
1424 }
1425 
setUsesSpecular(bool value)1426 void LDrawModelViewer::setUsesSpecular(bool value)
1427 {
1428 	if (value != flags.usesSpecular)
1429 	{
1430 		flags.usesSpecular = value;
1431 		flags.needsMaterialSetup = true;
1432 		flags.needsLightingSetup = true;
1433 	}
1434 }
1435 
setOneLight(bool value)1436 void LDrawModelViewer::setOneLight(bool value)
1437 {
1438 	if (value != flags.oneLight)
1439 	{
1440 		flags.oneLight = value;
1441 		flags.needsLightingSetup = true;
1442 	}
1443 }
1444 
setupMaterial(void)1445 void LDrawModelViewer::setupMaterial(void)
1446 {
1447 //	GLfloat mAmbient[] = {0.5f, 0.5f, 0.5f, 1.0f};
1448 	GLfloat mAmbient[] = {0.0f, 0.0f, 0.0f, 1.0f};
1449 //	GLfloat mSpecular[] = {1.0f, 1.0f, 1.0f, 1.0f};
1450 	GLfloat mSpecular[] = {0.5f, 0.5f, 0.5f, 1.0f};
1451 //	GLfloat mSpecular[] = {0.0f, 0.0f, 0.0f, 1.0f};
1452 
1453 	// Note: default emission is <0,0,0,1>, which is what we want.
1454 	if (!flags.usesSpecular)
1455 	{
1456 		mSpecular[0] = mSpecular[1] = mSpecular[2] = 0.0f;
1457 	}
1458 	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mAmbient);
1459 	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mSpecular);
1460 	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 64.0f);
1461 	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
1462 	glEnable(GL_COLOR_MATERIAL);
1463 	flags.needsMaterialSetup = false;
1464 }
1465 
setupLight(GLenum light,const TCVector & color)1466 void LDrawModelViewer::setupLight(GLenum light, const TCVector &color)
1467 {
1468 	GLfloat lDiffuse[4];
1469 	GLfloat lSpecular[4];
1470 	int i;
1471 
1472 	if (flags.subduedLighting)
1473 	{
1474 		for (i = 0; i < 3; i++)
1475 		{
1476 			lDiffuse[i] = color.get(i) * 0.5f;
1477 		}
1478 	}
1479 	else
1480 	{
1481 		for (i = 0; i < 3; i++)
1482 		{
1483 			lDiffuse[i] = color.get(i);
1484 		}
1485 	}
1486 	if (!flags.usesSpecular)
1487 	{
1488 		lSpecular[0] = lSpecular[1] = lSpecular[2] = 0.0f;
1489 	}
1490 	else
1491 	{
1492 		for (i = 0; i < 3; i++)
1493 		{
1494 			lSpecular[i] = color.get(i);
1495 		}
1496 	}
1497 	lDiffuse[3] = 1.0f;
1498 	lSpecular[3] = 1.0f;
1499 	glLightfv(light, GL_SPECULAR, lSpecular);
1500 	glLightfv(light, GL_DIFFUSE, lDiffuse);
1501 	glEnable(light);
1502 }
1503 
setupLighting(void)1504 void LDrawModelViewer::setupLighting(void)
1505 {
1506 	glDisable(GL_NORMALIZE);
1507 	if (flags.useLighting)
1508 	{
1509 		GLint maxLights;
1510 		int i;
1511 		bool lightDats = haveLightDats();
1512 		GLfloat ambient[] = {0.2f, 0.2f, 0.2f, 1.0f};
1513 
1514 		if (flags.subduedLighting)
1515 		{
1516 			ambient[0] = ambient[1] = ambient[2] = 0.7f;
1517 		}
1518 		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
1519 		glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
1520 		glEnable(GL_LIGHTING);
1521 		glGetIntegerv(GL_MAX_LIGHTS, &maxLights);
1522 		for (i = 0; i < maxLights; i++)
1523 		{
1524 			glDisable(GL_LIGHT0 + i);
1525 		}
1526 		if (!lightDats || !flags.optionalStandardLight)
1527 		{
1528 			setupLight(GL_LIGHT0);
1529 			glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 1.0f);
1530 			glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
1531 			glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0f);
1532 		}
1533 		if (lightDats)
1534 		{
1535 			const TCULongList &lightColors = mainTREModel->getLightColors();
1536 			TCULongList::const_iterator itColor = lightColors.begin();
1537 			float lightScale = 1.0f / 255.0f;
1538 			float atten = (float)sqr(size);
1539 			int start = 0;
1540 
1541 			glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
1542 			if (!flags.optionalStandardLight)
1543 			{
1544 				start = 1;
1545 			}
1546 			for (i = start; i < maxLights && itColor != lightColors.end(); i++)
1547 			{
1548 				TCByte rgb[4];
1549 				TCULong color = htonl(*itColor);
1550 				float minBrightness = 0.5f;
1551 				float brightness;
1552 
1553 				memcpy(rgb, &color, 4);
1554 				brightness = std::max(std::max(rgb[0] * lightScale,
1555 					rgb[1] * lightScale), rgb[2] * lightScale) * (1.0f - minBrightness) +
1556 					minBrightness;
1557 				setupLight(GL_LIGHT0 + i, TCVector(rgb[0] * lightScale,
1558 					rgb[1] * lightScale, rgb[2] * lightScale));
1559 				glLightf(GL_LIGHT0 + i, GL_CONSTANT_ATTENUATION, 0.5f);
1560 				glLightf(GL_LIGHT0 + i, GL_LINEAR_ATTENUATION, 0.0f);//1.0f / size);
1561 				glLightf(GL_LIGHT0 + i, GL_QUADRATIC_ATTENUATION, 2.0f / atten /
1562 					brightness);
1563 				itColor++;
1564 			}
1565 		}
1566 		else
1567 		{
1568 			if (forceOneLight())
1569 			{
1570 				glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
1571 			}
1572 			else
1573 			{
1574 				setupLight(GL_LIGHT1);
1575 				glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
1576 			}
1577 		}
1578 		flags.needsLightingSetup = false;
1579 	}
1580 	else
1581 	{
1582 		glDisable(GL_LIGHTING);
1583 		flags.needsLightingSetup = false;
1584 	}
1585 }
1586 
setFontData(TCByte * fontData,long length)1587 void LDrawModelViewer::setFontData(TCByte *fontData, long length)
1588 {
1589 	if (length == 4096)
1590 	{
1591 		int i, j;
1592 		TCByte *imageData = NULL;
1593 		int rowSize;
1594 		int imageSize;
1595 
1596 		fontImage1x = new TCImage;
1597 		fontImage1x->setFlipped(true);
1598 		fontImage1x->setLineAlignment(4);
1599 		fontImage1x->setDataFormat(TCRgba8);
1600 		fontImage1x->setSize(FONT_IMAGE_WIDTH, FONT_IMAGE_HEIGHT);
1601 		fontImage1x->allocateImageData();
1602 		imageData = fontImage1x->getImageData();
1603 		rowSize = fontImage1x->getRowSize();
1604 		imageSize = rowSize * FONT_IMAGE_HEIGHT;
1605 		for (i = 0; i < imageSize; i++)
1606 		{
1607 			if (i % 4 == 3)
1608 			{
1609 				imageData[i] = 0x00;	// Init alpha channel to clear.
1610 			}
1611 			else
1612 			{
1613 				imageData[i] = 0xFF;	// Init color channels to white.
1614 			}
1615 		}
1616 		for (i = 0; i < 4096; i++)
1617 		{
1618 			int row = i / 256 * 16 + i % 16;	// logical row (kinda)
1619 			int col = i / 16 % 16;				// logical column
1620 			int yOffset = (FONT_IMAGE_HEIGHT - row - 1) * rowSize;
1621 			TCByte fontByte = fontData[i];
1622 
1623 			for (j = 0; j < 8; j++)
1624 			{
1625 				if (fontByte & (1 << (7 - j)))
1626 				{
1627 					int offset = yOffset + (col * FONT_CHAR_WIDTH + j) * 4 + 3;
1628 
1629 					// That spot should be on, so set the bit.
1630 					if (offset < 131072)
1631 					{
1632 						imageData[offset] = 0xFF;
1633 					}
1634 				}
1635 			}
1636 		}
1637 	}
1638 }
1639 
1640 // Loads a font file in the format of VGA text-mode font data.
loadVGAFont(const char * fontFilename)1641 void LDrawModelViewer::loadVGAFont(const char *fontFilename)
1642 {
1643 	if (fontImage1x == NULL)
1644 	{
1645 		FILE *fontFile = ucfopen(fontFilename, "rb");
1646 
1647 		if (fontFile)
1648 		{
1649 			TCByte fontData[4096];
1650 
1651 			if (fread(fontData, 1, 4096, fontFile) == 4096)
1652 			{
1653 				setFontData(fontData, 4096);
1654 			}
1655 			fclose(fontFile);
1656 		}
1657 	}
1658 }
1659 
setFont2x(TCImage * image)1660 void LDrawModelViewer::setFont2x(TCImage *image)
1661 {
1662 	if (fontImage2x == NULL)
1663 	{
1664 		fontImage2x = TCObject::retain(image);
1665 	}
1666 }
1667 
setRawFont2xData(const TCByte * data,long length)1668 void LDrawModelViewer::setRawFont2xData(const TCByte *data, long length)
1669 {
1670 	if (fontImage2x == NULL)
1671 	{
1672 		int rowSize;
1673 		const int fontWidth = 256;
1674 		const int fontHeight = 512;
1675 
1676 		fontImage2x = new TCImage;
1677 		fontImage2x->setFlipped(true);
1678 		fontImage2x->setLineAlignment(4);
1679 		fontImage2x->setDataFormat(TCRgba8);
1680 		fontImage2x->setSize(fontWidth, fontHeight);
1681 		fontImage2x->allocateImageData();
1682 		rowSize = fontImage2x->getRowSize();
1683 		if (length == rowSize * fontHeight)
1684 		{
1685 			TCByte *imageData = fontImage2x->getImageData();
1686 			int i;
1687 
1688 			for (i = 0; i < fontHeight; ++i)
1689 			{
1690 				memcpy(imageData + rowSize * (fontHeight - 1 - i),
1691 					   data + rowSize * i, rowSize);
1692 			}
1693 		}
1694 		else
1695 		{
1696 			fontImage2x->release();
1697 			fontImage2x = NULL;
1698 		}
1699 	}
1700 }
1701 
setupFont2x(const char * fontFilename)1702 void LDrawModelViewer::setupFont2x(const char *fontFilename)
1703 {
1704 	if (fontImage2x == NULL)
1705 	{
1706 		fontImage2x = new TCImage;
1707 		fontImage2x->setFlipped(true);
1708 		fontImage2x->setLineAlignment(4);
1709 		if (!fontImage2x->loadFile(fontFilename))
1710 		{
1711 			fontImage2x->release();
1712 		}
1713 	}
1714 	setupFont(NULL);
1715 }
1716 
setupFont(const char * fontFilename)1717 void LDrawModelViewer::setupFont(const char *fontFilename)
1718 {
1719 //	printf("LDrawModelViewer::setupFont\n");
1720 	if (fontFilename)
1721 	{
1722 		loadVGAFont(fontFilename);
1723 	}
1724 	TCImage *fontImage = fontImage1x;
1725 	TCFloat fontScale = scaleFactor;
1726 	if (scaleFactor >= 1.5 && fontImage2x != NULL)
1727 	{
1728 		fontImage = fontImage2x;
1729 		fontScale = scaleFactor / 2.0;
1730 	}
1731 	if (fontImage != NULL)
1732 	{
1733 		int i;
1734 		int fontImageWidth = fontImage->getWidth();
1735 		int fontImageHeight = fontImage->getHeight();
1736 		int fontCharWidth = fontImageWidth / 16;
1737 		int fontCharHeight = fontImageHeight / 16;
1738 
1739 		glGenTextures(1, &fontTextureID);
1740 		glBindTexture(GL_TEXTURE_2D, fontTextureID);
1741 		glTexImage2D(GL_TEXTURE_2D, 0, 4, fontImageWidth, fontImageHeight,
1742 			0, GL_RGBA, GL_UNSIGNED_BYTE, fontImage->getImageData());
1743 		if (fontListBase)
1744 		{
1745 			glDeleteLists(fontListBase, FONT_NUM_CHARACTERS);
1746 		}
1747 		fontListBase = glGenLists(FONT_NUM_CHARACTERS);
1748 		for (i = 0; i < FONT_NUM_CHARACTERS; i++)
1749 		{
1750 			TCFloat cx, cy;
1751 			TCFloat wx, hy;
1752 			TCFloat tx, ty;
1753 
1754 			cx = (TCFloat)(i % 16) * fontCharWidth /
1755 				(TCFloat)(fontImageWidth);
1756 			cy = (TCFloat)(i / 16) * fontCharHeight /
1757 				(TCFloat)(fontImageHeight);
1758 			wx = (TCFloat)fontCharWidth / fontImageWidth;
1759 			hy = (TCFloat)fontCharHeight / fontImageHeight;
1760 			glNewList(fontListBase + i, GL_COMPILE);
1761 				glBegin(GL_QUADS);
1762 					tx = cx;
1763 					ty = 1.0f - cy - hy;
1764 					treGlTexCoord2f(tx, ty);			// Bottom Left
1765 					glVertex2i(0, 0);
1766 					tx = cx + wx;
1767 					ty = 1.0f - cy - hy;
1768 					treGlTexCoord2f(tx, ty);			// Bottom Right
1769 					glVertex2i(fontCharWidth * fontScale, 0);
1770 					tx = cx + wx;
1771 					ty = 1 - cy;
1772 					treGlTexCoord2f(tx, ty);			// Top Right
1773 					glVertex2i(fontCharWidth * fontScale,
1774 						fontCharHeight * fontScale);
1775 					tx = cx;
1776 					ty = 1 - cy;
1777 					treGlTexCoord2f(tx , ty);			// Top Left
1778 					glVertex2i(0, fontCharHeight * fontScale);
1779 				glEnd();
1780 				glTranslated((fontCharWidth + 1) * fontScale, 0, 0);
1781 			glEndList();
1782 		}
1783 	}
1784 }
1785 
setupTextures(void)1786 void LDrawModelViewer::setupTextures(void)
1787 {
1788 	if (programPath)
1789 	{
1790 		char textureFilename[1024];
1791 
1792 		sprintf(textureFilename, "%s/StudLogo.png", programPath);
1793 		TREMainModel::loadStudTexture(textureFilename);
1794 //		sprintf(textureFilename, "%s/Font.png", programPath);
1795 		sprintf(textureFilename, "%s/SansSerif.fnt", programPath);
1796 		setupFont(textureFilename);
1797 	}
1798 	else if (fontImage1x != NULL)
1799 	{
1800 		setupFont(NULL);
1801 	}
1802 	flags.needsTextureSetup = false;
1803 }
1804 
setup(void)1805 void LDrawModelViewer::setup(void)
1806 {
1807 	glEnable(GL_DEPTH_TEST);
1808 	setupLighting();
1809 	setupMaterial();
1810 	setupTextures();
1811 	flags.needsSetup = false;
1812 }
1813 
drawBoundingBox(void)1814 void LDrawModelViewer::drawBoundingBox(void)
1815 {
1816 	int lightingEnabled = glIsEnabled(GL_LIGHTING);
1817 
1818 	if (lightingEnabled)
1819 	{
1820 		glDisable(GL_LIGHTING);
1821 	}
1822 	if ((backgroundR + backgroundG + backgroundB) / 3.0 < 0.5)
1823 	{
1824 		glColor3ub(255, 255, 255);
1825 	}
1826 	else
1827 	{
1828 		glColor3ub(0, 0, 0);
1829 	}
1830 	glBegin(GL_LINE_STRIP);
1831 		treGlVertex3fv(boundingMin);
1832 		treGlVertex3f(boundingMax[0], boundingMin[1], boundingMin[2]);
1833 		treGlVertex3f(boundingMax[0], boundingMax[1], boundingMin[2]);
1834 		treGlVertex3f(boundingMin[0], boundingMax[1], boundingMin[2]);
1835 		treGlVertex3fv(boundingMin);
1836 		treGlVertex3f(boundingMin[0], boundingMin[1], boundingMax[2]);
1837 		treGlVertex3f(boundingMax[0], boundingMin[1], boundingMax[2]);
1838 		treGlVertex3fv(boundingMax);
1839 		treGlVertex3f(boundingMin[0], boundingMax[1], boundingMax[2]);
1840 		treGlVertex3f(boundingMin[0], boundingMin[1], boundingMax[2]);
1841 	glEnd();
1842 	glBegin(GL_LINES);
1843 		treGlVertex3f(boundingMin[0], boundingMax[1], boundingMin[2]);
1844 		treGlVertex3f(boundingMin[0], boundingMax[1], boundingMax[2]);
1845 		treGlVertex3f(boundingMax[0], boundingMax[1], boundingMin[2]);
1846 		treGlVertex3f(boundingMax[0], boundingMax[1], boundingMax[2]);
1847 		treGlVertex3f(boundingMax[0], boundingMin[1], boundingMin[2]);
1848 		treGlVertex3f(boundingMax[0], boundingMin[1], boundingMax[2]);
1849 	glEnd();
1850 	if (lightingEnabled)
1851 	{
1852 		glEnable(GL_LIGHTING);
1853 	}
1854 }
1855 
orthoView(void)1856 void LDrawModelViewer::orthoView(void)
1857 {
1858 	int actualWidth = (int)scale(width);
1859 	int actualHeight = (int)scale(height);
1860 	const char *glVendor = "";
1861 	const GLubyte *origVendorString = glGetString(GL_VENDOR);
1862 
1863 	if (origVendorString)
1864 	{
1865 		glVendor = (const char *)origVendorString;
1866 	}
1867 	if (stereoMode == LDVStereoCrossEyed || stereoMode == LDVStereoParallel)
1868 	{
1869 		glViewport(0, 0, actualWidth, actualHeight);
1870 	}
1871 	glMatrixMode(GL_PROJECTION);
1872 	glLoadIdentity();
1873 #ifdef __clang__
1874 #pragma clang diagnostic push
1875 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1876 #endif
1877 	gluOrtho2D(0.0, actualWidth, 0.0, actualHeight);
1878 #ifdef __clang__
1879 #pragma clang diagnostic pop
1880 #endif
1881 	glMatrixMode(GL_MODELVIEW);
1882 	glLoadIdentity();
1883 	if (strncmp(glVendor, "ATI Technologies Inc.", 3) != 0)
1884 	{
1885 		// This doesn't work right on ATI video cards, so skip.
1886 		treGlTranslatef(0.375f, 0.375f, 0.0f);
1887 	}
1888 }
1889 
setSeamWidth(TCFloat value)1890 void LDrawModelViewer::setSeamWidth(TCFloat value)
1891 {
1892 	if (!fEq(value, seamWidth))
1893 	{
1894 		seamWidth = value;
1895 		flags.needsReparse = true;
1896 		if (mainModel)
1897 		{
1898 			mainModel->setSeamWidth(seamWidth);
1899 		}
1900 		flags.needsCalcSize = true;
1901 	}
1902 }
1903 
drawString(TCFloat xPos,TCFloat yPos,char * string)1904 void LDrawModelViewer::drawString(TCFloat xPos, TCFloat yPos, char* string)
1905 {
1906 	if (!fontListBase)
1907 	{
1908 		setupTextures();
1909 	}
1910 	if (!fontListBase)
1911 	{
1912 		// Font file wasn't found; bail.
1913 		return;
1914 	}
1915 	if ((backgroundR + backgroundG + backgroundB) / 3.0f > 0.5)
1916 	{
1917 		glColor3ub(0, 0, 0);
1918 	}
1919 	else
1920 	{
1921 		glColor3ub(255, 255, 255);
1922 	}
1923 	orthoView();
1924 	treGlTranslatef(xPos * scaleFactor, yPos * scaleFactor, 0);
1925 	glPushAttrib(GL_ENABLE_BIT | GL_POLYGON_BIT | GL_TEXTURE_BIT);
1926 	glDisable(GL_DEPTH_TEST);
1927 	glEnable(GL_TEXTURE_2D);
1928 	enable(GL_BLEND);
1929 	blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1930 	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1931 	glBindTexture(GL_TEXTURE_2D, fontTextureID);
1932 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1933 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1934 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1935 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1936 	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1937 	glListBase(fontListBase);
1938 	glCallLists((GLsizei)strlen(string), GL_UNSIGNED_BYTE, string);
1939 	glPopAttrib();
1940 	perspectiveView();
1941 }
1942 
drawFPS(TCFloat fps)1943 void LDrawModelViewer::drawFPS(TCFloat fps)
1944 {
1945 	if (mainTREModel && fps > 0.0f)
1946 	{
1947 		char fpsString[1024];
1948 		int lightingEnabled = glIsEnabled(GL_LIGHTING);
1949 		int zBufferEnabled = glIsEnabled(GL_DEPTH_TEST);
1950 
1951 		if (lightingEnabled)
1952 		{
1953 			glDisable(GL_LIGHTING);
1954 		}
1955 		if (zBufferEnabled)
1956 		{
1957 			glDisable(GL_DEPTH_TEST);
1958 		}
1959 		sprintf(fpsString, "%4.4f", fps);
1960 		drawString(2.0f, 0.0f, fpsString);
1961 		if (lightingEnabled)
1962 		{
1963 			glEnable(GL_LIGHTING);
1964 		}
1965 		if (zBufferEnabled)
1966 		{
1967 			glEnable(GL_DEPTH_TEST);
1968 		}
1969 	}
1970 }
1971 
drawLight(GLenum light,TCFloat x,TCFloat y,TCFloat z)1972 void LDrawModelViewer::drawLight(GLenum light, TCFloat x, TCFloat y, TCFloat z)
1973 {
1974 	GLfloat position[4];
1975 
1976 	position[0] = (GLfloat)x;
1977 	position[1] = (GLfloat)y;
1978 	position[2] = (GLfloat)z;
1979 	// 0.0 in the w component of the light "position" vector means that this is
1980 	// a directional light.  As such, the "position" provided is actually a
1981 	// direction (or maybe the inverse of the direction).
1982 	position[3] = 0.0f;
1983 	glLightfv(light, GL_POSITION, position);
1984 }
1985 
drawLights(void)1986 void LDrawModelViewer::drawLights(void)
1987 {
1988 	drawLight(GL_LIGHT0, lightVector[0], lightVector[1], lightVector[2]);
1989 	if (!forceOneLight())
1990 	{
1991 		drawLight(GL_LIGHT1, -lightVector[0], -lightVector[1], -lightVector[2]);
1992 	}
1993 }
1994 
setLightVector(const TCVector & value)1995 void LDrawModelViewer::setLightVector(const TCVector &value)
1996 {
1997 	TCFloat length = value.length();
1998 	bool oldForce = forceOneLight();
1999 
2000 	if (length)
2001 	{
2002 		lightVector = value / length;
2003 	}
2004 	if (lightVector.approxEquals(TCVector(0.0f, 0.0f, 1.0f), 0.000001f))
2005 	{
2006 		flags.defaultLightVector = true;
2007 	}
2008 	else
2009 	{
2010 		flags.defaultLightVector = false;
2011 	}
2012 	if (forceOneLight() != oldForce)
2013 	{
2014 		flags.needsLightingSetup = true;
2015 	}
2016 }
2017 
setBackgroundRGB(int r,int g,int b)2018 void LDrawModelViewer::setBackgroundRGB(int r, int g, int b)
2019 {
2020 	setBackgroundRGBA(r, g, b, 255);
2021 /*
2022 	backgroundR = (GLclampf)r / 255.0f;
2023 	backgroundG = (GLclampf)g / 255.0f;
2024 	backgroundB = (GLclampf)b / 255.0f;
2025 	backgroundA = 1.0f;
2026 */
2027 }
2028 
setDefaultRGB(TCByte r,TCByte g,TCByte b,bool transparent)2029 void LDrawModelViewer::setDefaultRGB(TCByte r, TCByte g, TCByte b,
2030 									 bool transparent)
2031 {
2032 	if (defaultR != r || defaultG != g || defaultB != b ||
2033 		flags.defaultTrans != transparent)
2034 	{
2035 		defaultR = r;
2036 		defaultG = g;
2037 		defaultB = b;
2038 		flags.defaultTrans = transparent;
2039 		if (mainTREModel && defaultColorNumber == -1)
2040 		{
2041 			flags.needsReparse = true;
2042 		}
2043 	}
2044 }
2045 
getDefaultRGB(TCByte & r,TCByte & g,TCByte & b,bool & transparent) const2046 void LDrawModelViewer::getDefaultRGB(
2047 	TCByte &r,
2048 	TCByte &g,
2049 	TCByte &b,
2050 	bool &transparent) const
2051 {
2052 	r = defaultR;
2053 	g = defaultG;
2054 	b = defaultB;
2055 	transparent = flags.defaultTrans;
2056 }
2057 
setDefaultColorNumber(int value)2058 void LDrawModelViewer::setDefaultColorNumber(int value)
2059 {
2060 	if (value != defaultColorNumber)
2061 	{
2062 		defaultColorNumber = value;
2063 		flags.needsReparse = true;
2064 	}
2065 }
2066 
setBackgroundRGBA(int r,int g,int b,int a)2067 void LDrawModelViewer::setBackgroundRGBA(int r, int g, int b, int a)
2068 {
2069 	backgroundR = (GLclampf)r / 255.0f;
2070 	backgroundG = (GLclampf)g / 255.0f;
2071 	backgroundB = (GLclampf)b / 255.0f;
2072 	backgroundA = (GLclampf)a / 255.0f;
2073 }
2074 
setRedBackFaces(bool value)2075 void LDrawModelViewer::setRedBackFaces(bool value)
2076 {
2077 	if (value != flags.redBackFaces)
2078 	{
2079 		flags.redBackFaces = value;
2080 		if (flags.bfc)
2081 		{
2082 			flags.needsReload = true;
2083 			flags.needsLightingSetup = true;
2084 		}
2085 	}
2086 }
2087 
setGreenFrontFaces(bool value)2088 void LDrawModelViewer::setGreenFrontFaces(bool value)
2089 {
2090 	if (value != flags.greenFrontFaces)
2091 	{
2092 		flags.greenFrontFaces = value;
2093 		if (flags.bfc)
2094 		{
2095 			flags.needsReload = true;
2096 			flags.needsLightingSetup = true;
2097 		}
2098 	}
2099 }
2100 
setBlueNeutralFaces(bool value)2101 void LDrawModelViewer::setBlueNeutralFaces(bool value)
2102 {
2103 	if (value != flags.blueNeutralFaces)
2104 	{
2105 		flags.blueNeutralFaces = value;
2106 		if (flags.bfc)
2107 		{
2108 			flags.needsReload = true;
2109 			flags.needsLightingSetup = true;
2110 		}
2111 	}
2112 }
2113 
setBfc(bool value)2114 void LDrawModelViewer::setBfc(bool value)
2115 {
2116 	if (value != flags.bfc)
2117 	{
2118 		flags.bfc = value;
2119 		flags.needsReparse = true;
2120 		if (flags.redBackFaces || flags.greenFrontFaces ||
2121 			flags.blueNeutralFaces)
2122 		{
2123 			flags.needsReload = true;
2124 			//flags.needsMaterialSetup = true;
2125 			flags.needsLightingSetup = true;
2126 		}
2127 	}
2128 }
2129 
setDrawWireframe(bool value)2130 void LDrawModelViewer::setDrawWireframe(bool value)
2131 {
2132 	if (value != flags.drawWireframe)
2133 	{
2134 		flags.drawWireframe = value;
2135 		flags.needsReparse = true;
2136 	}
2137 }
2138 
setUseWireframeFog(bool value)2139 void LDrawModelViewer::setUseWireframeFog(bool value)
2140 {
2141 	flags.useWireframeFog = value;
2142 }
2143 
setDrawLightDats(bool value)2144 void LDrawModelViewer::setDrawLightDats(bool value)
2145 {
2146 	if (value != flags.drawLightDats)
2147 	{
2148 		flags.drawLightDats = value;
2149 		flags.needsLightingSetup = true;
2150 	}
2151 }
2152 
setOptionalStandardLight(bool value)2153 void LDrawModelViewer::setOptionalStandardLight(bool value)
2154 {
2155 	if (value != flags.optionalStandardLight)
2156 	{
2157 		flags.optionalStandardLight = value;
2158 		flags.needsLightingSetup = true;
2159 	}
2160 }
2161 
setNoLightGeom(bool value)2162 void LDrawModelViewer::setNoLightGeom(bool value)
2163 {
2164 	if (value != flags.noLightGeom)
2165 	{
2166 		flags.noLightGeom = value;
2167 		flags.needsReparse = true;
2168 	}
2169 }
2170 
setRemoveHiddenLines(bool value)2171 void LDrawModelViewer::setRemoveHiddenLines(bool value)
2172 {
2173 	if (value != flags.removeHiddenLines)
2174 	{
2175 		flags.removeHiddenLines = value;
2176 		flags.needsReparse = true;
2177 	}
2178 }
2179 
setEdgesOnly(bool value)2180 void LDrawModelViewer::setEdgesOnly(bool value)
2181 {
2182 	if (value != flags.edgesOnly)
2183 	{
2184 		flags.edgesOnly = value;
2185 	}
2186 	if (mainTREModel)
2187 	{
2188 		bool realValue = flags.edgesOnly && flags.showsHighlightLines;
2189 
2190 		if (realValue != mainTREModel->getEdgesOnlyFlag())
2191 		{
2192 			flags.needsReparse = true;
2193 		}
2194 	}
2195 }
2196 
setHiResPrimitives(bool value)2197 void LDrawModelViewer::setHiResPrimitives(bool value)
2198 {
2199 	if (value != flags.hiResPrimitives)
2200 	{
2201 		flags.hiResPrimitives = value;
2202 		flags.needsReparse = true;
2203 	}
2204 }
2205 
setUsePolygonOffset(bool value)2206 void LDrawModelViewer::setUsePolygonOffset(bool value)
2207 {
2208 	if (value != flags.usePolygonOffset)
2209 	{
2210 		flags.usePolygonOffset = value;
2211 		if (mainTREModel)
2212 		{
2213 			mainTREModel->setPolygonOffsetFlag(flags.usePolygonOffset);
2214 			if (!flags.usePolygonOffset)
2215 			{
2216 				disable(GL_POLYGON_OFFSET_FILL);
2217 			}
2218 		}
2219 	}
2220 }
2221 
setMultiThreaded(bool value)2222 void LDrawModelViewer::setMultiThreaded(bool value)
2223 {
2224 	if (value != flags.multiThreaded)
2225 	{
2226 		flags.multiThreaded = value;
2227 		flags.needsReparse = true;
2228 	}
2229 
2230 }
2231 
setBlackHighlights(bool value)2232 void LDrawModelViewer::setBlackHighlights(bool value)
2233 {
2234 	if (value != flags.blackHighlights)
2235 	{
2236 		flags.blackHighlights = value;
2237 		if (flags.showsHighlightLines)
2238 		{
2239 			flags.needsReload = true;
2240 		}
2241 	}
2242 }
2243 
setShowAllConditionalLines(bool value)2244 void LDrawModelViewer::setShowAllConditionalLines(bool value)
2245 {
2246 	if (value != flags.showAllConditionalLines)
2247 	{
2248 		flags.showAllConditionalLines = value;
2249 		flags.needsReparse = true;
2250 	}
2251 }
2252 
setShowConditionalControlPoints(bool value)2253 void LDrawModelViewer::setShowConditionalControlPoints(bool value)
2254 {
2255 	if (value != flags.showConditionalControlPoints)
2256 	{
2257 		flags.showConditionalControlPoints = value;
2258 		flags.needsReparse = true;
2259 	}
2260 }
2261 
setCurveQuality(int value)2262 void LDrawModelViewer::setCurveQuality(int value)
2263 {
2264 	if (value != curveQuality)
2265 	{
2266 		curveQuality = value;
2267 		if (flags.allowPrimitiveSubstitution)
2268 		{
2269 			flags.needsReparse = true;
2270 		}
2271 	}
2272 }
2273 
setRandomColors(bool value)2274 void LDrawModelViewer::setRandomColors(bool value)
2275 {
2276 	if (value != flags.randomColors)
2277 	{
2278 		flags.randomColors = value;
2279 		flags.needsReparse = true;
2280 	}
2281 }
2282 
setTextureStuds(bool value)2283 void LDrawModelViewer::setTextureStuds(bool value)
2284 {
2285 	if (value != flags.textureStuds)
2286 	{
2287 		flags.textureStuds = value;
2288 //		TGLStudLogo::setTextureStuds(value);
2289 		flags.needsReparse = true;
2290 	}
2291 }
2292 
setTextureFilterType(int value)2293 void LDrawModelViewer::setTextureFilterType(int value)
2294 {
2295 	if (value != textureFilterType)
2296 	{
2297 		textureFilterType = value;
2298 		if (mainTREModel)
2299 		{
2300 			mainTREModel->setStudTextureFilter(value);
2301 		}
2302 //		TGLStudLogo::setTextureFilterType(value);
2303 //		flags.needsRecompile = true;
2304 //		flags.needsTextureSetup = true;
2305 	}
2306 }
2307 
setScaleFactor(TCFloat value)2308 void LDrawModelViewer::setScaleFactor(TCFloat value)
2309 {
2310 	if (value != scaleFactor)
2311 	{
2312 		if (fontListBase)
2313 		{
2314 			glDeleteLists(fontListBase, 128);
2315 			fontListBase = 0;
2316 		}
2317 		scaleFactor = value;
2318 		flags.needsResize = true;
2319 		if (mainTREModel)
2320 		{
2321 			mainTREModel->setEdgeLineWidth(getScaledHighlightLineWidth());
2322 		}
2323 		requestRedraw();
2324 	}
2325 }
2326 
setWidth(TCFloat value)2327 void LDrawModelViewer::setWidth(TCFloat value)
2328 {
2329 	if (value != width)
2330 	{
2331 		width = value;
2332 		flags.needsResize = true;
2333 	}
2334 }
2335 
setHeight(TCFloat value)2336 void LDrawModelViewer::setHeight(TCFloat value)
2337 {
2338 	if (value != height)
2339 	{
2340 		height = value;
2341 		flags.needsResize = true;
2342 	}
2343 }
2344 
setXTile(int value)2345 void LDrawModelViewer::setXTile(int value)
2346 {
2347 	if (value != xTile)
2348 	{
2349 		xTile = value;
2350 		flags.needsResize = true;
2351 	}
2352 }
2353 
setYTile(int value)2354 void LDrawModelViewer::setYTile(int value)
2355 {
2356 	if (value != yTile)
2357 	{
2358 		yTile = value;
2359 		flags.needsResize = true;
2360 	}
2361 }
2362 
setNumXTiles(int value)2363 void LDrawModelViewer::setNumXTiles(int value)
2364 {
2365 	if (value != numXTiles)
2366 	{
2367 		numXTiles = value;
2368 		flags.needsResize = true;
2369 	}
2370 }
2371 
setNumYTiles(int value)2372 void LDrawModelViewer::setNumYTiles(int value)
2373 {
2374 	if (value != numYTiles)
2375 	{
2376 		numYTiles = value;
2377 		flags.needsResize = true;
2378 	}
2379 }
2380 
setStereoMode(LDVStereoMode mode)2381 void LDrawModelViewer::setStereoMode(LDVStereoMode mode)
2382 {
2383 	if (mode != stereoMode)
2384 	{
2385 		stereoMode = mode;
2386 		flags.needsResize = true;
2387 	}
2388 }
2389 
setCutawayMode(LDVCutawayMode mode)2390 void LDrawModelViewer::setCutawayMode(LDVCutawayMode mode)
2391 {
2392 	cutawayMode = mode;
2393 }
2394 
getScaledCutawayLineWidth(void) const2395 TCFloat32 LDrawModelViewer::getScaledCutawayLineWidth(void) const
2396 {
2397 	return std::max(1.0f, scale(cutawayLineWidth));
2398 }
2399 
setCutawayLineWidth(TCFloat32 value)2400 void LDrawModelViewer::setCutawayLineWidth(TCFloat32 value)
2401 {
2402 	cutawayLineWidth = value;
2403 }
2404 
setCutawayAlpha(TCFloat32 value)2405 void LDrawModelViewer::setCutawayAlpha(TCFloat32 value)
2406 {
2407 	cutawayAlpha = value;
2408 }
2409 
setUseLighting(bool value)2410 void LDrawModelViewer::setUseLighting(bool value)
2411 {
2412 	if (value != flags.useLighting)
2413 	{
2414 		flags.useLighting = value;
2415 		if (mainTREModel)
2416 		{
2417 			mainTREModel->setLightingFlag(value);
2418 		}
2419 		flags.needsRecompile = true;
2420 		flags.needsLightingSetup = true;
2421 	}
2422 }
2423 
setUseStipple(bool value)2424 void LDrawModelViewer::setUseStipple(bool value)
2425 {
2426 	if (value != flags.useStipple)
2427 	{
2428 		flags.useStipple = value;
2429 		if (mainTREModel)
2430 		{
2431 			mainTREModel->setStippleFlag(value);
2432 		}
2433 		if (flags.sortTransparent)
2434 		{
2435 			flags.needsReparse = true;
2436 		}
2437 		else
2438 		{
2439 //			flags.needsRecompile = true;
2440 		}
2441 	}
2442 }
2443 
setSortTransparent(bool value)2444 void LDrawModelViewer::setSortTransparent(bool value)
2445 {
2446 	if (value != flags.sortTransparent)
2447 	{
2448 		flags.sortTransparent = value;
2449 		flags.needsReparse = true;
2450 	}
2451 }
2452 
getScaledHighlightLineWidth(void) const2453 TCFloat32 LDrawModelViewer::getScaledHighlightLineWidth(void) const
2454 {
2455 	return std::max(1.0f, scale(highlightLineWidth));
2456 }
2457 
setHighlightLineWidth(TCFloat32 value)2458 void LDrawModelViewer::setHighlightLineWidth(TCFloat32 value)
2459 {
2460 	if (value != highlightLineWidth)
2461 	{
2462 		highlightLineWidth = value;
2463 		TCFloat32 scaledWidth = getScaledHighlightLineWidth();
2464 		if (mainTREModel)
2465 		{
2466 			mainTREModel->setEdgeLineWidth(scaledWidth);
2467 		}
2468 		if (highlightModel)
2469 		{
2470 			highlightModel->setEdgeLineWidth(scaledWidth);
2471 		}
2472 	}
2473 }
2474 
getScaledWireframeLineWidth(void) const2475 TCFloat32 LDrawModelViewer::getScaledWireframeLineWidth(void) const
2476 {
2477 	return std::max(1.0f, scale(wireframeLineWidth));
2478 }
2479 
setWireframeLineWidth(TCFloat32 value)2480 void LDrawModelViewer::setWireframeLineWidth(TCFloat32 value)
2481 {
2482 	wireframeLineWidth = value;
2483 }
2484 
setAnisoLevel(TCFloat32 value)2485 void LDrawModelViewer::setAnisoLevel(TCFloat32 value)
2486 {
2487 	if (value != anisoLevel)
2488 	{
2489 		anisoLevel = value;
2490 		if (mainTREModel)
2491 		{
2492 			mainTREModel->setStudAnisoLevel(anisoLevel);
2493 		}
2494 	}
2495 }
2496 
setTexmaps(bool value)2497 void LDrawModelViewer::setTexmaps(bool value)
2498 {
2499 	if (value != flags.texmaps)
2500 	{
2501 		flags.texmaps = value;
2502 		flags.needsReload = true;
2503 	}
2504 }
2505 
setTexturesAfterTransparent(bool value)2506 void LDrawModelViewer::setTexturesAfterTransparent(bool value)
2507 {
2508 	if (value != flags.texturesAfterTransparent)
2509 	{
2510 		flags.texturesAfterTransparent = value;
2511 		if (mainTREModel)
2512 		{
2513 			mainTREModel->setTexturesAfterTransparentFlag(value);
2514 		}
2515 		requestRedraw();
2516 	}
2517 }
2518 
setTextureOffsetFactor(TCFloat value)2519 void LDrawModelViewer::setTextureOffsetFactor(TCFloat value)
2520 {
2521 	if (value != textureOffsetFactor)
2522 	{
2523 		textureOffsetFactor = value;
2524 		if (mainTREModel)
2525 		{
2526 			mainTREModel->setTextureOffsetFactor(value);
2527 		}
2528 		requestRedraw();
2529 	}
2530 }
2531 
setUseStrips(bool value)2532 void LDrawModelViewer::setUseStrips(bool value)
2533 {
2534 	if (value != flags.useStrips)
2535 	{
2536 		flags.useStrips = value;
2537 		flags.needsReload = true;
2538 	}
2539 }
2540 
setQualityStuds(bool value)2541 void LDrawModelViewer::setQualityStuds(bool value)
2542 {
2543 	if (value != flags.qualityStuds)
2544 	{
2545 		flags.qualityStuds = value;
2546 		flags.needsReload = true;
2547 	}
2548 }
2549 
setAllowPrimitiveSubstitution(bool value)2550 void LDrawModelViewer::setAllowPrimitiveSubstitution(bool value)
2551 {
2552 	if (value != flags.allowPrimitiveSubstitution)
2553 	{
2554 		flags.allowPrimitiveSubstitution = value;
2555 		flags.needsReparse = true;
2556 	}
2557 }
2558 
updateCameraPosition(void)2559 void LDrawModelViewer::updateCameraPosition(void)
2560 {
2561 	float multiplier = 100.0f;
2562 
2563 #ifdef USE_STD_CHRONO
2564 	std::chrono::duration<double> diff = std::chrono::steady_clock::now() -
2565 		frameTime;
2566 	float factor = diff.count();
2567 #else
2568 	float factor;
2569 #ifdef WIN32
2570 	if (hrpcFrequency.QuadPart != 0)
2571 	{
2572 		LARGE_INTEGER newCount;
2573 
2574 		QueryPerformanceCounter(&newCount);
2575 		factor = (float)((newCount.QuadPart - hrpcFrameCount.QuadPart) /
2576 			(double)hrpcFrequency.QuadPart);
2577 	}
2578 	else
2579 	{
2580 		factor = (GetTickCount() - frameTicks) / 1000.0f;
2581 	}
2582 #endif // WIN32
2583 #ifdef _QT
2584 	factor = qtime.elapsed() /1000.0f;
2585 #endif
2586 #ifdef COCOA
2587 	if (FRAME_TIME != nil)
2588 	{
2589 		factor = (float)-[FRAME_TIME timeIntervalSinceNow];
2590 	}
2591 #endif // COCOA
2592 #if !defined(WIN32) && !defined(_QT) && !defined(COCOA)
2593 	factor = 1.0f / multiplier;
2594 #endif // None of the above
2595 #endif // !USE_STD_CHRONO
2596 	if (viewMode == VMWalk)
2597 	{
2598 		TCVector upVector(0.0f, -1.0f, 0.0f);
2599 		TCVector tempMotion(0.0f, 0.0f, 0.0f);
2600 		TCFloat matrix[16];
2601 		TCFloat inverseMatrix[16];
2602 		TCVector::invertMatrix(camera.getFacing().getMatrix(), inverseMatrix);
2603 		TCVector::multMatrix(inverseMatrix, rotationMatrix, matrix);
2604 		TCVector tempVector = upVector.transformNormal(matrix);
2605 		TCFloat motionAmount = 20.0f * (TCFloat)sqrt(fov / 45.0f);
2606 
2607 //		TCFloat identity[16];
2608 //		memcpy(identity, TCVector::sm_identityMatrix, sizeof(TCVector::sm_identityMatrix));
2609 //		TCVector::calcRotationMatrix(cameraXRotate, cameraYRotate, identity);
2610 
2611 		TCFloat identity[16] =
2612 		{
2613 			1.0, 0.0, 0.0, 0.0,
2614 			0.0, 1.0, 0.0, 0.0,
2615 			0.0, 0.0, 1.0, 0.0,
2616 			0.0, 0.0, 0.0, 1.0
2617 		};
2618 		TCVector::calcRotationMatrix(cameraXRotate, cameraYRotate, identity);
2619 		TCVector whateverVector = upVector.transformNormal(identity);
2620 
2621 		if (cameraMotion[2] > 0.0f)
2622 		{
2623 			tempMotion[1] += -tempVector[2];
2624 			tempMotion[2] += tempVector[1];
2625 		}
2626 		else if (cameraMotion[2] < 0.0f)
2627 		{
2628 			tempMotion[1] += tempVector[2];
2629 			tempMotion[2] += -tempVector[1];
2630 		}
2631 		if (cameraMotion[1] > 0.0f)
2632 		{
2633 			tempMotion[1] += (1 - tempVector[2]);
2634 			tempMotion[2] += (1 - tempVector[1]);
2635 		}
2636 		else if (cameraMotion[1] < 0.0f)
2637 		{
2638 			tempMotion[1] += -(1 - tempVector[2]);
2639 			tempMotion[2] += -(1 - tempVector[1]);
2640 		}
2641 		tempMotion[0] = cameraMotion[0];
2642 		camera.move(tempMotion * motionAmount * factor * multiplier);
2643 	}
2644 	else
2645 	{
2646 		camera.move(cameraMotion * size / 100.0f * factor * multiplier);
2647 	}
2648 	camera.rotate(TCVector(cameraXRotate, cameraYRotate, cameraZRotate) *
2649 		factor * multiplier * 1.5f);
2650 }
2651 
zoom(TCFloat amount,bool apply)2652 void LDrawModelViewer::zoom(TCFloat amount, bool apply)
2653 {
2654 	if (flags.paused)
2655 	{
2656 		return;
2657 	}
2658 	if (clipZoom)
2659 	{
2660 		nextClipAmount = clipAmount - amount / 1000.0f;
2661 
2662 		if (nextClipAmount > aspectRatio * 2)
2663 		{
2664 			nextClipAmount = aspectRatio * 2;
2665 		}
2666 /*
2667 		if (nextClipAmount > 1.0f)
2668 		{
2669 			nextClipAmount = 1.0f;
2670 		}
2671 */
2672 		else if (nextClipAmount < 0.0f)
2673 		{
2674 			nextClipAmount = 0.0f;
2675 		}
2676 	}
2677 	else
2678 	{
2679 		TCFloat distance = (camera.getPosition()).length();
2680 //		TCFloat distance = (camera.getPosition() - center).length();
2681 
2682 		nextDistance = distance + (amount * distance / 300.0f);
2683 		if (flags.constrainZoom && !skipCameraPositioning())
2684 		{
2685 			if (nextDistance < size / zoomMax)
2686 			{
2687 				nextDistance = size / zoomMax;
2688 			}
2689 		}
2690 		// We may as well always constrain the maximum zoom, since there not
2691 		// really any reason to move too far away.
2692 		if (nextDistance > defaultDistance * 10.0f && !skipCameraPositioning())
2693 		{
2694 			nextDistance = defaultDistance * 10.0f;
2695 		}
2696 	}
2697 	if (apply)
2698 	{
2699 		applyZoom();
2700 	}
2701 	if (!fEq(amount, 0))
2702 	{
2703 		requestRedraw();
2704 	}
2705 }
2706 
requestRedraw(void)2707 void LDrawModelViewer::requestRedraw(void)
2708 {
2709 	TCAlertManager::sendAlert(redrawAlertClass(), this);
2710 }
2711 
setShowBoundingBox(bool value)2712 void LDrawModelViewer::setShowBoundingBox(bool value)
2713 {
2714 	if (value != flags.showBoundingBox)
2715 	{
2716 		flags.showBoundingBox = value;
2717 		requestRedraw();
2718 	}
2719 }
2720 
setBoundingBoxesOnly(bool value)2721 void LDrawModelViewer::setBoundingBoxesOnly(bool value)
2722 {
2723 	if (value != flags.boundingBoxesOnly)
2724 	{
2725 		flags.boundingBoxesOnly = value;
2726 		flags.needsReparse = true;
2727 		flags.needsCalcSize = true;
2728 	}
2729 }
2730 
applyZoom(void)2731 void LDrawModelViewer::applyZoom(void)
2732 {
2733 	if (flags.paused)
2734 	{
2735 		return;
2736 	}
2737 	if (clipZoom)
2738 	{
2739 		if (!fEq(clipAmount, nextClipAmount))
2740 		{
2741 			clipAmount = nextClipAmount;
2742 			perspectiveView(false);
2743 		}
2744 	}
2745 	else
2746 	{
2747 		TCFloat distance = (camera.getPosition()).length();
2748 
2749 		if (!fEq(distance, nextDistance))
2750 		{
2751 			camera.move(TCVector(0.0f, 0.0f, nextDistance - distance));
2752 //			distance = newDistance;
2753 //			camera.move(TCVector(0.0f, 0.0f, amount * size / 300.0f));
2754 			perspectiveView(false);
2755 		}
2756 	}
2757 }
2758 
clearBackground(void)2759 void LDrawModelViewer::clearBackground(void)
2760 {
2761 	GLfloat backgroundColor[3];
2762 
2763 	if (cullBackFaces)
2764 	{
2765 		glEnable(GL_CULL_FACE);
2766 		glCullFace(GL_BACK);
2767 	}
2768 	else
2769 	{
2770 		glDisable(GL_CULL_FACE);
2771 	}
2772 	glClearDepth(1.0);
2773 	backgroundColor[0] = (GLfloat)backgroundR;
2774 	backgroundColor[1] = (GLfloat)backgroundG;
2775 	backgroundColor[2] = (GLfloat)backgroundB;
2776 	glFogfv(GL_FOG_COLOR, backgroundColor);
2777 	if (flags.slowClear && !getGl2ps())
2778 	{
2779 		GLint oldDepthFunc;
2780 		bool oldBlendEnabled = false;
2781 		bool oldPolygonOffsetEnabled = false;
2782 		bool oldLightingEnabled = false;
2783 
2784 		orthoView();
2785 		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
2786 		glGetIntegerv(GL_DEPTH_FUNC, &oldDepthFunc);
2787 		glDepthFunc(GL_ALWAYS);
2788 		glColor4f(backgroundR, backgroundG, backgroundB, backgroundA);
2789 		if (glIsEnabled(GL_BLEND))
2790 		{
2791 			glDisable(GL_BLEND);
2792 			oldBlendEnabled = true;
2793 		}
2794 		if (glIsEnabled(GL_POLYGON_OFFSET_FILL))
2795 		{
2796 			disable(GL_POLYGON_OFFSET_FILL);
2797 			oldPolygonOffsetEnabled = true;
2798 		}
2799 		if (glIsEnabled(GL_LIGHTING))
2800 		{
2801 			glDisable(GL_LIGHTING);
2802 			oldLightingEnabled = true;
2803 		}
2804 		glDepthMask(1);
2805 		glBegin(GL_QUADS);
2806 		glVertex3i(-1, -1, -1);
2807 		glVertex3i((GLint)scale(width) + 1, -1, -1);
2808 		glVertex3i((GLint)scale(width) + 1, (GLint)scale(height) + 1, -1);
2809 		glVertex3i(-1, (GLint)scale(height) + 1, -1);
2810 		glEnd();
2811 		glDepthFunc(oldDepthFunc);
2812 		if (oldBlendEnabled)
2813 		{
2814 			glEnable(GL_BLEND);
2815 		}
2816 		if (oldPolygonOffsetEnabled)
2817 		{
2818 			enable(GL_POLYGON_OFFSET_FILL);
2819 		}
2820 		if (oldLightingEnabled)
2821 		{
2822 			glEnable(GL_LIGHTING);
2823 		}
2824 		perspectiveView();
2825 	}
2826 	else
2827 	{
2828 		glClearColor(backgroundR, backgroundG, backgroundB, backgroundA);
2829 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
2830 			GL_STENCIL_BUFFER_BIT);
2831 	}
2832 	if (flags.drawWireframe)
2833 	{
2834 		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2835 		lineWidth(getScaledWireframeLineWidth());
2836 		if (flags.useWireframeFog)
2837 		{
2838 			glEnable(GL_FOG);
2839 		}
2840 		else
2841 		{
2842 			glDisable(GL_FOG);
2843 		}
2844 	}
2845 	else
2846 	{
2847 		lineWidth(getScaledHighlightLineWidth());
2848 		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
2849 		glDisable(GL_FOG);
2850 	}
2851 }
2852 
projectCamera(const TCVector & distance)2853 void LDrawModelViewer::projectCamera(const TCVector &distance)
2854 {
2855 	TCFloat inverseMatrix[16];
2856 
2857 	camera.getFacing().getInverseMatrix(inverseMatrix);
2858 	TCVector camCenter = -distance.mult(inverseMatrix) - camera.getPosition();
2859 	treGlMultMatrixf(inverseMatrix);
2860 	treGlTranslatef(camCenter[0], camCenter[1], camCenter[2]);
2861 }
2862 
drawSetup(TCFloat eyeXOffset)2863 void LDrawModelViewer::drawSetup(TCFloat eyeXOffset)
2864 {
2865 	glLoadIdentity();
2866 	if (flags.qualityLighting)
2867 	{
2868 		glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
2869 	}
2870 	else
2871 	{
2872 		glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 0);
2873 	}
2874 	if (flags.usesFlatShading)
2875 	{
2876 		glShadeModel(GL_FLAT);
2877 	}
2878 	else
2879 	{
2880 		glShadeModel(GL_SMOOTH);
2881 	}
2882 	drawLights();
2883 	glLoadIdentity();
2884 	if (mainTREModel)
2885 	{
2886 		applyZoom();
2887 //		camera.move(cameraMotion * size / 100.0f);
2888 		perspectiveView(false);
2889 //		camera.rotate(TCVector(cameraXRotate, cameraYRotate, cameraZRotate));
2890 		projectCamera(TCVector(-eyeXOffset - xPan, -yPan, 0.0f));
2891 //		treGlTranslatef(eyeXOffset + xPan, yPan, -distance);
2892 	}
2893 	else
2894 	{
2895 		projectCamera(TCVector(-eyeXOffset - xPan, -yPan, 10.0f));
2896 //		treGlTranslatef(eyeXOffset + xPan, yPan, -10.0f);
2897 	}
2898 }
2899 
setExamineMode(ExamineMode value)2900 void LDrawModelViewer::setExamineMode(ExamineMode value)
2901 {
2902 	examineMode = value;
2903 }
2904 
setViewMode(ViewMode value)2905 void LDrawModelViewer::setViewMode(ViewMode value)
2906 {
2907 	viewMode = value;
2908 }
2909 
innerDrawModel(void)2910 void LDrawModelViewer::innerDrawModel(void)
2911 {
2912 	mainTREModel->draw();
2913 	if (highlightModel != NULL)
2914 	{
2915 		if (flags.drawWireframe)
2916 		{
2917 			highlightModel->setEdgeLineWidth(getScaledWireframeLineWidth());
2918 		}
2919 		else
2920 		{
2921 			highlightModel->setEdgeLineWidth(getScaledHighlightLineWidth());
2922 		}
2923 		highlightModel->draw();
2924 	}
2925 	drawAxes(true);
2926 	if (flags.showBoundingBox)
2927 	{
2928 		drawBoundingBox();
2929 	}
2930 }
2931 
drawToClipPlaneUsingStencil(TCFloat eyeXOffset)2932 void LDrawModelViewer::drawToClipPlaneUsingStencil(TCFloat eyeXOffset)
2933 {
2934 	glPushAttrib(GL_POLYGON_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT |
2935 		GL_LINE_BIT | GL_STENCIL_BUFFER_BIT);
2936 	perspectiveViewToClipPlane();
2937 	glDisable(GL_DEPTH_TEST);
2938 	glClear(GL_STENCIL_BUFFER_BIT);
2939 	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
2940 	glEnable(GL_STENCIL_TEST);
2941 	glStencilFunc(GL_ALWAYS, 1, ~0u);
2942 	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
2943 	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2944 	lineWidth(getScaledCutawayLineWidth());
2945 	glDisable(GL_FOG);
2946 	glLoadIdentity();
2947 	projectCamera(TCVector(-eyeXOffset - xPan, -yPan, 0.0f));
2948 	if (viewMode == VMFlyThrough || viewMode == VMWalk || examineMode == EMFree)
2949 	{
2950 		if (rotationMatrix)
2951 		{
2952 			glPushMatrix();
2953 			glLoadIdentity();
2954 			treGlRotatef(rotationSpeed, xRotate, yRotate, zRotate);
2955 			treGlMultMatrixf(rotationMatrix);
2956 			treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
2957 			glPopMatrix();
2958 			treGlMultMatrixf(rotationMatrix);
2959 		}
2960 	}
2961 	else if (viewMode == VMExamine && examineMode == EMLatLong)
2962 	{
2963 		//examineLongitude += rotationSpeed;
2964 		//examineLatitude += rotationSpeed;
2965 		//examineLongitude = fmodf(examineLongitude, 360.0f);
2966 		//examineLatitude = fmodf(examineLatitude, 360.0f);
2967 		treGlRotatef(examineLongitude, 0.0f, 1.0f, 0.0f);
2968 		treGlRotatef(examineLatitude, 1.0f, 0.0f, 0.0f);
2969 	}
2970 	if (flags.autoCenter)
2971 	{
2972 		treGlTranslatef(-center[0], -center[1], -center[2]);
2973 	}
2974 	showLight();
2975 	innerDrawModel();
2976 	glDisable(GL_LIGHTING);
2977 	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
2978 	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
2979 	glMatrixMode(GL_PROJECTION);
2980 	glLoadIdentity();
2981 	glOrtho(0, 1, 0, 1, 0, 1);
2982 	glMatrixMode(GL_MODELVIEW);
2983 	glLoadIdentity();
2984 	if ((backgroundR + backgroundG + backgroundB) / 3.0f > 0.5)
2985 	{
2986 		glColor4f(0.0f, 0.0f, 0.0f, cutawayAlpha);
2987 	}
2988 	else
2989 	{
2990 		glColor4f(1.0f, 1.0f, 1.0f, cutawayAlpha);
2991 	}
2992 	enable(GL_BLEND);
2993 	blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2994 	glStencilFunc(GL_EQUAL, 1, ~0u);
2995 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
2996 	glRectf(0.0, 0.0, 1.0, 1.0);
2997 	perspectiveView();
2998 	glEnable(GL_DEPTH_TEST);
2999 	glDisable(GL_STENCIL_TEST);
3000 	disable(GL_BLEND);
3001 	glEnable(GL_LIGHTING);
3002 	glPopAttrib();
3003 }
3004 
3005 /*
3006 void LDrawModelViewer::drawToClipPlaneUsingAccum(GLfloat eyeXOffset)
3007 {
3008 	TCFloat weight = 0.25f;
3009 	TCFloat oldZoomSpeed = zoomSpeed;
3010 
3011 	glReadBuffer(GL_BACK);
3012 	glAccum(GL_LOAD, 1.0f - weight);
3013 	glClearColor(backgroundR, backgroundG, backgroundB, backgroundA);
3014 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3015 
3016 	zoomSpeed = 0.0f;
3017 	clearBackground();
3018 	drawSetup();
3019 	zoomSpeed = oldZoomSpeed;
3020 	if (rotationMatrix)
3021 	{
3022 		glPushMatrix();
3023 		glLoadIdentity();
3024 		treGlMultMatrixf(rotationMatrix);
3025 		treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3026 		glPopMatrix();
3027 		treGlMultMatrixf(rotationMatrix);
3028 	}
3029 	treGlTranslatef(-center[0], -center[1], -center[2]);
3030 	glColor3ub(192, 192, 192);
3031 	mainTREModel->draw();
3032 
3033 	glClearDepth(1.0);
3034 	glClear(GL_DEPTH_BUFFER_BIT);
3035 	perspectiveViewToClipPlane();
3036 	glClearDepth(1.0);
3037 	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
3038 	lineWidth(cutawayLineWidth);
3039 	glDisable(GL_FOG);
3040 	glLoadIdentity();
3041 	treGlTranslatef(eyeXOffset + xPan, yPan, -distance);
3042 	if (rotationMatrix)
3043 	{
3044 		glPushMatrix();
3045 		glLoadIdentity();
3046 		treGlMultMatrixf(rotationMatrix);
3047 		treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3048 		glPopMatrix();
3049 		treGlMultMatrixf(rotationMatrix);
3050 	}
3051 	treGlTranslatef(-center[0], -center[1], -center[2]);
3052 	mainTREModel->draw();
3053 	perspectiveView();
3054 	glAccum(GL_ACCUM, weight);
3055 	glAccum(GL_RETURN, 1.0f);
3056 }
3057 */
3058 
drawToClipPlaneUsingDestinationAlpha(TCFloat eyeXOffset)3059 void LDrawModelViewer::drawToClipPlaneUsingDestinationAlpha(TCFloat eyeXOffset)
3060 {
3061 	TCFloat32 weight = cutawayAlpha;
3062 
3063 	glPushAttrib(GL_POLYGON_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT |
3064 		GL_LINE_BIT);
3065 	perspectiveViewToClipPlane();
3066 	glClearDepth(1.0);
3067 	glClearColor(weight, weight, weight, weight);
3068 	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
3069 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3070 	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
3071 	enable(GL_BLEND);
3072 	blendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);
3073 	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
3074 	lineWidth(getScaledCutawayLineWidth());
3075 	glDisable(GL_FOG);
3076 	glLoadIdentity();
3077 	projectCamera(TCVector(-eyeXOffset - xPan, -yPan, 0.0f));
3078 //	treGlTranslatef(eyeXOffset + xPan, yPan, -distance);
3079 	//treGlTranslatef(0.0f, 0.0f, -distance);
3080 	if (rotationMatrix)
3081 	{
3082 		glPushMatrix();
3083 		glLoadIdentity();
3084 		treGlRotatef(rotationSpeed, xRotate, yRotate, zRotate);
3085 		treGlMultMatrixf(rotationMatrix);
3086 		treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3087 		glPopMatrix();
3088 		treGlMultMatrixf(rotationMatrix);
3089 	}
3090 	if (flags.autoCenter)
3091 	{
3092 		treGlTranslatef(-center[0], -center[1], -center[2]);
3093 	}
3094 	showLight();
3095 	mainTREModel->setCutawayDrawFlag(true);
3096 	innerDrawModel();
3097 	mainTREModel->setCutawayDrawFlag(false);
3098 	perspectiveView();
3099 	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
3100 	disable(GL_BLEND);
3101 	glPopAttrib();
3102 }
3103 
drawToClipPlaneUsingNoEffect(TCFloat eyeXOffset)3104 void LDrawModelViewer::drawToClipPlaneUsingNoEffect(TCFloat eyeXOffset)
3105 {
3106 	glPushAttrib(GL_POLYGON_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT |
3107 		GL_LINE_BIT);
3108 	perspectiveViewToClipPlane();
3109 	glClearDepth(1.0);
3110 	glClear(GL_DEPTH_BUFFER_BIT);
3111 //	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
3112 	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
3113 	lineWidth(getScaledCutawayLineWidth());
3114 	glDisable(GL_FOG);
3115 	glLoadIdentity();
3116 	projectCamera(TCVector(-eyeXOffset - xPan, -yPan, 0.0f));
3117 //	treGlTranslatef(eyeXOffset + xPan, yPan, -distance);
3118 	if (rotationMatrix)
3119 	{
3120 		glPushMatrix();
3121 		glLoadIdentity();
3122 		treGlRotatef(rotationSpeed, xRotate, yRotate, zRotate);
3123 		treGlMultMatrixf(rotationMatrix);
3124 		treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3125 		glPopMatrix();
3126 		treGlMultMatrixf(rotationMatrix);
3127 	}
3128 	if (flags.autoCenter)
3129 	{
3130 		treGlTranslatef(-center[0], -center[1], -center[2]);
3131 	}
3132 	showLight();
3133 	innerDrawModel();
3134 	perspectiveView();
3135 	glPopAttrib();
3136 //	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
3137 }
3138 
drawToClipPlane(TCFloat eyeXOffset)3139 void LDrawModelViewer::drawToClipPlane(TCFloat eyeXOffset)
3140 {
3141 	switch (cutawayMode)
3142 	{
3143 	case LDVCutawayNormal:
3144 		// Don't do anything
3145 		break;
3146 	case LDVCutawayWireframe:
3147 		if (fEq(cutawayAlpha, 1.0f))
3148 		{
3149 			drawToClipPlaneUsingNoEffect(eyeXOffset);
3150 		}
3151 		else
3152 		{
3153 			drawToClipPlaneUsingDestinationAlpha(eyeXOffset);
3154 		}
3155 		break;
3156 	case LDVCutawayStencil:
3157 		drawToClipPlaneUsingStencil(eyeXOffset);
3158 		break;
3159 	}
3160 //	drawToClipPlaneUsingAccum(eyeXOffset);
3161 }
3162 
clear(void)3163 void LDrawModelViewer::clear(void)
3164 {
3165 	glClearStencil(0);
3166 	glClearDepth(1.0);
3167 	glClearColor(backgroundR, backgroundG, backgroundB, backgroundA);
3168 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
3169 }
3170 
getLDrawCommandLineMatrix(char * matrixString,int bufferLength)3171 bool LDrawModelViewer::getLDrawCommandLineMatrix(char *matrixString,
3172 												 int bufferLength)
3173 {
3174 	if (rotationMatrix && bufferLength)
3175 	{
3176 		char buf[1024];
3177 		TCFloat matrix[16];
3178 		int i;
3179 		TCVector point;
3180 
3181 		if (flags.autoCenter)
3182 		{
3183 			point = -center;
3184 		}
3185 		for (i = 0; i < 16; i++)
3186 		{
3187 			if (fEq(rotationMatrix[i], 0.0f))
3188 			{
3189 				matrix[i] = 0.0f;
3190 			}
3191 			else if (i == 0 || i  == 4 || i == 8)
3192 			{
3193 				matrix[i] = rotationMatrix[i];
3194 			}
3195 			else
3196 			{
3197 				matrix[i] = -rotationMatrix[i];
3198 			}
3199 		}
3200 		point = point.transformPoint(matrix);
3201 		sprintf(buf, "-a%.2g,%.2g,%.2g,%.2g,%.2g,%.2g,%.2g,%.2g,%.2g",
3202 			matrix[0], matrix[4], matrix[8],
3203 			matrix[1], matrix[5], matrix[9],
3204 			matrix[2], matrix[6], matrix[10]);
3205 		strncpy(matrixString, buf, bufferLength);
3206 		matrixString[bufferLength - 1] = 0;
3207 		return true;
3208 	}
3209 	else
3210 	{
3211 		return false;
3212 	}
3213 }
3214 
getLDGLiteCommandLine(char * commandString,int bufferLength)3215 bool LDrawModelViewer::getLDGLiteCommandLine(char *commandString,
3216 											 int bufferLength)
3217 {
3218 	if (rotationMatrix && bufferLength)
3219 	{
3220 		char buf[1024];
3221 		char matrixString[512];
3222 		TCVector cameraPoint = camera.getPosition();
3223 //		TCVector cameraPoint = TCVector(0.0f, 0.0f, distance);
3224 		TCVector lookAt;
3225 
3226 		if (flags.autoCenter)
3227 		{
3228 			lookAt = center;
3229 		}
3230 		int i;
3231 		TCFloat transformationMatrix[16];
3232 
3233 		if (!getLDrawCommandLineMatrix(matrixString, 512))
3234 		{
3235 			return false;
3236 		}
3237 		TCVector::invertMatrix(rotationMatrix, transformationMatrix);
3238 		lookAt = lookAt.transformPoint(rotationMatrix);
3239 		cameraPoint += lookAt;
3240 //		cameraPoint = TGLShape::transformPoint(cameraPoint, rotationMatrix);
3241 		for (i = 0; i < 3; i++)
3242 		{
3243 			if (fEq(cameraPoint[i], 0.0f))
3244 			{
3245 				cameraPoint[i] = 0.0f;
3246 			}
3247 		}
3248 		sprintf(buf, "ldglite -J -v%d,%d "
3249 			"-cc%.4f,%.4f,%.4f -co%.4f,%.4f,%.4f "
3250 			"-cu0,1,0 %s \"%s\"", (int)scale(width), (int)scale(height),
3251 			cameraPoint[0], cameraPoint[1], cameraPoint[2],
3252 			lookAt[0], lookAt[1], lookAt[2],
3253 			matrixString, filename);
3254 		strncpy(commandString, buf, bufferLength);
3255 		commandString[bufferLength - 1] = 0;
3256 		return true;
3257 	}
3258 	return false;
3259 }
3260 
getLDrawCommandLine(char * shortFilename,char * commandString,int bufferLength)3261 bool LDrawModelViewer::getLDrawCommandLine(char *shortFilename,
3262 										   char *commandString,
3263 										   int bufferLength)
3264 {
3265 	if (rotationMatrix && bufferLength)
3266 	{
3267 		char buf[1024];
3268 		TCFloat matrix[16];
3269 		int i;
3270 		TCVector point;
3271 		TCFloat lScaleFactor = 500.0f;
3272 		TCFloat distance = (camera.getPosition()).length();
3273 //		TCFloat distance = (camera.getPosition() - center).length();
3274 
3275 		if (flags.autoCenter)
3276 		{
3277 			point = -center;
3278 		}
3279 		for (i = 0; i < 16; i++)
3280 		{
3281 			if (fEq(rotationMatrix[i], 0.0f))
3282 			{
3283 				matrix[i] = 0.0f;
3284 			}
3285 			else if (i == 0 || i  == 4 || i == 8)
3286 			{
3287 				matrix[i] = rotationMatrix[i];
3288 			}
3289 			else
3290 			{
3291 				matrix[i] = -rotationMatrix[i];
3292 			}
3293 		}
3294 		point = point.transformPoint(matrix);
3295 		sprintf(buf, "ldraw -s%.4g -o%d,%d "
3296 			"-a%.4g,%.4g,%.4g,%.4g,%.4g,%.4g,%.4g,%.4g,%.4g "
3297 			"%s",
3298 			lScaleFactor / distance,
3299 			(int)(point[0] * lScaleFactor / distance),
3300 			(int)(point[1] * lScaleFactor / distance),
3301 			matrix[0], matrix[4], matrix[8],
3302 			matrix[1], matrix[5], matrix[9],
3303 			matrix[2], matrix[6], matrix[10],
3304 			shortFilename);
3305 		strncpy(commandString, buf, bufferLength);
3306 		commandString[bufferLength - 1] = 0;
3307 		return true;
3308 	}
3309 	else
3310 	{
3311 		return false;
3312 	}
3313 }
3314 
3315 // Note: static method
fixLongitude(TCFloat & lon)3316 void LDrawModelViewer::fixLongitude(TCFloat &lon)
3317 {
3318 	lon = fmodf(lon, 360.0f);
3319 	if (lon > 180.0f)
3320 	{
3321 		lon -= 360.0f;
3322 	}
3323 	if (lon <= -180.0f)
3324 	{
3325 		lon += 360.0f;
3326 	}
3327 }
3328 
applyModelRotation(void)3329 void LDrawModelViewer::applyModelRotation(void)
3330 {
3331 	if (rotationMatrix)
3332 	{
3333 		if (!flags.paused)
3334 		{
3335 			if (viewMode == VMFlyThrough || viewMode == VMWalk || examineMode == EMFree)
3336 			{
3337 				TCFloat matrix[16];
3338 				TCVector rotation = TCVector(xRotate, yRotate, zRotate);
3339 
3340 				camera.getFacing().getInverseMatrix(matrix);
3341 				glPushMatrix();
3342 				glLoadIdentity();
3343 				rotation = rotation.mult(matrix);
3344 				treGlRotatef(rotationSpeed, rotation[0], rotation[1], rotation[2]);
3345 				treGlMultMatrixf(rotationMatrix);
3346 				treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3347 				glPopMatrix();
3348 			}
3349 			else if (viewMode == VMExamine && examineMode == EMLatLong)
3350 			{
3351 				if (rotationSpeed != 0)
3352 				{
3353 					examineLongitude += rotationSpeed * -yRotate * 0.1f;
3354 					fixLongitude(examineLongitude);
3355 					examineLatitude += rotationSpeed * xRotate * 0.2f;
3356 					if (examineLatitude > 90.0f)
3357 					{
3358 						examineLatitude = 90.0f;
3359 					}
3360 					if (examineLatitude < -90.0f)
3361 					{
3362 						examineLatitude = -90.0f;
3363 					}
3364 				}
3365 				glPushMatrix();
3366 				glLoadIdentity();
3367 				treGlRotatef(examineLatitude + 180.0f, 1.0f, 0.0f, 0.0f);
3368 				treGlRotatef(examineLongitude, 0.0f, 1.0f, 0.0f);
3369 				treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3370 				glPopMatrix();
3371 			}
3372 		}
3373 	}
3374 }
3375 
update(void)3376 void LDrawModelViewer::update(void)
3377 {
3378 	static GLubyte stipplePattern[128];
3379 	static bool stipplePatternSet = false;
3380 	TCFloat eyeXOffset = 0.0f;
3381 
3382 	flags.updating = true;
3383 	if (flags.needsReload)
3384 	{
3385 		reload();
3386 	}
3387 	if (flags.needsReparse)
3388 	{
3389 		parseModel();
3390 	}
3391 	if (!stipplePatternSet)
3392 	{
3393 		int i;
3394 
3395 		for (i = 0; i < 32; i++)
3396 		{
3397 			if (i % 2)
3398 			{
3399 				memset(stipplePattern + i * 4, 0xAA, 4);
3400 			}
3401 			else
3402 			{
3403 				memset(stipplePattern + i * 4, 0x55, 4);
3404 			}
3405 		}
3406 		stipplePatternSet = true;
3407 	}
3408 	if (!rotationMatrix)
3409 	{
3410 		rotationMatrix = new TCFloat[16];
3411 		setupDefaultViewAngle();
3412 		flags.needsRotationMatrixSetup = true;
3413 	}
3414 	if (flags.needsSetup)
3415 	{
3416 		setup();
3417 	}
3418 	if (flags.needsTextureSetup)
3419 	{
3420 		setupTextures();
3421 	}
3422 	if (flags.needsLightingSetup)
3423 	{
3424 		setupLighting();
3425 	}
3426 	if (flags.needsMaterialSetup)
3427 	{
3428 		setupMaterial();
3429 	}
3430 	else if (flags.needsRecompile)
3431 	{
3432 		recompile();
3433 	}
3434 	if (flags.needsViewReset)
3435 	{
3436 		perspectiveView();
3437 		resetView();
3438 	}
3439 	if (flags.needsResize)
3440 	{
3441 		perspectiveView();
3442 	}
3443 	glPolygonStipple(stipplePattern);
3444 	if (flags.needsRotationMatrixSetup)
3445 	{
3446 		setupRotationMatrix();
3447 	}
3448 	if ((flags.keepRightSideUp && viewMode == VMFlyThrough) || viewMode == VMWalk)
3449 	{
3450 		rightSideUp(false);
3451 	}
3452 	clearBackground();
3453 	if (!mainTREModel)
3454 	{
3455 		flags.updating = false;
3456 		return;
3457 	}
3458 	mainTREModel->setSaveAlphaFlag(flags.saveAlpha);
3459 	if (stereoMode == LDVStereoCrossEyed || stereoMode == LDVStereoParallel)
3460 	{
3461 		TCFloat distance = (camera.getPosition()).length();
3462 
3463 		eyeXOffset = stereoEyeSpacing * 2.0f / (TCFloat)pow((double)distance,
3464 			0.25);
3465 		if (stereoMode == LDVStereoCrossEyed)
3466 		{
3467 			eyeXOffset = -eyeXOffset;
3468 		}
3469 	}
3470 	if (rotationMatrix)
3471 	{
3472 		applyModelRotation();
3473 	}
3474 	updateCameraPosition();
3475 	zoom(zoomSpeed, false);
3476 	if (flags.drawWireframe && flags.removeHiddenLines)
3477 	{
3478 		removeHiddenLines();
3479 		drawModel(0.0f);
3480 	}
3481 	else
3482 	{
3483 		drawModel(eyeXOffset);
3484 	}
3485 	drawAxes(false);
3486 	if (stereoMode == LDVStereoCrossEyed || stereoMode == LDVStereoParallel)
3487 	{
3488 		eyeXOffset = -eyeXOffset;
3489 		GLint eyeWidth = (GLint)(scale(width) / 2);
3490 		glViewport(eyeWidth, 0, eyeWidth, (GLsizei)scale(height));
3491 		if (flags.slowClear)
3492 		{
3493 			clearBackground();
3494 			glViewport(eyeWidth, 0, eyeWidth, (GLsizei)scale(height));
3495 		}
3496 		if (flags.drawWireframe && flags.removeHiddenLines)
3497 		{
3498 			removeHiddenLines();
3499 			drawModel(0.0f);
3500 		}
3501 		else
3502 		{
3503 			drawModel(eyeXOffset);
3504 		}
3505 		drawAxes(false);
3506 		glViewport(0, 0, eyeWidth, (GLsizei)scale(height));
3507 	}
3508 	flags.updating = false;
3509 	if ((!fEq(rotationSpeed, 0.0f) ||
3510 		!fEq(zoomSpeed, 0.0f) ||
3511 		!fEq(cameraXRotate, 0.0f) ||
3512 		!fEq(cameraYRotate, 0.0f) ||
3513 		!fEq(cameraZRotate, 0.0f) ||
3514 		!fEq(cameraMotion.length(), 0.0f))
3515 		&& !getPaused())
3516 	{
3517 		requestRedraw();
3518 		flags.animating = true;
3519 	}
3520 	else
3521 	{
3522 		flags.animating = false;
3523 	}
3524 	TCAlertManager::sendAlert(frameDoneAlertClass(), this);
3525 	updateFrameTime(true);
3526 }
3527 
updateFrameTime(bool force)3528 void LDrawModelViewer::updateFrameTime(bool force /*=false*/)
3529 {
3530 	if (!flags.animating || force)
3531 	{
3532 #ifdef USE_STD_CHRONO
3533 		frameTime = std::chrono::steady_clock::now();
3534 #else
3535 #ifdef WIN32
3536 		if (hrpcFrequency.QuadPart != 0)
3537 		{
3538 			QueryPerformanceCounter(&hrpcFrameCount);
3539 		}
3540 		else
3541 		{
3542 			frameTicks = GetTickCount();
3543 		}
3544 #endif // WIN32
3545 #ifdef _QT
3546 		qtime.restart();
3547 #endif
3548 #ifdef COCOA
3549 		[FRAME_TIME release];
3550 		FRAME_TIME = [[NSDate alloc] init];
3551 #endif // COCOA
3552 #endif // !USE_STD_CHRONO
3553 	}
3554 }
3555 
removeHiddenLines(TCFloat eyeXOffset)3556 void LDrawModelViewer::removeHiddenLines(TCFloat eyeXOffset)
3557 {
3558 	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
3559 	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
3560 	mainTREModel->setLightingFlag(false);
3561 	mainTREModel->setRemovingHiddenLines(true);
3562 	if (flags.usePolygonOffset)
3563 	{
3564 		glPolygonOffset(POLYGON_OFFSET_FACTOR, POLYGON_OFFSET_UNITS);
3565 		enable(GL_POLYGON_OFFSET_FILL);
3566 	}
3567 	drawModel(eyeXOffset);
3568 	// Not sure why the following is necessary.
3569 	lineWidth(getScaledWireframeLineWidth());
3570 	if (flags.usePolygonOffset)
3571 	{
3572 		glPolygonOffset(0.0f, 0.0f);
3573 		enable(GL_POLYGON_OFFSET_FILL);
3574 	}
3575 	mainTREModel->setRemovingHiddenLines(false);
3576 	mainTREModel->setLightingFlag(flags.useLighting);
3577 	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
3578 	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
3579 }
3580 
setupRotationMatrix(void)3581 void LDrawModelViewer::setupRotationMatrix(void)
3582 {
3583 	glMatrixMode(GL_MODELVIEW);
3584 	glPushMatrix();
3585 	glLoadIdentity();
3586 	treGlRotatef(180.0f, 0.0f, 0.0f, 1.0f);
3587 	treGlRotatef(180.0f, 0.0f, 1.0f, 0.0f);
3588 	treGlMultMatrixf(rotationMatrix);
3589 	treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3590 	glPopMatrix();
3591 	flags.needsRotationMatrixSetup = false;
3592 /*
3593 	glMatrixMode(GL_MODELVIEW);
3594 	glPushMatrix();
3595 	glLoadIdentity();
3596 	treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3597 	treGlRotatef(180.0f, 0.0f, 0.0f, 1.0f);
3598 	treGlRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
3599 	treGlRotatef(50.0f, 0.0f, -1.5f, 1.0f);
3600 	treGlGetFloatv(GL_MODELVIEW_MATRIX, rotationMatrix);
3601 	glPopMatrix();
3602 */
3603 }
3604 
initLightDirModel(TREMainModel * & lightDirModel,TCULong color)3605 void LDrawModelViewer::initLightDirModel(
3606 	TREMainModel *&lightDirModel,
3607 	TCULong color)
3608 {
3609 	TREModel *subModel = new TREModel;
3610 	lightDirModel = new TREMainModel;
3611 	lightDirModel->setSendProgressFlag(false);
3612 	lightDirModel->setUseStripsFlag(getUseStrips());
3613 	TCFloat identityMatrix[16];
3614 	float length = size / 1.5f;
3615 	float radius = size / 100.0f;
3616 	int segments = 32;
3617 	float coneLength = radius * 4.0f;
3618 	float coneRadius = radius * 2.0f;
3619 	float offset = length / 3.0f;
3620 
3621 	lightDirModel->setLightingFlag(true);
3622 	TCVector::initIdentityMatrix(identityMatrix);
3623 	subModel->setMainModel(lightDirModel);
3624 	subModel->addCylinder(TCVector(0.0f, coneLength + offset, 0.0f), radius,
3625 		length - coneLength - offset, segments);
3626 	subModel->addDisc(TCVector(0.0, coneLength + offset, 0.0), coneRadius,
3627 		segments);
3628 	subModel->addDisc(TCVector(0.0, length, 0.0), radius, segments);
3629 	subModel->addCone(TCVector(0.0, coneLength + offset, 0.0), coneRadius,
3630 		-coneLength, segments);
3631 	lightDirModel->addSubModel(color, color, identityMatrix, subModel, false);
3632 	subModel->release();
3633 	lightDirModel->postProcess();
3634 }
3635 
initLightDirModels(void)3636 void LDrawModelViewer::initLightDirModels(void)
3637 {
3638 	if (size > 0.0 && !flags.noUI)
3639 	{
3640 		if (whiteLightDirModel == NULL)
3641 		{
3642 			initLightDirModel(whiteLightDirModel,
3643 				LDLPalette::colorForRGBA(255, 255, 255, 255));
3644 		}
3645 		if (blueLightDirModel == NULL)
3646 		{
3647 			initLightDirModel(blueLightDirModel,
3648 				LDLPalette::colorForRGBA(128, 128, 255, 255));
3649 		}
3650 	}
3651 }
3652 
getContrastingLightDirModel()3653 TREMainModel *LDrawModelViewer::getContrastingLightDirModel()
3654 {
3655 	if ((backgroundR + backgroundG + backgroundB) / 3.0f > 0.9f)
3656 	{
3657 		return blueLightDirModel;
3658 	}
3659 	else
3660 	{
3661 		return whiteLightDirModel;
3662 	}
3663 }
3664 
showLight(void)3665 void LDrawModelViewer::showLight(void)
3666 {
3667 	if (flags.showLight)
3668 	{
3669 		TCVector oldLightVector = lightVector;
3670 		TCFloat rotInverse[16];
3671 		LDLFacing facing;
3672 		bool oldSpecular = flags.usesSpecular;
3673 		bool oldSubdued = flags.subduedLighting;
3674 
3675 		TCVector::invertMatrix(rotationMatrix, rotInverse);
3676 		flags.usesSpecular = false;
3677 		flags.subduedLighting = false;
3678 		glPushMatrix();
3679 		glPushAttrib(GL_POLYGON_BIT | GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT |
3680 			GL_LINE_BIT | GL_LIGHTING_BIT);
3681 		facing.setFacing(TCVector(1.0f, 0.0f, 0.0f), (TCFloat)M_PI / 2.0f);
3682 		facing.pointAt(oldLightVector);
3683 		treGlTranslatef(center[0], center[1], center[2]);
3684 		treGlMultMatrixf(rotInverse);
3685 		treGlMultMatrixf(camera.getFacing().getMatrix());
3686 		lightVector = TCVector(0.0f, 0.0f, 1.0f);
3687 		setupLight(GL_LIGHT0);
3688 		glEnable(GL_LIGHTING);
3689 		glEnable(GL_LIGHT0);
3690 		glDisable(GL_LIGHT1);
3691 		glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
3692 		glPushMatrix();
3693 		glLoadIdentity();
3694 		drawLights();
3695 		glPopMatrix();
3696 		lightVector = oldLightVector;
3697 		treGlMultMatrixf(facing.getMatrix());
3698 		getContrastingLightDirModel()->draw();
3699 		glPopAttrib();
3700 		glPopMatrix();
3701 		flags.usesSpecular = oldSpecular;
3702 		flags.subduedLighting = oldSubdued;
3703 	}
3704 }
3705 
drawLightDats(void)3706 void LDrawModelViewer::drawLightDats(void)
3707 {
3708 	if (haveLightDats())
3709 	{
3710 		const TCVectorList &lightLocs = mainTREModel->getLightLocations();
3711 		GLint maxLights;
3712 
3713 		glGetIntegerv(GL_MAX_LIGHTS, &maxLights);
3714 		if (lightLocs.size() > 0)
3715 		{
3716 			int i;
3717 			TCVectorList::const_iterator itLoc = lightLocs.begin();
3718 			GLfloat position[4];
3719 			int start = 0;
3720 
3721 			if (!flags.optionalStandardLight)
3722 			{
3723 				start = 1;
3724 			}
3725 			// Point lights are distinguished from directional lights by the w
3726 			// component of their position vector.  Setting it to 0 yields a
3727 			// directional light.  Setting it to non-zero yields a point light.
3728 			position[3] = 1.0f;
3729 			for (i = start; i < maxLights && itLoc != lightLocs.end(); i++)
3730 			{
3731 				const TCVector &lightLoc = *itLoc;
3732 
3733 				position[0] = (GLfloat)lightLoc.get(0);
3734 				position[1] = (GLfloat)lightLoc.get(1);
3735 				position[2] = (GLfloat)lightLoc.get(2);
3736 				glLightfv(GL_LIGHT0 + i, GL_POSITION, position);
3737 				itLoc++;
3738 			}
3739 		}
3740 	}
3741 }
3742 
drawAxes(bool atOrigin)3743 void LDrawModelViewer::drawAxes(bool atOrigin)
3744 {
3745 	if (flags.showAxes && flags.axesAtOrigin == atOrigin)
3746 	{
3747 		if (flags.axesAtOrigin)
3748 		{
3749 			TCFloat margin = size / 8.0f;
3750 			TCVector minPoint(boundingMin);
3751 			TCVector maxPoint(boundingMax);
3752 			//TCVector margin = (boundingMax - boundingMin) / 8.0f;
3753 			int i;
3754 
3755 			for (i = 0; i < 3; i++)
3756 			{
3757 				if (boundingMin[i] > 0.0f)
3758 				{
3759 					minPoint[i] = -margin;
3760 				}
3761 				else
3762 				{
3763 					minPoint[i] = boundingMin[i] - margin;
3764 				}
3765 				if (boundingMax[i] < 0.0f)
3766 				{
3767 					maxPoint[i] = margin;
3768 				}
3769 				else
3770 				{
3771 					maxPoint[i] = boundingMax[i] + margin;
3772 				}
3773 			}
3774 			glPushAttrib(GL_LIGHTING_BIT | GL_LINE_BIT);
3775 			lineWidth(scale(2.0f));
3776 			glDisable(GL_LIGHTING);
3777 			glBegin(GL_LINES);
3778 				glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
3779 				glVertex3f(0.0f, 0.0f, 0.0f);
3780 				glVertex3f(maxPoint[0], 0.0f, 0.0f);
3781 				glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
3782 				glVertex3f(0.0f, 0.0f, 0.0f);
3783 				glVertex3f(0.0f, maxPoint[1], 0.0f);
3784 				glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
3785 				glVertex3f(0.0f, 0.0f, 0.0f);
3786 				glVertex3f(0.0f, 0.0f, maxPoint[2]);
3787 
3788 				glColor4f(0.5f, 0.0f, 0.0f, 1.0f);
3789 				glVertex3f(0.0f, 0.0f, 0.0f);
3790 				glVertex3f(minPoint[0], 0.0f, 0.0f);
3791 				glColor4f(0.0f, 0.5f, 0.0f, 1.0f);
3792 				glVertex3f(0.0f, 0.0f, 0.0f);
3793 				glVertex3f(0.0f, minPoint[1], 0.0f);
3794 				glColor4f(0.0f, 0.0f, 0.5f, 1.0f);
3795 				glVertex3f(0.0f, 0.0f, 0.0f);
3796 				glVertex3f(0.0f, 0.0f, minPoint[2]);
3797 				glEnd();
3798 			glPopAttrib();
3799 		}
3800 		else
3801 		{
3802 			int actualWidth = (int)scale(width);
3803 
3804 			if (stereoMode == LDVStereoCrossEyed || stereoMode == LDVStereoParallel)
3805 			{
3806 				actualWidth = (int)(scale(width) / 2);
3807 			}
3808 			glPushAttrib(GL_LIGHTING_BIT | GL_VIEWPORT_BIT);
3809 			glMatrixMode(GL_PROJECTION);
3810 			glLoadIdentity();
3811 			glOrtho(0.0f, actualWidth, 0.0f, (int)scale(height), -25.0f, 25.0f);
3812 			glMatrixMode(GL_MODELVIEW);
3813 			glLoadIdentity();
3814 			glTranslatef(30.0f, 30.0f, 0.0f);
3815 			if (rotationMatrix)
3816 			{
3817 				treGlMultMatrixf(rotationMatrix);
3818 			}
3819 			glDisable(GL_LIGHTING);
3820 			glBegin(GL_LINES);
3821 				glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
3822 				glVertex3f(0.0f, 0.0f, 0.0f);
3823 				glVertex3f(25.0f, 0.0f, 0.0f);
3824 				glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
3825 				glVertex3f(0.0f, 0.0f, 0.0f);
3826 				glVertex3f(0.0f, 25.0f, 0.0f);
3827 				glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
3828 				glVertex3f(0.0f, 0.0f, 0.0f);
3829 				glVertex3f(0.0f, 0.0f, 25.0f);
3830 			glEnd();
3831 			glPopAttrib();
3832 		}
3833 	}
3834 }
3835 
drawModel(TCFloat eyeXOffset)3836 void LDrawModelViewer::drawModel(TCFloat eyeXOffset)
3837 {
3838 	drawSetup(eyeXOffset);
3839 	if (rotationMatrix)
3840 	{
3841 		treGlMultMatrixf(rotationMatrix);
3842 	}
3843 	if (flags.autoCenter)
3844 	{
3845 		treGlTranslatef(-center[0], -center[1], -center[2]);
3846 	}
3847 	drawLightDats();
3848 //	drawBoundingBox();
3849 	glColor3ub(192, 192, 192);
3850 	if (mainTREModel)
3851 	{
3852 		showLight();
3853 		innerDrawModel();
3854 		if (clipAmount > 0.01)
3855 		{
3856 			drawToClipPlane(eyeXOffset);
3857 		}
3858 	}
3859 }
3860 
pause(void)3861 void LDrawModelViewer::pause(void)
3862 {
3863 	flags.paused = true;
3864 }
3865 
unpause(void)3866 void LDrawModelViewer::unpause(void)
3867 {
3868 	flags.paused = false;
3869 }
3870 
setRotationSpeed(TCFloat value)3871 void LDrawModelViewer::setRotationSpeed(TCFloat value)
3872 {
3873 	rotationSpeed = value;
3874 	if (value)
3875 	{
3876 		flags.paused = false;
3877 	}
3878 }
3879 
setZoomSpeed(TCFloat value)3880 void LDrawModelViewer::setZoomSpeed(TCFloat value)
3881 {
3882 	zoomSpeed = value;
3883 	if (value)
3884 	{
3885 		flags.paused = false;
3886 	}
3887 }
3888 
setExtraSearchDirs(TCStringArray * value)3889 void LDrawModelViewer::setExtraSearchDirs(TCStringArray *value)
3890 {
3891 	if (extraSearchDirs != value)
3892 	{
3893 		TCObject::release(extraSearchDirs);
3894 		extraSearchDirs = value;
3895 		TCObject::retain(extraSearchDirs);
3896 	}
3897 	// Since it is a string array, the contents might have changed, even if
3898 	// the array pointer itself didn't.
3899 	flags.needsReload = true;
3900 }
3901 
panXY(int xValue,int yValue)3902 void LDrawModelViewer::panXY(int xValue, int yValue)
3903 {
3904 	TCFloat adjustment;
3905 	TCFloat distance = (camera.getPosition()).length();
3906 //	TCFloat distance = (camera.getPosition() - center).length();
3907 
3908 	if (width > height)
3909 	{
3910 		adjustment = scale(width);
3911 	}
3912 	else
3913 	{
3914 		adjustment = scale(height);
3915 	}
3916 //	xPan += xValue / (TCFloat)pow(distance, 0.001);
3917 //	yPan -= yValue / (TCFloat)pow(distance, 0.001);
3918 	xPan += xValue / adjustment / (TCFloat)pow(2.0 / distance, 1.1) *
3919 		(TCFloat)(sin(deg2rad(fov)) / sin(deg2rad(45.0)));
3920 	yPan -= yValue / adjustment / (TCFloat)pow(2.0 / distance, 1.1) *
3921 		(TCFloat)(sin(deg2rad(fov)) / sin(deg2rad(45.0)));
3922 }
3923 
setXYPan(TCFloat xValue,TCFloat yValue)3924 void LDrawModelViewer::setXYPan(TCFloat xValue, TCFloat yValue)
3925 {
3926 	xPan = xValue;
3927 	yPan = yValue;
3928 }
3929 
openGlWillEnd(void)3930 void LDrawModelViewer::openGlWillEnd(void)
3931 {
3932 	if (mainTREModel)
3933 	{
3934 		mainTREModel->openGlWillEnd();
3935 	}
3936 }
3937 
getCompiled(void) const3938 bool LDrawModelViewer::getCompiled(void) const
3939 {
3940 	if (mainTREModel)
3941 	{
3942 		return mainTREModel->getCompiled();
3943 	}
3944 	else
3945 	{
3946 		return false;
3947 	}
3948 }
3949 
connectionFailure(TCWebClient * webClient)3950 bool LDrawModelViewer::connectionFailure(TCWebClient *webClient)
3951 {
3952 	// I'm not sure if we should add more errors here or not.  Hopefully this is
3953 	// enough.  In reality, the hostname lookup will fail if the user doesn't
3954 	// have an internet connection.  If they have a mis-configured proxy, they
3955 	// should either get TCNCE_CONNECT or TCNCE_CONNECTION_REFUSED.
3956 	switch (webClient->getErrorNumber())
3957 	{
3958 	case TCNCE_HOSTNAME_LOOKUP:
3959 	case TCNCE_NO_PORT:
3960 	case TCNCE_CONNECT:
3961 	case TCNCE_CONNECTION_REFUSED:
3962 		return true;
3963 	default:
3964 		return false;
3965 	}
3966 }
3967 
findFileAlertCallback(LDLFindFileAlert * alert)3968 void LDrawModelViewer::findFileAlertCallback(LDLFindFileAlert *alert)
3969 {
3970 	char *lfilename = copyString(alert->getFilename());
3971 	size_t len = strlen(lfilename);
3972 	char *url;
3973 	char *partOutputFilename = copyString(LDLModel::lDrawDir(), len + 32);
3974 	char *primitiveOutputFilename = copyString(LDLModel::lDrawDir(), len + 32);
3975 	bool primitive = false;
3976 	bool part = false;
3977 	//const char *partUrlBase = "http://media.peeron.com/tmp/";
3978 	const char *partUrlBase = "http://www.ldraw.org/library/unofficial/parts/";
3979 	const char *primitiveUrlBase = "http://www.ldraw.org/library/unofficial/p/";
3980 	bool found = false;
3981 	char *key = new char[strlen(lfilename) + 128];
3982 
3983 	replaceStringCharacter(partOutputFilename, '\\', '/');
3984 	replaceStringCharacter(primitiveOutputFilename, '\\', '/');
3985 	strcat(partOutputFilename, "/Unofficial/parts/");
3986 	strcat(primitiveOutputFilename, "/Unofficial/p/");
3987 	convertStringToLower(lfilename);
3988 	replaceStringCharacter(lfilename, '\\', '/');
3989 	if (stringHasPrefix(lfilename, "48/"))
3990 	{
3991 		primitive = true;
3992 		url = copyString(primitiveUrlBase, len + 2);
3993 	}
3994 	else
3995 	{
3996 		if (stringHasPrefix(lfilename, "s/"))
3997 		{
3998 			// The only thing this is used for is to prevent it from checking
3999 			// for the file as a primitive if it's not found as a part.
4000 			part = true;
4001 		}
4002 		url = copyString(partUrlBase, len + 2);
4003 	}
4004 	strcat(partOutputFilename, lfilename);
4005 	strcat(primitiveOutputFilename, lfilename);
4006 	if (fileExists(partOutputFilename))
4007 	{
4008 		primitive = false;
4009 		found = true;
4010 		alert->setPartFlag(true);
4011 	}
4012 	else if (!part && fileExists(primitiveOutputFilename))
4013 	{
4014 		primitive = true;
4015 		found = true;
4016 		delete[] url;
4017 		url = copyString(primitiveUrlBase, len + 2);
4018 	}
4019 	if (canCheckForUnofficialPart(lfilename, found))
4020 	{
4021 		TCWebClient *webClient;
4022 		// FIX: dynamically allocate and use local string AND handle abort
4023 		UCCHAR message[1024];
4024 		bool abort;
4025 		UCSTR ucFilename = mbstoucstring(lfilename);
4026 
4027 		sprintf(key, "UnofficialPartChecks/%s/LastModified", lfilename);
4028 		if (found)
4029 		{
4030 			sucprintf(message, COUNT_OF(message), ls(_UC("CheckingForUpdates")),
4031 				ucFilename);
4032 		}
4033 		else
4034 		{
4035 			sucprintf(message, COUNT_OF(message), ls(_UC("TryingToDownload")),
4036 				ucFilename);
4037 		}
4038 		delete[] ucFilename;
4039 		TCProgressAlert::send("LDrawModelViewer", message, -1.0f, &abort, this);
4040 		strcat(url, lfilename);
4041 		webClient = new TCWebClient(url);
4042 		if (found)
4043 		{
4044 			char *lastModified = TCUserDefaults::stringForKey(key, NULL, false);
4045 
4046 			if (lastModified)
4047 			{
4048 				webClient->setLastModifiedString(lastModified);
4049 				delete[] lastModified;
4050 			}
4051 		}
4052 		if (primitive)
4053 		{
4054 			*strrchr(primitiveOutputFilename, '/') = 0;
4055 			webClient->setOutputDirectory(primitiveOutputFilename);
4056 			primitiveOutputFilename[strlen(primitiveOutputFilename)] = '/';
4057 		}
4058 		else
4059 		{
4060 			*strrchr(partOutputFilename, '/') = 0;
4061 			webClient->setOutputDirectory(partOutputFilename);
4062 			partOutputFilename[strlen(partOutputFilename)] = '/';
4063 		}
4064 		if (webClient->fetchURL() ||
4065 			webClient->getErrorNumber() == WCE_NOT_MODIFIED)
4066 		{
4067 			found = true;
4068 			if (!primitive)
4069 			{
4070 				alert->setPartFlag(true);
4071 			}
4072 		}
4073 		else if (connectionFailure(webClient))
4074 		{
4075 			// If we had a connection failure, we probably don't have an
4076 			// internet connection, or our proxy is mis-configured.  Don't try
4077 			// to connect again for now, and let the user know that auto part
4078 			// updates have been disabled.
4079 
4080 			preferences->setCheckPartTracker(false, true);
4081 			flags.checkPartTracker = false;
4082 			TCAlertManager::sendAlert(alertClass(), this,
4083 				ls(_UC("PartCheckDisabled")));
4084 		}
4085 		else
4086 		{
4087 			if (!primitive && !part)
4088 			{
4089 				// We don't know if it's a primitive or a part.  The part
4090 				// download failed, so try as a primitive.
4091 				delete[] url;
4092 				url = copyString(primitiveUrlBase, len + 2);
4093 				strcat(url, lfilename);
4094 				webClient->release();
4095 				webClient = new TCWebClient(url);
4096 				*strrchr(primitiveOutputFilename, '/') = 0;
4097 				webClient->setOutputDirectory(primitiveOutputFilename);
4098 				primitiveOutputFilename[strlen(primitiveOutputFilename)] = '/';
4099 				if (webClient->fetchURL() ||
4100 					webClient->getErrorNumber() == WCE_NOT_MODIFIED)
4101 				{
4102 					primitive = true;
4103 					found = true;
4104 				}
4105 			}
4106 		}
4107 		if (webClient->getLastModifiedString())
4108 		{
4109 			TCUserDefaults::setStringForKey(
4110 				webClient->getLastModifiedString(), key, false);
4111 		}
4112 		webClient->release();
4113 		sprintf(key, "UnofficialPartChecks/%s/LastUpdateCheckTime",
4114 			lfilename);
4115 		TCUserDefaults::setLongForKey((long)time(NULL), key, false);
4116 		if (!found)
4117 		{
4118 			unofficialPartNotFound(lfilename);
4119 		}
4120 	}
4121 	if (found)
4122 	{
4123 		alert->setFileFound(true);
4124 		if (primitive)
4125 		{
4126 			alert->setFilename(primitiveOutputFilename);
4127 		}
4128 		else
4129 		{
4130 			alert->setFilename(partOutputFilename);
4131 		}
4132 		setUnofficialPartPrimitive(lfilename, primitive);
4133 	}
4134 	delete[] key;
4135 	delete[] lfilename;
4136 	delete[] url;
4137 	delete[] partOutputFilename;
4138 	delete[] primitiveOutputFilename;
4139 }
4140 
getPartsList(void)4141 LDPartsList *LDrawModelViewer::getPartsList(void)
4142 {
4143 	if (mainModel)
4144 	{
4145 		LDPartsList *partsList = new LDPartsList;
4146 
4147 		partsList->scanModel(getCurModel(), defaultColorNumber);
4148 		return partsList;
4149 	}
4150 	else
4151 	{
4152 		return NULL;
4153 	}
4154 }
4155 
4156 // NOTE: static function
fileExists(const char * filename)4157 bool LDrawModelViewer::fileExists(const char* filename)
4158 {
4159 	FILE* file = ucfopen(filename, "r");
4160 
4161 	if (file)
4162 	{
4163 		fclose(file);
4164 		return true;
4165 	}
4166 	else
4167 	{
4168 		return false;
4169 	}
4170 }
4171 
4172 // NOTE: static function
setUnofficialPartPrimitive(const char * filename,bool primitive)4173 void LDrawModelViewer::setUnofficialPartPrimitive(const char *filename,
4174 												  bool primitive)
4175 {
4176 	char *key = new char[strlen(filename) + 128];
4177 
4178 	sprintf(key, "UnofficialPartChecks/%s/Primitive", filename);
4179 	TCUserDefaults::setLongForKey(primitive ? 1 : 0, key, false);
4180 	delete[] key;
4181 }
4182 
saveViewPoint(void) const4183 LDViewPoint *LDrawModelViewer::saveViewPoint(void) const
4184 {
4185 	LDViewPoint *viewPoint = new LDViewPoint;
4186 
4187 	viewPoint->setCamera(camera);
4188 	viewPoint->setRotation(TCVector(xRotate, yRotate, zRotate));
4189 	viewPoint->setCameraRotation(TCVector(cameraXRotate, cameraYRotate,
4190 		cameraZRotate));
4191 	viewPoint->setPan(TCVector(xPan, yPan, 0.0f));
4192 	viewPoint->setRotationMatrix(rotationMatrix);
4193 	viewPoint->setRotationSpeed(rotationSpeed);
4194 	viewPoint->setAutoCenter(flags.autoCenter != false);
4195 	viewPoint->setBackgroundColor(backgroundR, backgroundG, backgroundB,
4196 		backgroundA);
4197 	viewPoint->setStereoMode(stereoMode);
4198 	viewPoint->setClipAmount(clipAmount);
4199 	viewPoint->setDefaultDistance(defaultDistance);
4200 	return viewPoint;
4201 }
4202 
rightSideUp(bool shouldRequestRedraw)4203 void LDrawModelViewer::rightSideUp(bool shouldRequestRedraw /*= true*/)
4204 {
4205 	if (!flags.needsSetup)
4206 	{
4207 		TCVector upVector(0.0, -1.0, 0.0);
4208 		TCFloat matrix[16];
4209 		TCFloat inverseMatrix[16];
4210 		TCVector::invertMatrix(camera.getFacing().getMatrix(), inverseMatrix);
4211 		TCVector::multMatrix(inverseMatrix, rotationMatrix, matrix);
4212 		TCVector tempVector = upVector.transformNormal(matrix);
4213 		float lzRotate = 0.0;
4214 
4215 		if (tempVector[0] == 0.0 && tempVector[1] == 0.0)
4216 		{
4217 			lzRotate = 0;
4218 		}
4219 		else
4220 		{
4221 			lzRotate = atan2(tempVector[0], tempVector[1]);
4222 		}
4223 		camera.rotate(TCVector(0.0f, 0.0f, lzRotate));
4224 		if (shouldRequestRedraw)
4225 		{
4226 			requestRedraw();
4227 		}
4228 	}
4229 }
4230 
restoreViewPoint(const LDViewPoint * viewPoint)4231 void LDrawModelViewer::restoreViewPoint(const LDViewPoint *viewPoint)
4232 {
4233 	TCVector tempVector;
4234 
4235 	camera = viewPoint->getCamera();
4236 	tempVector = viewPoint->getRotation();
4237 	xRotate = tempVector[0];
4238 	yRotate = tempVector[1];
4239 	zRotate = tempVector[2];
4240 	tempVector = viewPoint->getCameraRotation();
4241 	cameraXRotate = tempVector[0];
4242 	cameraYRotate = tempVector[1];
4243 	cameraZRotate = tempVector[2];
4244 	tempVector = viewPoint->getPan();
4245 	xPan = tempVector[0];
4246 	yPan = tempVector[1];
4247 	memcpy(rotationMatrix, viewPoint->getRotationMatrix(),
4248 		16 * sizeof(rotationMatrix[0]));
4249 	rotationSpeed = viewPoint->getRotationSpeed();
4250 	flags.autoCenter = viewPoint->getAutoCenter();
4251 	viewPoint->getBackgroundColor(backgroundR, backgroundG, backgroundB,
4252 		backgroundA);
4253 	stereoMode = viewPoint->getStereoMode();
4254 	clipAmount = viewPoint->getClipAmount();
4255 	defaultDistance = viewPoint->getDefaultDistance();
4256 }
4257 
canCheckForUnofficialPart(const char * lfilename,bool exists)4258 bool LDrawModelViewer::canCheckForUnofficialPart(const char *lfilename,
4259 												 bool exists)
4260 {
4261 	bool retValue = false;
4262 
4263 	if (flags.checkPartTracker)
4264 	{
4265 		char *key = new char[strlen(lfilename) + 128];
4266 		time_t lastCheck;
4267 		time_t now = time(NULL);
4268 		int days;
4269 
4270 		if (exists)
4271 		{
4272 			sprintf(key, "UnofficialPartChecks/%s/LastUpdateCheckTime",
4273 				lfilename);
4274 			days = updatedPartWait;
4275 		}
4276 		else
4277 		{
4278 			sprintf(key, "UnofficialPartChecks/%s/LastCheckTime", lfilename);
4279 			days = missingPartWait;
4280 		}
4281 		lastCheck = (time_t)TCUserDefaults::longForKey(key, 0, false);
4282 		if (days < 1)
4283 		{
4284 			days = 1;
4285 		}
4286 		if (now - lastCheck > 24 * 3600 * days)
4287 		{
4288 			retValue = true;
4289 		}
4290 		delete[] key;
4291 	}
4292 	return retValue;
4293 }
4294 
unofficialPartNotFound(const char * lfilename)4295 void LDrawModelViewer::unofficialPartNotFound(const char *lfilename)
4296 {
4297 	if (flags.checkPartTracker)
4298 	{
4299 		char *key = new char[strlen(lfilename) + 128];
4300 		time_t now = time(NULL);
4301 
4302 		sprintf(key, "UnofficialPartChecks/%s/LastCheckTime", lfilename);
4303 		TCUserDefaults::setLongForKey((long)now, key, false);
4304 		delete[] key;
4305 	}
4306 }
4307 
4308 // NOTE: static function
cleanupFloats(TCFloat * array,int count)4309 void LDrawModelViewer::cleanupFloats(TCFloat *array, int count)
4310 {
4311 	int i;
4312 
4313 	for (i = 0; i < count; i++)
4314 	{
4315 		if (fabs(array[i]) < 1e-6)
4316 		{
4317 			array[i] = 0.0f;
4318 		}
4319 	}
4320 }
4321 
4322 // Note: static method
checkAspect(TCFloat width,TCFloat height,int aspectW,int aspectH)4323 bool LDrawModelViewer::checkAspect(
4324 	TCFloat width,
4325 	TCFloat height,
4326 	int aspectW,
4327 	int aspectH)
4328 {
4329 	return fabs(width * aspectH / aspectW - height) < 1e-5;
4330 }
4331 
4332 // Note: static method
getAspectString(TCFloat width,TCFloat height,CUCSTR separator,bool standardOnly)4333 ucstring LDrawModelViewer::getAspectString(
4334 	TCFloat width,
4335 	TCFloat height,
4336 	CUCSTR separator,
4337 	bool standardOnly /*= false*/)
4338 {
4339 	ucstring aspect;
4340 	ucstring denom;
4341 
4342 	if (checkAspect(width, height, 235, 100))
4343 	{
4344 		aspect = _UC("2.35");
4345 		denom = _UC("1");
4346 	}
4347 	else if (checkAspect(width, height, 1920, 817) ||
4348 		checkAspect(width, height, 1280, 544))
4349 	{
4350 		aspect = _UC("~2.35");
4351 		denom = _UC("1");
4352 	}
4353 	else if (checkAspect(width, height, 16, 9))
4354 	{
4355 		aspect = _UC("16");
4356 		denom = _UC("9");
4357 	}
4358 	else if (checkAspect(width, height, 1136, 640) ||
4359 		checkAspect(width, height, 1334, 750))
4360 	{
4361 		aspect = _UC("~16");
4362 		denom = _UC("9");
4363 	}
4364 	else if (checkAspect(width, height, 5, 3))
4365 	{
4366 		aspect = _UC("5");
4367 		denom = _UC("3");
4368 	}
4369 	else if (checkAspect(width, height, 16, 10))
4370 	{
4371 		aspect = _UC("16");
4372 		denom = _UC("10");
4373 	}
4374 	else if (checkAspect(width, height, 3, 2))
4375 	{
4376 		aspect = _UC("3");
4377 		denom = _UC("2");
4378 	}
4379 	else if (checkAspect(width, height, 4, 3))
4380 	{
4381 		aspect = _UC("4");
4382 		denom = _UC("3");
4383 	}
4384 	else if (checkAspect(width, height, 5, 4))
4385 	{
4386 		aspect = _UC("5");
4387 		denom = _UC("4");
4388 	}
4389 	else if (checkAspect(width, height, 21, 9))
4390 	{
4391 		aspect = _UC("21");
4392 		denom = _UC("9");
4393 	}
4394 	else if (checkAspect(width, height, 64, 27))
4395 	{
4396 		aspect = _UC("~21");
4397 		denom = _UC("9");
4398 	}
4399 	else if (checkAspect(width, height, 256, 135))
4400 	{
4401 		aspect = _UC("~1.9");
4402 		denom = _UC("1");
4403 	}
4404 	else if (standardOnly)
4405 	{
4406 		return _UC("");
4407 	}
4408 	else
4409 	{
4410 		aspect = ftoucstr(width / height);
4411 	}
4412 	if (denom.length() > 0)
4413 	{
4414 		aspect += separator;
4415 		aspect += denom;
4416 	}
4417 	return aspect;
4418 }
4419 
getPovCameraInfo(UCCHAR * & userMessage,char * & povCamera)4420 void LDrawModelViewer::getPovCameraInfo(UCCHAR *&userMessage, char *&povCamera)
4421 {
4422 	TCFloat tmpMatrix[16];
4423 	TCFloat matrix[16];
4424 	TCFloat lrotationMatrix[16];
4425 	TCFloat centerMatrix[16];
4426 	TCFloat positionMatrix[16];
4427 	TCFloat cameraMatrix[16];
4428 	TCFloat otherMatrix[16] = {1,0,0,0,0,-1,0,0,0,0,-1,0,0,0,0,1};
4429 	UCCHAR locationString[1024];
4430 	UCCHAR lookAtString[1204];
4431 	UCCHAR upString[1024];
4432 	UCCHAR message[4096];
4433 	TCVector directionVector = TCVector(0.0f, 0.0f, 1.0f);
4434 	TCVector locationVector;
4435 	TCVector lookAtVector;
4436 	TCVector upVector = TCVector(0.0f, -1.0f, 0.0f);
4437 	double direction[3];
4438 	double up[3];
4439 	double location[3];
4440 	LDLFacing facing;
4441 	UCCHAR cameraString[4096];
4442 	double lookAt[3];
4443 	double tempV[3];
4444 	ucstring povAspect;
4445 
4446 	if (!mainTREModel)
4447 	{
4448 		userMessage = NULL;
4449 		povCamera = NULL;
4450 		return;
4451 	}
4452 	TCVector cameraPosition = camera.getPosition();
4453 
4454 	memcpy(lrotationMatrix, getRotationMatrix(), sizeof(lrotationMatrix));
4455 	TCVector::initIdentityMatrix(positionMatrix);
4456 	positionMatrix[12] = cameraPosition[0] - getXPan();
4457 	positionMatrix[13] = -cameraPosition[1] + getYPan();
4458 	positionMatrix[14] = -cameraPosition[2];
4459 	TCVector::initIdentityMatrix(centerMatrix);
4460 	if (getAutoCenter())
4461 	{
4462 		centerMatrix[12] = center[0];
4463 		centerMatrix[13] = center[1];
4464 		centerMatrix[14] = center[2];
4465 	}
4466 	TCVector::multMatrix(otherMatrix, lrotationMatrix, tmpMatrix);
4467 	TCVector::invertMatrix(tmpMatrix, cameraMatrix);
4468 	TCVector::multMatrix(centerMatrix, cameraMatrix, tmpMatrix);
4469 	TCVector::multMatrix(tmpMatrix, positionMatrix, matrix);
4470 
4471 	facing = camera.getFacing();
4472 	facing[0] = -facing[0];
4473 	facing.getInverseMatrix(cameraMatrix);
4474 	TCVector::multMatrix(matrix, cameraMatrix, tmpMatrix);
4475 	memcpy(matrix, tmpMatrix, sizeof(matrix));
4476 	cleanupFloats(matrix);
4477 	locationVector = TCVector(matrix[12], matrix[13], matrix[14]);
4478 	location[0] = (double)matrix[12];
4479 	location[1] = (double)matrix[13];
4480 	location[2] = (double)matrix[14];
4481 	cleanupFloats(matrix);
4482 	// Note that the location accuracy isn't nearly as important as the
4483 	// directional accuracy, so we don't have to re-do this string prior
4484 	// to putting it on the clipboard in the POV code copy.
4485 	sucprintf(locationString, COUNT_OF(locationString), _UC("%s,%s,%s"),
4486 		ftoucstr(location[0]).c_str(), ftoucstr(location[1]).c_str(),
4487 		ftoucstr(location[2]).c_str());
4488 
4489 	matrix[12] = matrix[13] = matrix[14] = 0.0f;
4490 	directionVector = directionVector.transformPoint(matrix);
4491 	upVector = upVector.transformPoint(matrix);
4492 	// Grab the values prior to normalization.  That will make the
4493 	// normalization more accurate in double precision.
4494 	directionVector.upConvert(direction);
4495 	lookAtVector = locationVector + directionVector *
4496 		locationVector.length();
4497 	upVector.upConvert(up);
4498 	directionVector = directionVector.normalize();
4499 	upVector = upVector.normalize();
4500 	cleanupFloats(directionVector, 3);
4501 	cleanupFloats(upVector, 3);
4502 	// The following 3 strings will get re-done later at higher accuracy
4503 	// for POV-Ray.
4504 	sucprintf(lookAtString, COUNT_OF(lookAtString), _UC("%s"),
4505 		lookAtVector.ucstring().c_str());
4506 	sucprintf(upString, COUNT_OF(upString), _UC("%s"),
4507 		upVector.ucstring().c_str());
4508 	sucprintf(message, COUNT_OF(message), ls(_UC("PovCameraMessage")),
4509 		locationString, lookAtString, upString);
4510 	TCVector::doubleNormalize(up);
4511 	TCVector::doubleNormalize(direction);
4512 	TCVector::doubleMultiply(direction, tempV,
4513 		TCVector::doubleLength(location));
4514 	TCVector::doubleAdd(location, tempV, lookAt);
4515 	// Re-do the strings with higher accuracy, so they'll be
4516 	// accepted by POV-Ray.
4517 	sucprintf(upString, COUNT_OF(upString), _UC("%s,%s,%s"),
4518 		ftoucstr(up[0], 20).c_str(), ftoucstr(up[1], 20).c_str(),
4519 		ftoucstr(up[2], 20).c_str());
4520 	sucprintf(lookAtString, COUNT_OF(lookAtString), _UC("%s,%s,%s"),
4521 		ftoucstr(lookAt[0], 20).c_str(), ftoucstr(lookAt[1], 20).c_str(),
4522 		ftoucstr(lookAt[2], 20).c_str());
4523 	if (flags.povCameraAspect)
4524 	{
4525 		povAspect = getAspectString(width, height, _UC("/"));
4526 	}
4527 	else
4528 	{
4529 		povAspect = _UC("4/3");
4530 	}
4531 	sucprintf(cameraString, COUNT_OF(cameraString),
4532 		_UC("camera\n")
4533 		_UC("{\n")
4534 		_UC("\t#declare ASPECT = %s;\n")
4535 		_UC("\tlocation < %s >\n")
4536 		_UC("\tsky < %s >\n")
4537 		_UC("\tright ASPECT * < -1,0,0 >\n")
4538 		_UC("\tlook_at < %s >\n")
4539 		_UC("\tangle %g\n")
4540 		_UC("}\n"),
4541 		povAspect.c_str(), locationString, upString, lookAtString, getHFov());
4542 	userMessage = copyString(message);
4543 	povCamera = ucstringtombs(cameraString);
4544 }
4545 
mouseDown(LDVMouseMode mode,int x,int y)4546 bool LDrawModelViewer::mouseDown(LDVMouseMode mode, int x, int y)
4547 {
4548 	if ((mouseMode != LDVMouseNone && mouseMode != mode) ||
4549 		mode == LDVMouseNone)
4550 	{
4551 		return false;
4552 	}
4553 	if (mode != LDVMouseLight)
4554 	{
4555 		return false;
4556 	}
4557 	if (mode == LDVMouseLight && !flags.useLighting)
4558 	{
4559 		// Allowing this will likely just lead to confusion.
4560 		return false;
4561 	}
4562 	lastMouseX = x;
4563 	lastMouseY = y;
4564 	mouseMode = mode;
4565 	switch (mouseMode)
4566 	{
4567 	case LDVMouseLight:
4568 		if (haveStandardLight())
4569 		{
4570 			flags.showLight = true;
4571 			requestRedraw();
4572 		}
4573 		break;
4574 	default:
4575 		break;
4576 	}
4577 	return true;
4578 }
4579 
setShowLightDir(bool value)4580 void LDrawModelViewer::setShowLightDir(bool value)
4581 {
4582 	if (value != flags.showLight)
4583 	{
4584 		flags.showLight = value;
4585 		if (!value && preferences != NULL)
4586 		{
4587 			preferences->setLightVector(lightVector, true);
4588 		}
4589 	}
4590 }
4591 
mouseUp(int x,int y)4592 bool LDrawModelViewer::mouseUp(int x, int y)
4593 {
4594 	int deltaX = x - lastMouseX;
4595 	int deltaY = y - lastMouseY;
4596 
4597 	if (mouseMode != LDVMouseLight)
4598 	{
4599 		debugPrintf("LDVMouseLight is the only mode currently supported.\n");
4600 		return false;
4601 	}
4602 	lastMouseX = x;
4603 	lastMouseY = y;
4604 	switch (mouseMode)
4605 	{
4606 	case LDVMouseLight:
4607 		if (haveStandardLight())
4608 		{
4609 			mouseMoveLight(deltaX, deltaY);
4610 			preferences->setLightVector(lightVector, true);
4611 			flags.showLight = false;
4612 			requestRedraw();
4613 		}
4614 		break;
4615 	default:
4616 		break;
4617 	}
4618 	mouseMode = LDVMouseNone;
4619 	return true;
4620 }
4621 
mouseMove(int x,int y)4622 bool LDrawModelViewer::mouseMove(int x, int y)
4623 {
4624 	int deltaX = x - lastMouseX;
4625 	int deltaY = y - lastMouseY;
4626 
4627 	if (mouseMode != LDVMouseLight)
4628 	{
4629 		//debugPrintf("LDVMouseLight is the only mode currently supported.\n");
4630 		return false;
4631 	}
4632 	lastMouseX = x;
4633 	lastMouseY = y;
4634 	switch (mouseMode)
4635 	{
4636 	case LDVMouseLight:
4637 		mouseMoveLight(deltaX, deltaY);
4638 		requestRedraw();
4639 		break;
4640 	default:
4641 		break;
4642 	}
4643 	return true;
4644 }
4645 
mouseMoveLight(int deltaX,int deltaY)4646 void LDrawModelViewer::mouseMoveLight(int deltaX, int deltaY)
4647 {
4648 	TCFloat matrix[16];
4649 	double lightScale = std::min(scale(width), scale(height)) / 10.0;
4650 	double angle = deltaX / lightScale;
4651 	TCVector newLightVector;
4652 
4653 	TCVector::initIdentityMatrix(matrix);
4654 	matrix[0] = (float)cos(angle);
4655 	matrix[2] = (float)-sin(angle);
4656 	matrix[8] = (float)sin(angle);
4657 	matrix[10] = (float)cos(angle);
4658 	newLightVector = lightVector.transformPoint(matrix);
4659 	angle = deltaY / lightScale;
4660 	TCVector::initIdentityMatrix(matrix);
4661 	matrix[5] = (float)cos(angle);
4662 	matrix[6] = (float)sin(angle);
4663 	matrix[9] = (float)-sin(angle);
4664 	matrix[10] = (float)cos(angle);
4665 	setLightVector(newLightVector.transformPoint(matrix));
4666 }
4667 
getOpenGLDriverInfo(int & numExtensions)4668 UCSTR LDrawModelViewer::getOpenGLDriverInfo(int &numExtensions)
4669 {
4670 	UCSTR vendorString = mbstoucstring((const char*)glGetString(GL_VENDOR));
4671 	UCSTR rendererString = mbstoucstring((const char*)glGetString(GL_RENDERER));
4672 	UCSTR versionString = mbstoucstring((const char*)glGetString(GL_VERSION));
4673 	const char *extensionsString = (const char*)glGetString(GL_EXTENSIONS);
4674 	UCSTR extensionsList;
4675 	size_t len;
4676 	UCSTR message;
4677 
4678 	numExtensions = 0;
4679 	if (!vendorString)
4680 	{
4681 		vendorString = copyString(ls(_UC("*Unknown*")));
4682 	}
4683 	if (!rendererString)
4684 	{
4685 		rendererString = copyString(ls(_UC("*Unknown*")));
4686 	}
4687 	if (!versionString)
4688 	{
4689 		versionString = copyString(ls(_UC("*Unknown*")));
4690 	}
4691 	if (extensionsString)
4692 	{
4693 		char *temp = stringByReplacingSubstring(extensionsString, " ",
4694 			"\r\n");
4695 
4696 		stripCRLF(temp);
4697 		numExtensions = countStringLines(temp);
4698 		extensionsList = mbstoucstring(temp);
4699 		delete[] temp;
4700 	}
4701 	else
4702 	{
4703 		extensionsList = copyString(ls(_UC("*None*")));
4704 	}
4705 	len = ucstrlen(vendorString) + ucstrlen(rendererString) +
4706 		ucstrlen(versionString) + ucstrlen(extensionsList) + 128;
4707 	message = new UCCHAR[len];
4708 	sucprintf(message, len, ls(_UC("OpenGlInfo")), vendorString, rendererString,
4709 		versionString, extensionsList);
4710 	delete[] vendorString;
4711 	delete[] rendererString;
4712 	delete[] versionString;
4713 	delete[] extensionsList;
4714 	return message;
4715 }
4716 
getViewInfo(ucstring & message,ucstring & commandLine)4717 bool LDrawModelViewer::getViewInfo(ucstring &message, ucstring &commandLine)
4718 {
4719 	if (mainModel)
4720 	{
4721 		TCFloat matrix[16];
4722 		TCFloat otherMatrix[16] = {1,0,0,0,0,-1,0,0,0,0,-1,0,0,0,0,1};
4723 		UCCHAR matrixString[1024];
4724 		UCCHAR zoomString[128];
4725 		UCCHAR messageBuf[4096];
4726 		UCCHAR commandLineBuf[1024];
4727 		TCFloat cameraDistance;
4728 
4729 		TCVector::multMatrix(otherMatrix, rotationMatrix, matrix);
4730 		cleanupFloats(matrix);
4731 		sucprintf(matrixString, COUNT_OF(matrixString),
4732 			_UC("%.6g,%.6g,%.6g,%.6g,%.6g,%.6g,%.6g,%.6g,%.6g"), matrix[0],
4733 			matrix[4], matrix[8], matrix[1], matrix[5], matrix[9],
4734 			matrix[2], matrix[6], matrix[10]);
4735 		cameraDistance = camera.getPosition().length();
4736 		if (distanceMultiplier == 0.0f || cameraDistance == 0.0f)
4737 		{
4738 			// If we don't have a model, we don't know the default zoom, so
4739 			// just say 1.
4740 			ucstrcpy(zoomString, _UC("1"));
4741 		}
4742 		else
4743 		{
4744 			sucprintf(zoomString, COUNT_OF(zoomString), _UC("%.6g"),
4745 				defaultDistance / distanceMultiplier / cameraDistance);
4746 		}
4747 		sucprintf(messageBuf, COUNT_OF(messageBuf), ls(_UC("ViewInfoMessage")),
4748 			matrixString, zoomString);
4749 		sucprintf(commandLineBuf, COUNT_OF(commandLineBuf),
4750 			_UC("-DefaultMatrix=%s -DefaultZoom=%s"), matrixString, zoomString);
4751 		message = messageBuf;
4752 		commandLine = commandLineBuf;
4753 		return true;
4754 	}
4755 	else
4756 	{
4757 		return false;
4758 	}
4759 }
4760 
getWideLineMargin(void)4761 TCFloat LDrawModelViewer::getWideLineMargin(void)
4762 {
4763 	TCFloat margin = 0.0f;
4764 
4765 	if (flags.showsHighlightLines)
4766 	{
4767 		if (getScaledHighlightLineWidth() >= 2.0f)
4768 		{
4769 			margin = getScaledHighlightLineWidth() / 2.0f;
4770 		}
4771 		else
4772 		{
4773 			margin = 1.0f;
4774 		}
4775 	}
4776 	if (flags.drawWireframe && getScaledWireframeLineWidth() > 1.0)
4777 	{
4778 		if (getScaledWireframeLineWidth() / 2.0f > margin)
4779 		{
4780 			margin = getScaledWireframeLineWidth() / 2.0f;
4781 		}
4782 	}
4783 	return margin;
4784 }
4785 
4786 //static int _numPoints = 0;
4787 
4788 // This is conversion of Lars Hassing's auto camera code from L3P.  It computes
4789 // the correct distance and pan amount for the camera so that the viewing
4790 // pyramid will be positioned in the closest possible position, such that the
4791 // model just touches the edges of the view on either the top and bottom, the
4792 // left and right, or all four.
4793 // After processing all the model data for the current camera angle, it ends up
4794 // with 6 equations with 6 unknowns.  It uses a matrix solving routine to solve
4795 // these.  The 6 values seem to be the X, Y, and Z coordinates of two points.
4796 // Once it has the values, it decides which point is the correct point, and then
4797 // uses that as the camera location.
zoomToFit(void)4798 void LDrawModelViewer::zoomToFit(void)
4799 {
4800 	if (mainTREModel)
4801 	{
4802 		LDLAutoCamera *autoCamera = new LDLAutoCamera;
4803 		char *cameraGlobe = TCUserDefaults::stringForKey(CAMERA_GLOBE_KEY, NULL,
4804 			false);
4805 
4806 		autoCamera->setModel(getCurModel());
4807 		autoCamera->setModelCenter(center);
4808 		autoCamera->setRotationMatrix(rotationMatrix);
4809 		autoCamera->setCamera(camera);
4810 		autoCamera->setCameraGlobe(cameraGlobe);
4811 		autoCamera->setDistanceMultiplier(distanceMultiplier);
4812 		autoCamera->setWidth(scale(width) * numXTiles / getStereoWidthModifier());
4813 		autoCamera->setHeight(scale(height) * numYTiles);
4814 		autoCamera->setMargin(getWideLineMargin() * 2.0f);
4815 		autoCamera->setFov(fov);
4816 		autoCamera->setStep(step);
4817 		autoCamera->setScanConditionalControlPoints(
4818 			getShowConditionalControlPoints());
4819 
4820 		autoCamera->zoomToFit();
4821 		camera.setPosition(autoCamera->getCamera().getPosition());
4822 		xPan = 0.0f;
4823 		yPan = 0.0f;
4824 		TCObject::release(autoCamera);
4825 	}
4826 }
4827 
setStep(int value)4828 void LDrawModelViewer::setStep(int value)
4829 {
4830 	step = value - 1;
4831 	if (mainTREModel)
4832 	{
4833 		mainTREModel->setStep(step);
4834 	}
4835 }
4836 
getNumSteps(void) const4837 int LDrawModelViewer::getNumSteps(void) const
4838 {
4839 	if (mainTREModel)
4840 	{
4841 		return mainTREModel->getNumSteps();
4842 	}
4843 	else
4844 	{
4845 		return 0;
4846 	}
4847 }
4848 
setLatLon(float lat,float lon,float distance)4849 void LDrawModelViewer::setLatLon(
4850 	float lat,
4851 	float lon,
4852 	float distance /*= -1.0f*/)
4853 {
4854 	fixLongitude(lon);
4855 	if (viewMode == VMExamine && examineMode == EMLatLong)
4856 	{
4857 		examineLatitude = lat;
4858 		examineLongitude = lon;
4859 	}
4860 	else if (rotationMatrix)
4861 	{
4862 		TCVector::calcRotationMatrix(lat, lon, rotationMatrix);
4863 		flags.needsRotationMatrixSetup = true;
4864 	}
4865 	if (distance >= 0.0f)
4866 	{
4867 		camera.setPosition(TCVector(0.0, 0.0, distance));
4868 		flags.constrainZoom = false;
4869 	}
4870 	else
4871 	{
4872 		flags.constrainZoom = viewMode == VMExamine;
4873 	}
4874 	requestRedraw();
4875 }
4876 
enable(GLenum cap)4877 void LDrawModelViewer::enable(GLenum cap)
4878 {
4879 	if (getGl2ps())
4880 	{
4881 		GLint mode = GL2PS_BLEND;
4882 
4883 		if (cap == GL_POLYGON_OFFSET_FILL)
4884 		{
4885 			mode = GL2PS_POLYGON_OFFSET_FILL;
4886 		}
4887 		gl2psEnable(mode);
4888 	}
4889 	glEnable(cap);
4890 }
4891 
disable(GLenum cap)4892 void LDrawModelViewer::disable(GLenum cap)
4893 {
4894 	if (getGl2ps())
4895 	{
4896 		GLint mode = GL2PS_BLEND;
4897 
4898 		if (cap == GL_POLYGON_OFFSET_FILL)
4899 		{
4900 			mode = GL2PS_POLYGON_OFFSET_FILL;
4901 		}
4902 		gl2psDisable(mode);
4903 	}
4904 	glDisable(cap);
4905 }
4906 
blendFunc(GLenum sfactor,GLenum dfactor)4907 void LDrawModelViewer::blendFunc(GLenum sfactor, GLenum dfactor)
4908 {
4909 	if (getGl2ps())
4910 	{
4911 		gl2psBlendFunc(sfactor, dfactor);
4912 	}
4913 	glBlendFunc(sfactor, dfactor);
4914 }
4915 
lineWidth(GLfloat lwidth)4916 void LDrawModelViewer::lineWidth(GLfloat lwidth)
4917 {
4918 	if (getGl2ps())
4919 	{
4920 		gl2psLineWidth(lwidth);
4921 	}
4922 	glLineWidth(lwidth);
4923 }
4924 
setMpdChildIndex(int index)4925 void LDrawModelViewer::setMpdChildIndex(int index)
4926 {
4927 	if (index != mpdChildIndex)
4928 	{
4929 		mpdChildIndex = index;
4930 		flags.needsReparse = true;
4931 		flags.needsCalcSize = true;
4932 		flags.needsViewReset = true;
4933 		flags.needsResetStep = true;
4934 		requestRedraw();
4935 	}
4936 }
4937 
getMpdChild(void)4938 LDLModel *LDrawModelViewer::getMpdChild(void)
4939 {
4940 	if (mainModel == NULL)
4941 	{
4942 		return NULL;
4943 	}
4944 	LDLModelVector &mpdModels = mainModel->getMpdModels();
4945 
4946 	if (mpdModels.size() > 0 && mpdName.size() > 0)
4947 	{
4948 		for (size_t i = 0; i < mpdModels.size(); i++)
4949 		{
4950 			if (strcasecmp(mpdModels[i]->getName(), mpdName.c_str()) == 0)
4951 			{
4952 				mpdChildIndex = (int)i;
4953 				mpdName = "";
4954 				break;
4955 			}
4956 		}
4957 	}
4958 	if ((int)mpdModels.size() > mpdChildIndex && mpdChildIndex >= 0)
4959 	{
4960 		return mpdModels[mpdChildIndex];
4961 	}
4962 	else
4963 	{
4964 		return NULL;
4965 	}
4966 }
4967 
getMpdChild(void) const4968 const LDLModel *LDrawModelViewer::getMpdChild(void) const
4969 {
4970 	return const_cast<LDrawModelViewer *>(this)->getMpdChild();
4971 }
4972 
getCurModel(void)4973 LDLModel *LDrawModelViewer::getCurModel(void)
4974 {
4975 	LDLModel *curModel = getMpdChild();
4976 
4977 	if (curModel == NULL)
4978 	{
4979 		curModel = mainModel;
4980 	}
4981 	return curModel;
4982 }
4983 
getCurModel(void) const4984 const LDLModel *LDrawModelViewer::getCurModel(void) const
4985 {
4986 	return const_cast<LDrawModelViewer *>(this)->getCurModel();
4987 }
4988 
initExporter(void)4989 LDExporter *LDrawModelViewer::initExporter(void)
4990 {
4991 	if (exporter == NULL)
4992 	{
4993 		switch (exportType)
4994 		{
4995 		case ETPov:
4996 			exporter = new LDPovExporter;
4997 			break;
4998 #ifdef EXPORT_LDR
4999 		case ETLdr:
5000 			exporter = new LDLdrExporter;
5001 			break;
5002 #endif // EXPORT_LDR
5003 		case ETStl:
5004 			exporter = new LDStlExporter;
5005 			break;
5006 #ifdef EXPORT_3DS
5007 		case ET3ds:
5008 			exporter = new LD3dsExporter;
5009 			break;
5010 #endif // EXPORT_3DS
5011 		default:
5012 			exporter = NULL;
5013 			break;
5014 		}
5015 	}
5016 	return exporter;
5017 }
5018 
setExportType(ExportType type,bool forceNew)5019 void LDrawModelViewer::setExportType(ExportType type, bool forceNew /*= false*/)
5020 {
5021 	if (forceNew || (type != exportType && type != (ExportType)0))
5022 	{
5023 		if (type != (ExportType)0)
5024 		{
5025 			exportType = type;
5026 		}
5027 		TCObject::release(exporter);
5028 		exporter = NULL;
5029 	}
5030 	initExporter();
5031 }
5032 
getExporter(ExportType type,bool forceNew)5033 LDExporter *LDrawModelViewer::getExporter(
5034 	ExportType type /*= (ExportType)0*/,
5035 	bool forceNew /*= false*/)
5036 {
5037 	setExportType(type, forceNew);
5038 	return initExporter();
5039 }
5040 
exportCurModel(const char * lfilename,const char * version,const char * copyright,ExportType type)5041 int LDrawModelViewer::exportCurModel(
5042 	const char *lfilename,
5043 	const char *version /*= NULL*/,
5044 	const char *copyright /*= NULL*/,
5045 	ExportType type /*= (ExportType)0*/)
5046 {
5047 	LDLModel *model = getCurModel();
5048 	int retValue = 1;
5049 
5050 	try
5051 	{
5052 		if (model != NULL)
5053 		{
5054 			setExportType(type);
5055 			TCObject::release(exporter);
5056 			exporter = NULL;
5057 			if (initExporter() != NULL)
5058 			{
5059 				exporter->setBoundingBox(boundingMin, boundingMax);
5060 				exporter->setCenter(center);
5061 				exporter->setWidth(width);
5062 				exporter->setHeight(height);
5063 				exporter->setRadius(size / 2.0f);
5064 				exporter->setBackgroundColor(backgroundR, backgroundG,
5065 					backgroundB);
5066 				exporter->setCamera(camera);
5067 				exporter->setRotationMatrix(rotationMatrix);
5068 				exporter->setFov(fov);
5069 				exporter->setXPan(xPan);
5070 				exporter->setYPan(yPan);
5071 				exporter->setAppUrl("https://tcobbs.github.io/ldview/");
5072 				exporter->setAppName("LDView");
5073 				if (!sm_appVersion.empty())
5074 				{
5075 					exporter->setAppVersion(sm_appVersion.c_str());
5076 				}
5077 				else if (version != NULL)
5078 				{
5079 					exporter->setAppVersion(version);
5080 				}
5081 				if (!sm_appCopyright.empty())
5082 				{
5083 					exporter->setAppCopyright(sm_appCopyright.c_str());
5084 				}
5085 				else if (copyright)
5086 				{
5087 					exporter->setAppCopyright(copyright);
5088 				}
5089 				if (lfilename != NULL)
5090 				{
5091 					exporter->setFilename(lfilename);
5092 				}
5093 				if (exporter->usesLDLModel())
5094 				{
5095 					if (exporter->usesTREModel())
5096 					{
5097 						retValue = exporter->doExport(model, mainTREModel);
5098 					}
5099 					else
5100 					{
5101 						retValue = exporter->doExport(model);
5102 					}
5103 				}
5104 				else
5105 				{
5106 					retValue = exporter->doExport(mainTREModel);
5107 				}
5108 				exporter->release();
5109 				exporter = NULL;
5110 			}
5111 		}
5112 	}
5113 	catch (char const* exception)
5114 	{
5115 		debugPrintf("Exception during export: %s\n", exception);
5116 		// Until we actually handle this, leave the exception alone.
5117 		throw exception;
5118 	}
5119 	return retValue;
5120 }
5121 
getCurFilename(void) const5122 std::string LDrawModelViewer::getCurFilename(void) const
5123 {
5124 	const LDLModel *curModel = getCurModel();
5125 	const char *lfilename = getFilename();
5126 
5127 	if (curModel == mainModel)
5128 	{
5129 		return lfilename;
5130 	}
5131 	else
5132 	{
5133 		const char *modelName = curModel->getName();
5134 		std::string retValue = directoryFromPath(lfilename);
5135 		char *temp;
5136 
5137 		retValue += '/';
5138 		retValue += modelName;
5139 		temp = cleanedUpPath(retValue.c_str());
5140 		retValue = temp;
5141 		delete[] temp;
5142 		return retValue;
5143 	}
5144 }
5145 
5146 // Note: static method
addStandardSize(int width,int height,CUCSTR name)5147 void LDrawModelViewer::addStandardSize(int width, int height, CUCSTR name)
5148 {
5149 	StandardSize standardSize;
5150 	ucstring buf = ltoucstr(width);
5151 	ucstring aspectString = getAspectString(width, height, _UC(":"), true);
5152 
5153 	standardSize.width = width;
5154 	standardSize.height = height;
5155 	standardSizes.push_back(standardSize);
5156 	buf += _UC(" x ");
5157 	buf += ltoucstr(height);
5158 	if (aspectString.length() > 0)
5159 	{
5160 		buf += _UC(" (");
5161 		buf += aspectString.c_str();
5162 		if (name != NULL)
5163 		{
5164 			buf += _UC(" \"");
5165 			buf += name;
5166 			buf += _UC("\"");
5167 		}
5168 		buf += _UC(")");
5169 	}
5170 	standardSizes.back().name = buf;
5171 }
5172 
5173 // Note: static method
initStandardSizes(void)5174 void LDrawModelViewer::initStandardSizes(void)
5175 {
5176 	if (standardSizes.size() == 0)
5177 	{
5178 		addStandardSize(640, 480, _UC("VGA"));
5179 		// Note: 720 width has all heights for "standard" aspect ratios.
5180 		addStandardSize(720, 306);	// 2.35:1
5181 		addStandardSize(720, 405);	// 16:9
5182 		addStandardSize(720, 432);	// 5:3
5183 		addStandardSize(720, 450);	// 16:10
5184 		addStandardSize(720, 480);	// 3:2
5185 		addStandardSize(720, 540);	// 4:3
5186 		addStandardSize(720, 576);	// 5:4
5187 		addStandardSize(800, 450);
5188 		addStandardSize(800, 480);
5189 		addStandardSize(800, 600, _UC("SVGA"));
5190 		addStandardSize(960, 640); // 3.5" Retina iPhone
5191 		addStandardSize(1024, 576);
5192 		addStandardSize(1024, 768, _UC("XGA"));
5193 		addStandardSize(1136, 640); // 4" Retina iPhone
5194 		addStandardSize(1152, 864);
5195 		addStandardSize(1280, 544);
5196 		addStandardSize(1280, 720, _UC("HD"));
5197 		addStandardSize(1280, 768);
5198 		addStandardSize(1280, 960);
5199 		addStandardSize(1280, 1024);
5200 		addStandardSize(1334, 750); // 4.7" Retina iPhone
5201 		addStandardSize(1600, 1200);
5202 		addStandardSize(1680, 1050);
5203 		addStandardSize(1792, 828); // 6.1" Retina iPhone Xr
5204 		addStandardSize(1920, 817);
5205 		addStandardSize(1920, 1080, _UC("FHD")); // "1080p"
5206 		addStandardSize(1920, 1200); // "WUXGA"
5207 		addStandardSize(1920, 1440); // 4:3
5208 		addStandardSize(2048, 1080, _UC("DCI 2K"));
5209 		addStandardSize(2048, 1536); // Retina iPad
5210 		addStandardSize(2160, 1440); // Surface Pro 3
5211 		addStandardSize(2208, 1242); // 5.5" Retina iPhone
5212 		addStandardSize(2436, 1125); // 5.8" Retina iPhone X
5213 		addStandardSize(2520, 1080); // "2.5Kx1K"
5214 		addStandardSize(2560, 1080); // "2.5Kx1K"
5215 		addStandardSize(2560, 1440); // "1440p"
5216 		addStandardSize(2560, 1600); // 13" Retina MacBook Pro
5217 		addStandardSize(2560, 1920); // 4:3
5218 		addStandardSize(2688, 1242); // 6.5" Retina iPhone Xs Max
5219 		addStandardSize(2736, 1824); // Surface Pro 4
5220 		addStandardSize(2880, 1800); // 15" Retina MacBook Pro
5221 		addStandardSize(3840, 2160, _UC("UHD")); // "2160p" or "4K"
5222 		addStandardSize(3840, 2400); // "WQUXGA"
5223 		addStandardSize(4096, 2160, _UC("DCI 4K"));
5224 		addStandardSize(5120, 2160, _UC("5Kx2K"));
5225 		addStandardSize(5120, 2880, _UC("5K"));
5226 		addStandardSize(7680, 4320, _UC("8K")); // "4320p"
5227 		addStandardSize(8192, 4320, _UC("DCI 8K"));
5228 	}
5229 }
5230 
5231 // Note: static method
getStandardSizes(int maxWidth,int maxHeight,StandardSizeVector & sizes)5232 void LDrawModelViewer::getStandardSizes(
5233 	int maxWidth,
5234 	int maxHeight,
5235 	StandardSizeVector &sizes)
5236 {
5237 	initStandardSizes();
5238 	sizes.clear();
5239 	for (StandardSizeList::const_iterator it = standardSizes.begin();
5240 		it != standardSizes.end(); ++it)
5241 	{
5242 		const StandardSize &standardSize = *it;
5243 
5244 		if (standardSize.width <= maxWidth && standardSize.height <= maxHeight)
5245 		{
5246 			sizes.push_back(standardSize);
5247 		}
5248 	}
5249 }
5250 
getDistance(void) const5251 TCFloat LDrawModelViewer::getDistance(void) const
5252 {
5253 	return camera.getPosition().length();
5254 }
5255 
setCameraMotion(const TCVector & value)5256 void LDrawModelViewer::setCameraMotion(const TCVector &value)
5257 {
5258 	cameraMotion = value;
5259 	updateFrameTime();
5260 }
5261 
setCameraXRotate(TCFloat value)5262 void LDrawModelViewer::setCameraXRotate(TCFloat value)
5263 {
5264 	cameraXRotate = value;
5265 	updateFrameTime();
5266 }
5267 
setCameraYRotate(TCFloat value)5268 void LDrawModelViewer::setCameraYRotate(TCFloat value)
5269 {
5270 	cameraYRotate = value;
5271 	updateFrameTime();
5272 }
5273 
setCameraZRotate(TCFloat value)5274 void LDrawModelViewer::setCameraZRotate(TCFloat value)
5275 {
5276 	cameraZRotate = value;
5277 	updateFrameTime();
5278 }
5279 
setHighlightPaths(std::string value)5280 void LDrawModelViewer::setHighlightPaths(std::string value)
5281 {
5282 	highlightPaths.clear();
5283 	while (value.size() > 0)
5284 	{
5285 		size_t index = value.find('\n');
5286 		std::string line;
5287 
5288 		if (index < value.size())
5289 		{
5290 			line = value.substr(0, index);
5291 			value = value.substr(index + 1);
5292 		}
5293 		else
5294 		{
5295 			line = value;
5296 			value = "";
5297 		}
5298 		if (line.size() > 0)
5299 		{
5300 			highlightPaths.push_back(line);
5301 		}
5302 	}
5303 	highlightPathsChanged();
5304 }
5305 
setHighlightPaths(const StringList & value)5306 void LDrawModelViewer::setHighlightPaths(const StringList &value)
5307 {
5308 	highlightPaths = value;
5309 	highlightPathsChanged();
5310 }
5311 
attachFileLine(LDLFileLine * dstFileLine,LDLFileLineArray * dstFileLines,LDLModel * dstModel)5312 void LDrawModelViewer::attachFileLine(
5313 	LDLFileLine *dstFileLine,
5314 	LDLFileLineArray *dstFileLines,
5315 	LDLModel *dstModel)
5316 {
5317 	if (dstFileLine)
5318 	{
5319 		dstFileLine->setLineNumber(dstFileLines->getCount());
5320 		dstFileLine->setParentModel(dstModel);
5321 		dstFileLines->addObject(dstFileLine);
5322 		dstModel->setActiveLineCount(dstModel->getActiveLineCount() + 1);
5323 		dstFileLine->release();
5324 	}
5325 }
5326 
attachLineLine(LDLFileLineArray * dstFileLines,LDLModel * dstModel,const TCVector & pt0,const TCVector & pt1)5327 void LDrawModelViewer::attachLineLine(
5328 	LDLFileLineArray *dstFileLines,
5329 	LDLModel *dstModel,
5330 	const TCVector &pt0,
5331 	const TCVector &pt1)
5332 {
5333 	char line[1024];
5334 	LDLLineLine *dstFileLine;
5335 
5336 	sprintf(line, "2 16 %s %s", pt0.string().c_str(), pt1.string().c_str());
5337 	dstFileLine = new LDLLineLine(dstModel, line, dstFileLines->getCount());
5338 	dstFileLine->parse();
5339 	attachFileLine(dstFileLine, dstFileLines, dstModel);
5340 }
5341 
resetColors(LDLModel * model)5342 void LDrawModelViewer::resetColors(LDLModel *model)
5343 {
5344 	if (model != NULL)
5345 	{
5346 		LDLFileLineArray *fileLines = model->getFileLines();
5347 
5348 		if (fileLines != NULL)
5349 		{
5350 			int count = model->getActiveLineCount();
5351 
5352 			for (int i = 0; i < count; i++)
5353 			{
5354 				resetColors((*fileLines)[i]);
5355 			}
5356 		}
5357 	}
5358 }
5359 
resetColors(LDLFileLine * fileLine)5360 void LDrawModelViewer::resetColors(LDLFileLine *fileLine)
5361 {
5362 	if (fileLine != NULL && fileLine->isActionLine())
5363 	{
5364 		LDLActionLine *actionLine = (LDLActionLine *)fileLine;
5365 
5366 		if (actionLine->getColorNumber() != 24)
5367 		{
5368 			actionLine->setColorNumber(16);
5369 		}
5370 		if (actionLine->getLineType() == LDLLineTypeModel)
5371 		{
5372 			resetColors(((LDLModelLine *)actionLine)->getLowResModel());
5373 			resetColors(((LDLModelLine *)actionLine)->getModel(true));
5374 		}
5375 	}
5376 }
5377 
parseHighlightPath(const std::string & path,const LDLModel * srcModel,LDLModel * dstModel,const std::string & prePath,int pathNum)5378 void LDrawModelViewer::parseHighlightPath(
5379 	const std::string &path,
5380 	const LDLModel *srcModel,
5381 	LDLModel *dstModel,
5382 	const std::string &prePath,
5383 	int pathNum)
5384 {
5385 	int lineNum = atoi(&path[1]) - 1;
5386 	const LDLFileLineArray *srcFileLines = srcModel->getFileLines();
5387 
5388 	if (lineNum < srcFileLines->getCount())
5389 	{
5390 		const LDLFileLine *srcFileLine = (*srcFileLines)[lineNum];
5391 		LDLFileLineArray *dstFileLines = dstModel->getFileLines(true);
5392 		LDLFileLine *dstFileLine = NULL;
5393 		bool isModel = false;
5394 		size_t nextSlash = path.find('/', 1);
5395 
5396 		switch (srcFileLine->getLineType())
5397 		{
5398 		case LDLLineTypeModel:
5399 			isModel = true;
5400 			if (nextSlash < path.size())
5401 			{
5402 				const LDLModelLine *srcModelLine =
5403 					(const LDLModelLine *)srcFileLine;
5404 				// The names need to be unique to prevent them from being
5405 				// combined.  Since we're not showing the actual model, we
5406 				// use a combination of our pathNum and the path to now as
5407 				// the model name.
5408 				std::string name = ltostr(pathNum) + prePath + '/';
5409 
5410 				name += ltostr(lineNum + 1);
5411 				dstFileLine = new LDLModelLine(dstModel, srcFileLine->getLine(),
5412 					dstFileLines->getCount(), srcFileLine->getOriginalLine());
5413 				LDLModelLine *dstModelLine = (LDLModelLine *)dstFileLine;
5414 				dstModelLine->setMatrix(srcModelLine->getMatrix());
5415 				if (srcModelLine->getLowResModel() != NULL)
5416 				{
5417 					dstModelLine->createLowResModel(dstModel->getMainModel(),
5418 						name.c_str());
5419 				}
5420 				if (srcModelLine->getHighResModel() != NULL)
5421 				{
5422 					dstModelLine->createHighResModel(dstModel->getMainModel(),
5423 						name.c_str());
5424 				}
5425 				const LDLModel *srcHighResModel =
5426 					srcModelLine->getHighResModel();
5427 				if (srcHighResModel->isPart() && !srcModel->isPart() &&
5428 					seamWidth > 0.0)
5429 				{
5430 					// We have to apply seams manually here, because TRE, which
5431 					// normally handles the seams, does so based on the actual
5432 					// geometry, not the LDLModel's bounding box.  Since only a
5433 					// subset of the geometry makes it to TRE, we have to do the
5434 					// seams adjustment here instead.
5435 					TCFloat scaleMatrix[16];
5436 					TCFloat newMatrix[16];
5437 
5438 					TCVector lboundingMin, lboundingMax;
5439 					srcHighResModel->getBoundingBox(lboundingMin, lboundingMax);
5440 					TCVector::calcScaleMatrix(seamWidth, scaleMatrix,
5441 						lboundingMin, lboundingMax);
5442 					TCVector::multMatrix(dstModelLine->getMatrix(), scaleMatrix,
5443 						newMatrix);
5444 					dstModelLine->setMatrix(newMatrix);
5445 					dstModelLine->setColorNumber(16);
5446 				}
5447 			}
5448 			else
5449 			{
5450 				dstFileLine = (LDLFileLine *)srcFileLine->copy();
5451 				resetColors((LDLModelLine *)dstFileLine);
5452 			}
5453 			break;
5454 		case LDLLineTypeLine:
5455 		case LDLLineTypeTriangle:
5456 		case LDLLineTypeQuad:
5457 			dstFileLine = (LDLFileLine *)srcFileLine->copy();
5458 			break;
5459 		case LDLLineTypeConditionalLine:
5460 			{
5461 				LDLConditionalLineLine *condLine =
5462 					(LDLConditionalLineLine *)srcFileLine;
5463 
5464 				attachLineLine(dstFileLines, dstModel, condLine->getPoints()[0],
5465 					condLine->getPoints()[1]);
5466 				attachLineLine(dstFileLines, dstModel, condLine->getPoints()[0],
5467 					condLine->getControlPoints()[0]);
5468 				attachLineLine(dstFileLines, dstModel, condLine->getPoints()[0],
5469 					condLine->getControlPoints()[1]);
5470 			}
5471 			break;
5472 		default:
5473 			// Don't do anything for other line types.
5474 			break;
5475 		}
5476 		if (!isModel)
5477 		{
5478 			if (nextSlash < path.size())
5479 			{
5480 				throw "Invalid Tree Path";
5481 			}
5482 		}
5483 		attachFileLine(dstFileLine, dstFileLines, dstModel);
5484 		if (isModel)
5485 		{
5486 			if (nextSlash < path.size())
5487 			{
5488 				LDLModelLine *dstModelLine = (LDLModelLine *)dstFileLine;
5489 
5490 				if (dstModelLine->getLowResModel())
5491 				{
5492 					parseHighlightPath(path.substr(nextSlash),
5493 						((LDLModelLine *)srcFileLine)->getModel(),
5494 						dstModelLine->getLowResModel(),
5495 						prePath + path.substr(0, nextSlash),
5496 						pathNum);
5497 				}
5498 				if (dstModelLine->getHighResModel())
5499 				{
5500 					parseHighlightPath(path.substr(nextSlash),
5501 						((LDLModelLine *)srcFileLine)->getModel(),
5502 						dstModelLine->getHighResModel(),
5503 						prePath + path.substr(0, nextSlash),
5504 						pathNum);
5505 				}
5506 			}
5507 		}
5508 	}
5509 }
5510 
setHighlightColor(int r,int g,int b,bool redraw)5511 void LDrawModelViewer::setHighlightColor(
5512 	int r,
5513 	int g,
5514 	int b,
5515 	bool redraw /*= true*/)
5516 {
5517 	highlightR = r & 0xFF;
5518 	highlightG = g & 0xFF;
5519 	highlightB = b & 0xFF;
5520 	if (redraw)
5521 	{
5522 		highlightPathsChanged();
5523 	}
5524 }
5525 
adjustHighlightPath(std::string path,LDLModel * mpdChild)5526 std::string LDrawModelViewer::adjustHighlightPath(
5527 	std::string path,
5528 	LDLModel *mpdChild)
5529 {
5530 	LDLModel *curModel = mainModel;
5531 
5532 	while (curModel != mpdChild && path.size() > 0)
5533 	{
5534 		int lineNum = atoi(&path[1]) - 1;
5535 		const LDLFileLineArray *fileLines = curModel->getFileLines();
5536 
5537 		if (lineNum < fileLines->getCount())
5538 		{
5539 			const LDLFileLine *fileLine = (*fileLines)[lineNum];
5540 			size_t index;
5541 
5542 			if (fileLine->getLineType() != LDLLineTypeModel)
5543 			{
5544 				return "";
5545 			}
5546 			curModel = ((LDLModelLine *)fileLine)->getModel();
5547 			index = path.find('/', 1);
5548 			if (index < path.size())
5549 			{
5550 				path = path.substr(index);
5551 			}
5552 		}
5553 		else
5554 		{
5555 			return "";
5556 		}
5557 	}
5558 	return path;
5559 }
5560 
highlightPathsChanged(void)5561 void LDrawModelViewer::highlightPathsChanged(void)
5562 {
5563 	TCObject::release(highlightModel);
5564 	highlightModel = NULL;
5565 	if (highlightPaths.size() > 0)
5566 	{
5567 		LDModelParser *modelParser = NULL;
5568 		LDLMainModel *ldlModel = new LDLMainModel;
5569 		int i = 0;
5570 		LDLModel *mpdChild = getMpdChild();
5571 		int highlightColorNumber = 0x3000000 | (highlightR << 16) |
5572 			(highlightG << 8) | highlightB;
5573 
5574 		ldlModel->setMainModel(ldlModel);
5575 		ldlModel->setForceHighlightColor(true);
5576 		ldlModel->setHighlightColorNumber(highlightColorNumber);
5577 		ldlModel->setLowResStuds(!flags.qualityStuds);
5578 		ldlModel->setTexmaps(false);
5579 		for (StringList::const_iterator it = highlightPaths.begin();
5580 			it != highlightPaths.end(); ++it)
5581 		{
5582 			if (mpdChild != NULL)
5583 			{
5584 				std::string path = adjustHighlightPath(*it, mpdChild);
5585 				if (path.size() > 0)
5586 				{
5587 					parseHighlightPath(path, mpdChild, ldlModel, "", i++);
5588 				}
5589 			}
5590 			else
5591 			{
5592 				parseHighlightPath(*it, mainModel, ldlModel, "", i++);
5593 			}
5594 		}
5595 		modelParser = new LDModelParser(this);
5596 		modelParser->setTexmapsFlag(false);
5597 		modelParser->setIsHighlightModel(true);
5598 		modelParser->setDefaultColorNumber(highlightColorNumber);
5599 		modelParser->setFlattenPartsFlag(false);
5600 		if (modelParser->parseMainModel(ldlModel))
5601 		{
5602 			highlightModel = modelParser->getMainTREModel();
5603 			if (TCUserDefaults::boolForKey(HIGHLIGHT_MODEL_EDGES_NO_DEPTH_KEY,
5604 			   true))
5605 			{
5606 				highlightModel->setNoDepthEdgeLinesFlag(true);
5607 			}
5608 			highlightModel->retain();
5609 			highlightModel->setSaveAlphaFlag(false);
5610 		}
5611 		ldlModel->release();
5612 		modelParser->release();
5613 	}
5614 	requestRedraw();
5615 }
5616 
5617 // Note: static method
resetUnofficialDownloadTimes(void)5618 void LDrawModelViewer::resetUnofficialDownloadTimes(void)
5619 {
5620 	TCUserDefaults::removeValueGroup("UnofficialPartChecks", false);
5621 }
5622 
getCameraLocation(void)5623 TCVector LDrawModelViewer::getCameraLocation(void)
5624 {
5625 	TCFloat transformationMatrix[16];
5626 	TCVector::invertMatrix(rotationMatrix, transformationMatrix);
5627 	TCVector cameraPosition = camera.getPosition().transformPoint(
5628 		transformationMatrix) + center;
5629 	for (int i = 0; i < 3; ++i)
5630 	{
5631 		if (fEq(cameraPosition[i], 0.0))
5632 		{
5633 			cameraPosition[i] = 0.0;
5634 		}
5635 	}
5636 	return cameraPosition;
5637 }
5638 
setCameraLocation(const TCVector & newLocation,LDVLookAt lookAt,bool shouldRequestRedraw)5639 void LDrawModelViewer::setCameraLocation(
5640 	const TCVector& newLocation,
5641 	LDVLookAt lookAt,
5642 	bool shouldRequestRedraw)
5643 {
5644 	TCVector transLoc = (newLocation - center).transformPoint(
5645 		rotationMatrix);
5646 	camera.setPosition(transLoc);
5647 	if (lookAt != LDVLookAtNone)
5648 	{
5649 		TCVector lookAtPoint;
5650 		if (lookAt == LDVLookAtOrigin)
5651 		{
5652 			// I know this seems backwards, but it is this way due to the fact
5653 			// that center is always added to and subtracted from the camera
5654 			// location before we get to the point of aiming the camera. Becaus
5655 			// of that, we have to seemingly aim it at the center in order to
5656 			// really aim it at the origin, and vice versa.
5657 			lookAtPoint = center;
5658 		}
5659 		LDLFacing facing;
5660 		facing.pointAt(transLoc - lookAtPoint);
5661 		camera.setFacing(facing);
5662 		rightSideUp(shouldRequestRedraw);
5663 	}
5664 	else if (shouldRequestRedraw)
5665 	{
5666 		requestRedraw();
5667 	}
5668 }
5669 
rotationCenterChanged(void)5670 void LDrawModelViewer::rotationCenterChanged(void)
5671 {
5672 	flags.needsReparse = true;
5673 	flags.needsCalcSize = true;
5674 	flags.needsViewReset = true;
5675 	requestRedraw();
5676 }
5677 
setRotationCenter(const TCVector & value)5678 void LDrawModelViewer::setRotationCenter(const TCVector& value)
5679 {
5680 	std::string key = getModelKey("RotationCenter");
5681 	std::string valueString = value.defaultsString();
5682 	TCUserDefaults::setStringForKey(valueString.c_str(), key.c_str(), false);
5683 	rotationCenterChanged();
5684 }
5685 
resetRotationCenter(void)5686 void LDrawModelViewer::resetRotationCenter(void)
5687 {
5688 	std::string key = getModelKey("RotationCenter");
5689 	TCUserDefaults::removeValue(key.c_str());
5690 	rotationCenterChanged();
5691 }
5692 
getModelKey(const std::string & keyName)5693 std::string LDrawModelViewer::getModelKey(const std::string& keyName)
5694 {
5695 	std::string key = "Models/";
5696 	if (filename == NULL)
5697 	{
5698 		key += "Unknown";
5699 	}
5700 	else
5701 	{
5702 		std::string mangledFilename = filename;
5703 		replaceStringCharacter(&mangledFilename[0], '/', ':');
5704 		key += mangledFilename;
5705 	}
5706 	return key + "/" + keyName;
5707 }
5708 
getRotationCenter(TCVector & rotationCenter)5709 bool LDrawModelViewer::getRotationCenter(TCVector& rotationCenter)
5710 {
5711 	std::string key = getModelKey("RotationCenter");
5712 	char* centerString = TCUserDefaults::stringForKey(key.c_str(), NULL, false);
5713 	bool result = false;
5714 	if (centerString != NULL)
5715 	{
5716 		try {
5717 			rotationCenter = TCVector(centerString);
5718 			result = true;
5719 		} catch (...) {
5720 			// Ignore.
5721 		}
5722 		delete[] centerString;
5723 	}
5724 	return result;
5725 }
5726