1 #include "LDLModel.h"
2 #include "LDLMainModel.h"
3 #include "LDLCommentLine.h"
4 #include "LDLModelLine.h"
5 #include "LDLFindFileAlert.h"
6 #include "LDrawIni.h"
7 #include <TCFoundation/TCDictionary.h>
8 #include <TCFoundation/mystring.h>
9 #include <TCFoundation/TCStringArray.h>
10 #include <TCFoundation/TCAlertManager.h>
11 #include <TCFoundation/TCProgressAlert.h>
12 #include <TCFoundation/TCLocalStrings.h>
13 #include <TCFoundation/TCUserDefaults.h>
14 #include <TCFoundation/TCImage.h>
15 #include <math.h>
16 
17 #ifdef WIN32
18 #include <direct.h>
19 
20 #if defined(_MSC_VER) && _MSC_VER >= 1400 && defined(_DEBUG)
21 #define new DEBUG_CLIENTBLOCK
22 #endif // _DEBUG
23 
24 #else // WIN32
25 #include <unistd.h>
26 #endif // WIN32
27 
28 #define LDL_LOWRES_PREFIX "LDL-LOWRES:"
29 #define LOAD_MESSAGE TCLocalStrings::get(_UC("LDLModelLoading"))
30 #define MAIN_READ_FRACTION 0.1f
31 
32 char *LDLModel::sm_systemLDrawDir = NULL;
33 char *LDLModel::sm_defaultLDrawDir = NULL;
34 LDrawIniS *LDLModel::sm_lDrawIni = NULL;
35 int LDLModel::sm_modelCount = 0;
36 LDLFileCaseCallback LDLModel::fileCaseCallback = NULL;
37 LDLModel::LDLModelCleanup LDLModel::sm_cleanup;
38 StringList LDLModel::sm_checkDirs;
39 
~LDLModelCleanup(void)40 LDLModel::LDLModelCleanup::~LDLModelCleanup(void)
41 {
42 	delete[] LDLModel::sm_systemLDrawDir;
43 	delete[] LDLModel::sm_defaultLDrawDir;
44 	LDLModel::sm_systemLDrawDir = NULL;
45 	if (LDLModel::sm_lDrawIni)
46 	{
47 		LDrawIniFree(LDLModel::sm_lDrawIni);
48 	}
49 }
50 
51 
LDLModel(void)52 LDLModel::LDLModel(void)
53 	:m_filename(NULL),
54 	m_name(NULL),
55 	m_author(NULL),
56 	m_description(NULL),
57 	m_fileLines(NULL),
58 	m_mpdTexmapModels(NULL),
59 	m_mpdTexmapLines(NULL),
60 	m_mpdTexmapImages(NULL),
61 	m_mainModel(NULL),
62 	m_activeLineCount(0),
63 	m_activeMPDModel(NULL),
64 	m_texmapImage(NULL),
65 	m_dataLine(NULL)
66 {
67 	// Initialize Private flags
68 	m_flags.loadingPart = false;
69 	m_flags.loadingSubPart = false;
70 	m_flags.loadingPrimitive = false;
71 	m_flags.loadingUnoffic = false;
72 	m_flags.mainModelLoaded = false;
73 	m_flags.mainModelParsed = false;
74 	m_flags.started = false;
75 	m_flags.bfcClip = false;
76 	m_flags.bfcWindingCCW = true;
77 	m_flags.bfcInvertNext = false;
78 	m_flags.haveBoundingBox = false;
79 	m_flags.haveMaxRadius = false;
80 	m_flags.haveMaxFullRadius = false;
81 	m_flags.fullRadius = false;
82 	m_flags.texmapStarted = false;
83 	m_flags.texmapFallback = false;
84 	m_flags.texmapNext = false;
85 	// Initialize Public flags
86 	m_flags.part = false;
87 	m_flags.subPart = false;
88 	m_flags.primitive = false;
89 	m_flags.mpd = false;
90 	m_flags.noShrink = false;
91 	m_flags.official = false;
92 	m_flags.unofficial = false;
93 	m_flags.hasStuds = false;
94 	m_flags.bfcCertify = BFCUnknownState;
95 	m_flags.bboxIgnoreOn = false;
96 	m_flags.bboxIgnoreBegun = false;
97 	sm_modelCount++;
98 }
99 
LDLModel(const LDLModel & other)100 LDLModel::LDLModel(const LDLModel &other)
101 	:m_filename(copyString(other.m_filename)),
102 	m_name(copyString(other.m_name)),
103 	m_author(copyString(other.m_author)),
104 	m_description(copyString(other.m_description)),
105 	m_fileLines(NULL),
106 	m_mpdTexmapModels(NULL),
107 	m_mpdTexmapLines(NULL),
108 	m_mpdTexmapImages(NULL),
109 	m_mainModel(other.m_mainModel),
110 	m_stepIndices(other.m_stepIndices),
111 	m_activeLineCount(other.m_activeLineCount),
112 	m_activeMPDModel(NULL),
113 	m_boundingMin(other.m_boundingMin),
114 	m_boundingMax(other.m_boundingMax),
115 	m_center(other.m_center),
116 	m_maxRadius(other.m_maxRadius),
117 	m_texmapImage(TCObject::retain(other.m_texmapImage)),
118 	m_dataLine(TCObject::retain(other.m_dataLine)),
119 	m_flags(other.m_flags)
120 {
121 	if (other.m_fileLines)
122 	{
123 		m_fileLines = (LDLFileLineArray *)other.m_fileLines->copy();
124 	}
125 	if (other.m_mpdTexmapModels)
126 	{
127 		m_mpdTexmapModels = (LDLModelArray *)other.m_mpdTexmapModels->copy();
128 	}
129 	if (other.m_mpdTexmapLines)
130 	{
131 		m_mpdTexmapLines = (LDLCommentLineArray *)other.m_mpdTexmapLines->copy();
132 	}
133 	if (other.m_mpdTexmapImages)
134 	{
135 		m_mpdTexmapImages = (TCImageArray *)other.m_mpdTexmapImages->copy();
136 	}
137 }
138 
dealloc(void)139 void LDLModel::dealloc(void)
140 {
141 	delete[] m_filename;
142 	delete[] m_name;
143 	delete[] m_author;
144 	delete[] m_description;
145 	TCObject::release(m_fileLines);
146 	TCObject::release(m_mpdTexmapModels);
147 	TCObject::release(m_mpdTexmapLines);
148 	TCObject::release(m_mpdTexmapImages);
149 	TCObject::release(m_texmapImage);
150 	TCObject::release(m_dataLine);
151 	sm_modelCount--;
152 	TCObject::dealloc();
153 }
154 
copy(void) const155 TCObject *LDLModel::copy(void) const
156 {
157 	return new LDLModel(*this);
158 }
159 
setFilename(const char * filename)160 void LDLModel::setFilename(const char *filename)
161 {
162 	delete[] m_filename;
163 	m_filename = copyString(filename);
164 }
165 
setName(const char * name)166 void LDLModel::setName(const char *name)
167 {
168 	delete[] m_name;
169 	m_name = copyString(name);
170 }
171 
getPackedRGBA(int colorNumber)172 TCULong LDLModel::getPackedRGBA(int colorNumber)
173 {
174 	int r, g, b, a;
175 
176 	getRGBA(colorNumber, r, g, b, a);
177 	return r << 24 | g << 16 | b << 8 | a;
178 }
179 
hasSpecular(int colorNumber)180 bool LDLModel::hasSpecular(int colorNumber)
181 {
182 	return m_mainModel->hasSpecular(colorNumber);
183 }
184 
hasShininess(int colorNumber)185 bool LDLModel::hasShininess(int colorNumber)
186 {
187 	return m_mainModel->hasShininess(colorNumber);
188 }
189 
getSpecular(int colorNumber,float * specular)190 void LDLModel::getSpecular(int colorNumber, float *specular)
191 {
192 	m_mainModel->getSpecular(colorNumber, specular);
193 }
194 
getShininess(int colorNumber,float & shininess)195 void LDLModel::getShininess(int colorNumber, float &shininess)
196 {
197 	m_mainModel->getShininess(colorNumber, shininess);
198 }
199 
getEdgeColorNumber(int colorNumber)200 int LDLModel::getEdgeColorNumber(int colorNumber)
201 {
202 	return m_mainModel->getEdgeColorNumber(colorNumber);
203 }
204 
getRGBA(int colorNumber,int & r,int & g,int & b,int & a)205 void LDLModel::getRGBA(int colorNumber, int& r, int& g, int& b, int& a)
206 {
207 	m_mainModel->getRGBA(colorNumber, r, g, b, a);
208 }
209 
colorNumberIsTransparent(int colorNumber)210 bool LDLModel::colorNumberIsTransparent(int colorNumber)
211 {
212 	return m_mainModel->colorNumberIsTransparent(colorNumber);
213 }
214 
subModelNamed(const char * subModelName,bool lowRes,bool secondAttempt,const LDLModelLine * fileLine,bool knownPart)215 LDLModel *LDLModel::subModelNamed(const char *subModelName, bool lowRes,
216 								  bool secondAttempt,
217 								  const LDLModelLine *fileLine, bool knownPart)
218 {
219 	TCDictionary* subModelDict = getLoadedModels();
220 	LDLModel* subModel;
221 	char *dictName = NULL;
222 	char *adjustedName;
223 	bool &ancestorCheck = m_mainModel->ancestorCheck(subModelName);
224 	bool loop = false;
225 
226 	if (ancestorCheck)
227 	{
228 		// Recursion: the model named subModelName is an ancestor of the current
229 		// model.  Loading it will result in an infinite loop and crash after
230 		// the stack overflows.
231 		return NULL;
232 	}
233 	else
234 	{
235 		ancestorCheck = true;
236 	}
237 	if (strcasecmp(subModelName, "stud.dat") == 0)
238 	{
239 		m_flags.hasStuds = true;
240 	}
241 	adjustedName = copyString(subModelName);
242 	replaceStringCharacter(adjustedName, '\\', '/');
243 	if (lowRes)
244 	{
245 		if (stringHasCaseInsensitivePrefix(adjustedName, "stud"))
246 		{
247 			adjustedName[3] = '2';
248 		}
249 		else
250 		{
251 			delete[] adjustedName;
252 			ancestorCheck = false;
253 			return NULL;
254 		}
255 		dictName = new char[strlen(LDL_LOWRES_PREFIX) + strlen(adjustedName) +
256 			1];
257 		sprintf(dictName, "%s%s", LDL_LOWRES_PREFIX, adjustedName);
258 	}
259 	else
260 	{
261 		dictName = copyString(adjustedName);
262 	}
263 	subModel = (LDLModel*)(subModelDict->objectForKey(dictName));
264 	if (subModel == NULL)
265 	{
266 		std::ifstream subModelStream;
267 		std::string subModelPath;
268 
269 		if (openSubModelNamed(adjustedName, subModelPath, subModelStream,
270 			knownPart, &loop))
271 		{
272 			bool clearSubModel = false;
273 			replaceStringCharacter(&subModelPath[0], '\\', '/');
274 			subModel = new LDLModel;
275 			subModel->setFilename(subModelPath.c_str());
276 
277 			if (!initializeNewSubModel(subModel, dictName, subModelStream))
278 			{
279 				clearSubModel = true;
280 			}
281 			if (clearSubModel)
282 			{
283 				subModel = NULL;
284 			}
285 			m_flags.loadingPart = false;
286 			m_flags.loadingSubPart = false;
287 			m_flags.loadingUnoffic = false;
288 		}
289 	}
290 	if (subModel != NULL && subModel->isUnOfficial())
291 	{
292 		sendUnofficialWarningIfPart(subModel, fileLine, subModelName);
293 	}
294 	delete[] adjustedName;
295 	if (!subModel && !secondAttempt && !loop)
296 	{
297 		LDLFindFileAlert *alert = new LDLFindFileAlert(subModelName);
298 
299 		TCAlertManager::sendAlert(alert, this);
300 		if (alert->getFileFound())
301 		{
302 			subModel = subModelNamed(alert->getFilename(), lowRes, true,
303 				fileLine, alert->getPartFlag());
304 			if (subModel)
305 			{
306 				// The following is necessary in order for primitive
307 				// substitution to work.
308 				subModel->setName(dictName);
309 				sendUnofficialWarningIfPart(subModel, fileLine, subModelName);
310 			}
311 		}
312 		alert->release();
313 	}
314 	delete[] dictName;
315 	if (!loop)
316 	{
317 		ancestorCheck = false;
318 	}
319 	return subModel;
320 }
321 
sendUnofficialWarningIfPart(const LDLModel * subModel,const LDLModelLine * fileLine,const char * subModelName)322 void LDLModel::sendUnofficialWarningIfPart(
323 	const LDLModel *subModel,
324 	const LDLModelLine *fileLine,
325 	const char *subModelName)
326 {
327 	if (!isPart() && subModel->isPart())
328 	{
329 		UCCHAR szWarning[1024];
330 		UCSTR ucSubModelName = mbstoucstring(subModelName);
331 
332 		sucprintf(szWarning, COUNT_OF(szWarning),
333 			TCLocalStrings::get(_UC("LDLModelUnofficialPart")),
334 			ucSubModelName);
335 		delete[] ucSubModelName;
336 		reportWarning(LDLEUnofficialPart, *fileLine, szWarning);
337 	}
338 }
339 
340 // NOTE: static function
openStream(const char * filename,std::ifstream & stream)341 bool LDLModel::openStream(const char *filename, std::ifstream &stream)
342 {
343 	// Use binary mode to work with DOS and Unix line endings and allow
344 	// seeking in the file.  The file parsing code will still work fine and
345 	// strip out the extra data.
346 #ifdef _MSC_VER
347 	ucstring ucFilename;
348 	utf8toucstring(ucFilename, filename);
349 	if (!ucFilename.empty())
350 	{
351 		// Windows STL has a non-standard extension to support wide-character
352 		// filenames
353 		stream.open(ucFilename.c_str(), std::ios_base::binary);
354 	}
355 	else
356 	{
357 		// If the filename isn't valid UTF-8, try to open it using
358 		// the original non-wide version.
359 		stream.open(filename, std::ios_base::binary);
360 	}
361 #else // _MSC_VER
362 	stream.open(filename, std::ios_base::binary);
363 #endif // !_MSC_VER
364 	return stream.is_open() && !stream.fail();
365 }
366 
367 // NOTE: static function
openFile(const char * filename,std::ifstream & modelStream)368 bool LDLModel::openFile(const char *filename, std::ifstream &modelStream)
369 {
370 	char *newFilename = copyString(filename);
371 
372 	convertStringToLower(newFilename);
373 	if (fileCaseCallback)
374 	{
375 		if (openStream(newFilename, modelStream))
376 		{
377 			delete[] newFilename;
378 			return true;
379 		}
380 		convertStringToUpper(newFilename);
381 		if (openStream(newFilename, modelStream))
382 		{
383 			delete[] newFilename;
384 			return true;
385 		}
386 		strcpy(newFilename, filename);
387 		if (openStream(newFilename, modelStream))
388 		{
389 			delete[] newFilename;
390 			return true;
391 		}
392 		if (fileCaseCallback(newFilename))
393 		{
394 			openStream(newFilename, modelStream);
395 		}
396 	}
397 	else
398 	{
399 		openStream(newFilename, modelStream);
400 	}
401 	delete[] newFilename;
402 	return modelStream.is_open();
403 }
404 
openModelFile(const char * filename,std::ifstream & modelStream,bool isText,bool knownPart)405 bool LDLModel::openModelFile(
406 	const char *filename,
407 	std::ifstream &modelStream,
408 	bool isText,
409 	bool knownPart /*= false*/)
410 {
411 	if (openFile(filename, modelStream))
412 	{
413 		if (knownPart)
414 		{
415 			m_flags.loadingPart = true;
416 		}
417 		if (isText)
418 		{
419 			// Check for UTF-8 Byte order mark (BOM), and skip over it if
420 			// present. Only do this on text files. (Right now, texture maps
421 			// are the only binary files that get opened by this function.)
422 			skipUtf8BomIfPresent(modelStream);
423 		}
424 	}
425 	return modelStream.is_open() && !modelStream.fail();
426 }
427 
isSubPart(const char * subModelName)428 bool LDLModel::isSubPart(const char *subModelName)
429 {
430 	return stringHasCaseInsensitivePrefix(subModelName, "s/") ||
431 		stringHasCaseInsensitivePrefix(subModelName, "s\\");
432 }
433 
isAbsolutePath(const char * path)434 bool LDLModel::isAbsolutePath(const char *path)
435 {
436 #ifdef WIN32
437 	return strlen(path) >= 2 && path[1] == ':' || path[0] == '/';
438 #else
439 	return path[0] == '/';
440 #endif
441 }
442 
443 // NOTE: static function.
combinePathParts(std::string & path,const std::string & left,const std::string & middle,const std::string & right)444 void LDLModel::combinePathParts(
445 	std::string &path,
446 	const std::string &left,
447 	const std::string& middle,
448 	const std::string &right)
449 {
450 	path = left;
451 	path += middle;
452 	path += right;
453 }
454 
openSubModelNamed(const char * subModelName,std::string & subModelPath,std::ifstream & subModelStream,bool knownPart,bool * pLoop,bool isText)455 bool LDLModel::openSubModelNamed(
456 	const char* subModelName,
457 	std::string &subModelPath,
458 	std::ifstream &subModelStream,
459 	bool knownPart,
460 	bool *pLoop /*= NULL*/,
461 	bool isText /*= true*/)
462 {
463 	TCStringArray *extraSearchDirs = m_mainModel->getExtraSearchDirs();
464 
465 	if (pLoop != NULL)
466 	{
467 		*pLoop = false;
468 	}
469 	subModelPath = subModelName;
470 	if (isAbsolutePath(subModelPath.c_str()))
471 	{
472 		return openModelFile(subModelPath.c_str(), subModelStream, isText,
473 			knownPart);
474 	}
475 	else if (sm_lDrawIni && sm_lDrawIni->nSearchDirs > 0)
476 	{
477 		int i;
478 
479 		for (i = 0; i < sm_lDrawIni->nSearchDirs; i++)
480 		{
481 			LDrawSearchDirS *searchDir = &sm_lDrawIni->SearchDirs[i];
482 			bool skip = false;
483 
484 			if (searchDir->Flags & LDSDF_UNOFFIC)
485 			{
486 				if (m_mainModel->getCheckPartTracker())
487 				{
488 					skip = true;
489 				}
490 				else
491 				{
492 					m_flags.loadingUnoffic = true;
493 				}
494 			}
495 			if ((searchDir->Flags & LDSDF_SKIP) == 0 && !skip)
496 			{
497 				combinePathParts(subModelPath, searchDir->Dir, "/",
498 					subModelName);
499 				if (openModelFile(subModelPath.c_str(), subModelStream, isText))
500 				{
501 					char *mainModelPath = copyString(m_mainModel->getFilename());
502 #ifdef WIN32
503 					replaceStringCharacter(mainModelPath, '\\', '/');
504 					replaceStringCharacter(&subModelPath[0], '\\', '/');
505 #endif // WIN32
506 					if (strcasecmp(mainModelPath, subModelPath.c_str()) == 0)
507 					{
508 						// Recursive call to main model.
509 						delete[] mainModelPath;
510 						subModelStream.close();
511 						if (pLoop != NULL)
512 						{
513 							*pLoop = true;
514 						}
515 						return false;
516 					}
517 					delete[] mainModelPath;
518 					if (searchDir->Flags & LDSDF_DEFPRIM)
519 					{
520 						m_flags.loadingPrimitive = true;
521 					}
522 					else if (searchDir->Flags & LDSDF_DEFPART)
523 					{
524 						if (isSubPart(subModelName))
525 						{
526 							m_flags.loadingSubPart = true;
527 						}
528 						else
529 						{
530 							m_flags.loadingPart = true;
531 						}
532 					}
533 					return true;
534 				}
535 			}
536 		}
537 	}
538 	else
539 	{
540 		if (openModelFile(subModelPath.c_str(), subModelStream, isText))
541 		{
542 			return true;
543 		}
544 		combinePathParts(subModelPath, lDrawDir(), "/P/", subModelName);
545 		if (openModelFile(subModelPath.c_str(), subModelStream, isText))
546 		{
547 			m_flags.loadingPrimitive = true;
548 			return true;
549 		}
550 		combinePathParts(subModelPath, lDrawDir(), "/PARTS/", subModelName);
551 		if (openModelFile(subModelPath.c_str(), subModelStream, isText))
552 		{
553 			if (isSubPart(subModelName))
554 			{
555 				m_flags.loadingSubPart = true;
556 			}
557 			else
558 			{
559 				m_flags.loadingPart = true;
560 			}
561 			return true;
562 		}
563 		combinePathParts(subModelPath, lDrawDir(), "/MODELS/", subModelName);
564 		if (openModelFile(subModelPath.c_str(), subModelStream, isText))
565 		{
566 			return true;
567 		}
568 	}
569 	if (extraSearchDirs)
570 	{
571 		int i;
572 		int count = extraSearchDirs->getCount();
573 
574 		for (i = 0; i < count; i++)
575 		{
576 			combinePathParts(subModelPath, (*extraSearchDirs)[i], "/",
577 				subModelName);
578 			if (openModelFile(subModelPath.c_str(), subModelStream, isText))
579 			{
580 				return true;
581 			}
582 		}
583 	}
584 	return false;
585 }
586 
initializeNewSubModel(LDLModel * subModel,const char * dictName)587 bool LDLModel::initializeNewSubModel(LDLModel *subModel, const char *dictName)
588 {
589 	std::ifstream closedStream;
590 	return initializeNewSubModel(subModel, dictName, closedStream);
591 }
592 
initializeNewSubModel(LDLModel * subModel,const char * dictName,std::ifstream & subModelStream)593 bool LDLModel::initializeNewSubModel(
594 	LDLModel *subModel,
595 	const char *dictName,
596 	std::ifstream &subModelStream)
597 {
598 	TCDictionary* subModelDict = getLoadedModels();
599 
600 	subModelDict->setObjectForKey(subModel, dictName);
601 	subModel->release();
602 	subModel->m_mainModel = m_mainModel;
603 	if (m_flags.loadingPart)
604 	{
605 		subModel->m_flags.part = true;
606 		subModel->m_flags.subPart = false;
607 //		subModel->m_flags.bfcCertify = BFCForcedOnState;
608 	}
609 	if (m_flags.loadingSubPart)
610 	{
611 		subModel->m_flags.part = false;
612 		subModel->m_flags.subPart = true;
613 	}
614 	if (m_flags.loadingPrimitive)
615 	{
616 		subModel->m_flags.primitive = true;
617 	}
618 	if (m_flags.loadingUnoffic)
619 	{
620 		subModel->m_flags.unofficial = true;
621 	}
622 	if (subModelStream.is_open() && !subModel->load(subModelStream))
623 	{
624 		subModelDict->removeObjectForKey(dictName);
625 		return false;
626 	}
627 	subModel->setName(dictName);
628 	return true;
629 }
630 
631 // NOTE: static function.
verifyLDrawDir(const char * value)632 bool LDLModel::verifyLDrawDir(const char *value)
633 {
634 	char currentDir[1024];
635 	bool retValue = false;
636 
637 	if (value && getcwd(currentDir, sizeof(currentDir)))
638 	{
639 		if (chdir(value) == 0)
640 		{
641 			if (chdir("parts") == 0 && chdir("..") == 0 && chdir("p") == 0)
642 			{
643 				retValue = true;
644 			}
645 		}
646 		if (chdir(currentDir) != 0)
647 		{
648 			debugPrintf("Error going back to original directory.\n");
649 			debugPrintf("currentDir before: <%s>\n", currentDir);
650 			if (getcwd(currentDir, sizeof(currentDir)) != NULL)
651 			{
652 				debugPrintf("currentDir  after: <%s>\n", currentDir);
653 			}
654 		}
655 	}
656 	return retValue;
657 }
658 
659 // NOTE: static function.
setFileCaseCallback(LDLFileCaseCallback value)660 void LDLModel::setFileCaseCallback(LDLFileCaseCallback value)
661 {
662 	fileCaseCallback = value;
663 	// If bool isn't 1 byte, then we can't support the case callback, so don't
664 	// even try.  This will trigger an error in LDLMainModel if the P and/or
665 	// PARTS directory inside the LDraw directory aren't capitalized.
666 	if (sizeof(bool) == sizeof(char))
667 	{
668 		if (sm_lDrawIni)
669 		{
670 			LDrawIniSetFileCaseCallback(fileCaseCallback);
671 		}
672 	}
673 }
674 
675 // NOTE: static function.
setLDrawDir(const char * value)676 void LDLModel::setLDrawDir(const char *value)
677 {
678 	if (value != sm_systemLDrawDir || !value)
679 	{
680 		delete[] sm_systemLDrawDir;
681 		if (value)
682 		{
683 			sm_systemLDrawDir = cleanedUpPath(value);
684 		}
685 		else
686 		{
687 			sm_systemLDrawDir = NULL;
688 		}
689 		if (sm_lDrawIni)
690 		{
691 			LDrawIniFree(sm_lDrawIni);
692 		}
693 		sm_lDrawIni = LDrawIniGet(sm_systemLDrawDir, NULL, NULL);
694 		if (sm_lDrawIni)
695 		{
696 			if (fileCaseCallback)
697 			{
698 				LDrawIniSetFileCaseCallback(fileCaseCallback);
699 			}
700 			if (!sm_systemLDrawDir)
701 			{
702 				sm_systemLDrawDir = copyString(sm_lDrawIni->LDrawDir);
703 			}
704 			if (sm_systemLDrawDir)
705 			{
706 				stripTrailingPathSeparators(sm_systemLDrawDir);
707 				LDrawIniComputeRealDirs(sm_lDrawIni, 1, 0, NULL);
708 			}
709 		}
710 	}
711 }
712 
initCheckDirs()713 void LDLModel::initCheckDirs()
714 {
715 	const char *value = getenv("LDRAWDIR");
716 
717 	if (value)
718 	{
719 		sm_checkDirs.push_back(value);
720 	}
721 #ifdef WIN32
722 	char buf[1024];
723 
724 	if (GetPrivateProfileString("LDraw", "BaseDirectory", "", buf, 1024,
725 		"ldraw.ini"))
726 	{
727 		buf[1023] = 0;
728 	}
729 	if (buf[0])
730 	{
731 		sm_checkDirs.push_back(buf);
732 	}
733 	sm_checkDirs.push_back("C:\\ldraw");
734 #else // WIN32
735 #ifdef __APPLE__
736 	const char *libDir = "/Library/ldraw";
737 	const char *homeDir = getenv("HOME");
738 
739 	if (homeDir != NULL)
740 	{
741 		char *homeLib = copyString(homeDir, strlen(libDir));
742 
743 		stripTrailingPathSeparators(homeLib);
744 		strcat(homeLib, libDir);
745 		sm_checkDirs.push_back(homeLib);
746 		delete[] homeLib;
747 	}
748 	sm_checkDirs.push_back(libDir);
749 	sm_checkDirs.push_back("/Applications/Bricksmith/LDraw");
750 #else // __APPLE__
751 	sm_checkDirs.push_back("/usr/share/ldraw");
752 	const char *homeDir = getenv("HOME");
753 	if (homeDir != NULL)
754 	{
755 		char *cleanHome = cleanedUpPath(homeDir);
756 		std::string homeLDraw;
757 
758 		stripTrailingPathSeparators(cleanHome);
759 		homeLDraw = cleanHome;
760 		delete[] cleanHome;
761 		homeLDraw += "/ldraw";
762 		sm_checkDirs.push_back(homeLDraw);
763 	}
764 #endif // __APPLE__
765 #endif // WIN32
766 	char *ldviewDir = copyString(TCUserDefaults::getAppPath());
767 	stripTrailingPathSeparators(ldviewDir);
768 	char *ldviewLDrawDir = copyString(ldviewDir, 10);
769 
770 	// LDView Dir/ldraw
771 	strcat(ldviewLDrawDir, "/ldraw");
772 	sm_checkDirs.push_back(ldviewLDrawDir);
773 #ifndef COCOA
774 	delete[] ldviewLDrawDir;
775 	char *ldviewParentDir = directoryFromPath(ldviewDir);
776 	stripTrailingPathSeparators(ldviewParentDir);
777 	ldviewLDrawDir = copyString(ldviewParentDir, 10);
778 	delete[] ldviewParentDir;
779 	// LDView Dir/../ldraw
780 	strcat(ldviewLDrawDir, "/ldraw");
781 	sm_checkDirs.push_back(ldviewLDrawDir);
782 #endif // COCOA
783 	delete[] ldviewDir;
784 	delete[] ldviewLDrawDir;
785 }
786 
787 // NOTE: static function.
lDrawDir(bool defaultValue)788 const char* LDLModel::lDrawDir(bool defaultValue /*= false*/)
789 {
790 	char *origValue = NULL;
791 
792 	if (defaultValue)
793 	{
794 		if (sm_defaultLDrawDir)
795 		{
796 			return sm_defaultLDrawDir;
797 		}
798 		origValue = copyString(sm_systemLDrawDir);
799 		if (sm_systemLDrawDir)
800 		{
801 			setLDrawDir(NULL);
802 		}
803 	}
804 	if (!sm_systemLDrawDir)
805 	{
806 		bool found = false;
807 
808 		if (sm_checkDirs.size() == 0)
809 		{
810 			initCheckDirs();
811 		}
812 		for (StringList::const_iterator it = sm_checkDirs.begin(); !found &&
813 			 it != sm_checkDirs.end(); ++it)
814 		{
815 			const char *dir = it->c_str();
816 
817 			if (verifyLDrawDir(dir))
818 			{
819 				setLDrawDir(dir);
820 				found = true;
821 			}
822 		}
823 		if (!found)
824 		{
825 			sm_systemLDrawDir = copyString("");
826 		}
827 	}
828 	if (defaultValue)
829 	{
830 		sm_defaultLDrawDir = copyString(sm_systemLDrawDir);
831 		setLDrawDir(origValue);
832 		delete[] origValue;
833 		return sm_defaultLDrawDir;
834 	}
835 	else
836 	{
837 		return sm_systemLDrawDir;
838 	}
839 }
840 
readComment(LDLCommentLine * commentLine)841 void LDLModel::readComment(LDLCommentLine *commentLine)
842 {
843 	std::string filename;
844 	std::string author;
845 
846 	if (commentLine->getMPDFilename(&filename))
847 	{
848 		replaceStringCharacter(&filename[0], '\\', '/');
849 		if (m_flags.mainModelLoaded)
850 		{
851 			if (m_activeLineCount == 0)
852 			{
853 				m_activeLineCount = commentLine->getLineNumber() - 1;
854 			}
855 			if (!getLoadedModels()->objectForKey(filename.c_str()))
856 			{
857 				LDLModel *subModel = new LDLModel;
858 
859 				if (commentLine->isDataMeta())
860 				{
861 					subModel->m_dataLine = TCObject::retain(commentLine);
862 				}
863 				subModel->setFilename(m_filename);
864 				initializeNewSubModel(subModel, filename.c_str());
865 				m_activeMPDModel = subModel;
866 				if (this == getMainModel())
867 				{
868 					getMainModel()->addMpdModel(subModel);
869 				}
870 			}
871 		}
872 		else
873 		{
874 			m_flags.mainModelLoaded = true;
875 			m_flags.mpd = true;
876 			if (this == getMainModel())
877 			{
878 				getMainModel()->addMpdModel(this);
879 				if (m_name == NULL)
880 				{
881 					setName(filename.c_str());
882 				}
883 			}
884 		}
885 	}
886 	else if (commentLine->isPartMeta())
887 	{
888 		if (commentLine->isOfficialPartMeta(true))
889 		{
890 			// Note that even if we decide it's a primitive, it can still be an
891 			// official file.
892 			m_flags.official = true;
893 		}
894 		// No matter what the comment says, remember that a primitive cannot
895 		// be a part, so if we found the file in the P directory, then by
896 		// definition it isn't a part.
897 		if (m_flags.mainModelLoaded)
898 		{
899 			if (m_activeMPDModel && !m_activeMPDModel->isPrimitive())
900 			{
901 				if (m_flags.loadingSubPart)
902 				{
903 					m_activeMPDModel->m_flags.subPart = true;
904 				}
905 				else if (!m_activeMPDModel->m_flags.subPart)
906 				{
907 					m_activeMPDModel->m_flags.part = true;
908 				}
909 			}
910 		}
911 		else if (!isPrimitive())
912 		{
913 			if (m_flags.loadingSubPart)
914 			{
915 				m_flags.subPart = true;
916 			}
917 			else if (!m_flags.subPart)
918 			{
919 				m_flags.part = true;
920 			}
921 			// This is now a part, so if we've already hit a BFC CERTIFY line
922 			// switch it to forced certify.
923 			if (m_flags.bfcCertify == BFCOnState)
924 			{
925 				m_flags.bfcCertify = BFCForcedOnState;
926 			}
927 		}
928 	}
929 	else if (commentLine->isPrimitiveMeta())
930 	{
931 		if (m_flags.mainModelLoaded)
932 		{
933 			m_activeMPDModel->m_flags.part = false;
934 			m_activeMPDModel->m_flags.subPart = false;
935 			m_activeMPDModel->m_flags.primitive = true;
936 		}
937 		else
938 		{
939 			m_flags.part = false;
940 			m_flags.subPart = false;
941 			m_flags.primitive = true;
942 		}
943 	}
944 	else if (commentLine->isNoShrinkMeta())
945 	{
946 		if (m_flags.mainModelLoaded)
947 		{
948 			if (m_activeMPDModel)
949 			{
950 				m_activeMPDModel->m_flags.noShrink = true;
951 			}
952 		}
953 		else
954 		{
955 			m_flags.noShrink = true;
956 		}
957 	}
958 	else if (commentLine->getAuthor(author))
959 	{
960 		if (!m_author)
961 		{
962 			m_author = copyString(author.c_str());
963 		}
964 	}
965 }
966 
967 /*
968 static char *myFgets(char *buf, int bufSize, FILE *file)
969 {
970 	int i;
971 
972 	for (i = 0; i < bufSize - 1; i++)
973 	{
974 		int char1 = fgetc(file);
975 
976 		if (feof(file))
977 		{
978 			buf[i] = 0;
979 			if (i > 0)
980 			{
981 				return buf;
982 			}
983 			else
984 			{
985 				return NULL;
986 			}
987 		}
988 		if (char1 == '\r' || char1 == '\n')
989 		{
990 			int char2 = fgetc(file);
991 
992 			buf[i] = '\n';
993 			buf[i + 1] = 0;
994 			if (!feof(file))
995 			{
996 				if (!(char1 == '\r' && char2 == '\n' ||
997 					char1 == '\n' && char2 == '\r'))
998 				{
999 					ungetc(char2, file);
1000 				}
1001 			}
1002 			return buf;
1003 		}
1004 		buf[i] = (char)char1;
1005 	}
1006 	buf[bufSize - 1] = 0;
1007 	return buf;
1008 }
1009 */
1010 
read(std::ifstream & stream)1011 bool LDLModel::read(std::ifstream &stream)
1012 {
1013 	std::string line;
1014 	int lineNumber = 1;
1015 	bool done = false;
1016 	bool retValue = true;
1017 
1018 	m_fileLines = new LDLFileLineArray;
1019 	while (!done && !getLoadCanceled())
1020 	{
1021 		if (std::getline(stream, line))
1022 		{
1023 			LDLFileLine *fileLine;
1024 
1025 			stripCRLF(&line[0]);
1026 			fileLine = LDLFileLine::initFileLine(this, line.c_str(), lineNumber);
1027 			lineNumber++;
1028 			m_fileLines->addObject(fileLine);
1029 			fileLine->release();
1030 			if (fileLine->getLineType() == LDLLineTypeComment)
1031 			{
1032 				// To a certain extent, this will actually parse the comment, but
1033 				// we really need to do some parsing prior to parsing the rest
1034 				// of the file.
1035 				readComment((LDLCommentLine *)fileLine);
1036 			}
1037 		}
1038 		else
1039 		{
1040 			if (m_activeLineCount == 0)
1041 			{
1042 				m_activeLineCount = m_fileLines->getCount();
1043 			}
1044 			done = true;
1045 		}
1046 	}
1047 	stream.close();
1048 	m_activeMPDModel = NULL;
1049 	return retValue && !getLoadCanceled();
1050 }
1051 
reportProgress(const char * message,float progress,bool mainOnly)1052 void LDLModel::reportProgress(const char *message, float progress,
1053 							  bool mainOnly)
1054 {
1055 	if (!mainOnly || this == m_mainModel)
1056 	{
1057 		bool loadCanceled;
1058 
1059 		TCProgressAlert::send("LDLModel", message, progress, &loadCanceled,
1060 			this);
1061 		if (loadCanceled)
1062 		{
1063 			cancelLoad();
1064 		}
1065 	}
1066 }
1067 
reportProgress(const wchar_t * message,float progress,bool mainOnly)1068 void LDLModel::reportProgress(const wchar_t *message, float progress,
1069 							  bool mainOnly)
1070 {
1071 	if (!mainOnly || this == m_mainModel)
1072 	{
1073 		bool loadCanceled;
1074 
1075 		TCProgressAlert::send("LDLModel", message, progress, &loadCanceled,
1076 			this);
1077 		if (loadCanceled)
1078 		{
1079 			cancelLoad();
1080 		}
1081 	}
1082 }
1083 
load(std::ifstream & stream,bool trackProgress)1084 bool LDLModel::load(std::ifstream &stream, bool trackProgress)
1085 {
1086 	bool retValue;
1087 
1088 	if (trackProgress)
1089 	{
1090 		reportProgress(LOAD_MESSAGE, 0.0f);
1091 	}
1092 	if (!read(stream))
1093 	{
1094 		if (trackProgress)
1095 		{
1096 			reportProgress(LOAD_MESSAGE, 1.0f);
1097 		}
1098 		return false;
1099 	}
1100 	if (trackProgress)
1101 	{
1102 		reportProgress(LOAD_MESSAGE, MAIN_READ_FRACTION);
1103 	}
1104 	retValue = parse();
1105 	if (trackProgress)
1106 	{
1107 		reportProgress(LOAD_MESSAGE, 1.0f);
1108 	}
1109 	return retValue;
1110 }
1111 
parseMPDMeta(int index,const char * filename)1112 int LDLModel::parseMPDMeta(int index, const char *filename)
1113 {
1114 	int i = index + 1;
1115 	int count = m_fileLines->getCount();
1116 
1117 	if (m_flags.mainModelParsed)
1118 	{
1119 		LDLModel *subModel;
1120 
1121 		for (i = index + 1; i < count; i++)
1122 		{
1123 			LDLFileLine *fileLine = (*m_fileLines)[i];
1124 
1125 			if (fileLine->getLineType() == LDLLineTypeComment &&
1126 				((LDLCommentLine *)fileLine)->getMPDFilename())
1127 			{
1128 				break;
1129 			}
1130 		}
1131 		count = i;
1132 		subModel = (LDLModel *)getLoadedModels()->objectForKey(filename);
1133 		if (subModel)
1134 		{
1135 			if (!subModel->m_fileLines)
1136 			{
1137 				subModel->m_fileLines = new LDLFileLineArray(count - index - 1);
1138 				for (i = index + 1; i < count; i++)
1139 				{
1140 					LDLFileLine *fileLine = (*m_fileLines)[i];
1141 
1142 					subModel->m_fileLines->addObject(fileLine);
1143 				}
1144 				subModel->m_activeLineCount = subModel->m_fileLines->getCount();
1145 				LDLModel *oldActiveMpd = m_mainModel->m_activeMPDModel;
1146 				m_mainModel->m_activeMPDModel = this;
1147 				if (!subModel->parse())
1148 				{
1149 					m_mainModel->m_activeMPDModel = oldActiveMpd;
1150 					return -1;
1151 				}
1152 				m_mainModel->m_activeMPDModel = oldActiveMpd;
1153 			}
1154 			else
1155 			{
1156 				reportError(LDLEMPDError, *(*m_fileLines)[index],
1157 					TCLocalStrings::get(_UC("LDLModelMpdAlreadyLoaded")));
1158 			}
1159 		}
1160 	}
1161 	else
1162 	{
1163 		m_flags.mainModelParsed = true;
1164 	}
1165 	return i - index - 1;
1166 }
1167 
parseBBoxIgnoreMeta(LDLCommentLine * commentLine)1168 int LDLModel::parseBBoxIgnoreMeta(LDLCommentLine *commentLine)
1169 {
1170 	if (commentLine->containsBBoxIgnoreCommand("BEGIN"))
1171 	{
1172 		m_flags.bboxIgnoreOn = true;
1173 		m_flags.bboxIgnoreBegun = true;
1174 	}
1175 	else if (commentLine->containsBBoxIgnoreCommand("NEXT"))
1176 	{
1177 		m_flags.bboxIgnoreOn = true;
1178 	}
1179 	else if (commentLine->containsBBoxIgnoreCommand("END"))
1180 	{
1181 		if (!m_flags.bboxIgnoreBegun)
1182 		{
1183 			reportWarning(LDLEMetaCommand, *commentLine,
1184 				TCLocalStrings::get(_UC("LDLModelBBoxEndUnexpected")));
1185 		}
1186 		m_flags.bboxIgnoreOn = false;
1187 		m_flags.bboxIgnoreBegun = false;
1188 	}
1189 	else
1190 	{
1191 		reportWarning(LDLEMetaCommand, *commentLine,
1192 			TCLocalStrings::get(_UC("LDLModelBBoxCommand")));
1193 	}
1194 	return 0;
1195 }
1196 
endTexmap(void)1197 void LDLModel::endTexmap(void)
1198 {
1199 	m_flags.texmapStarted = false;
1200 	m_flags.texmapNext = false;
1201 	TCObject::release(m_texmapImage);
1202 	m_texmapImage = NULL;
1203 }
1204 
openTexmap(const char * filename,std::ifstream & texmapStream,std::string & path)1205 bool LDLModel::openTexmap(
1206 	const char *filename,
1207 	std::ifstream &texmapStream,
1208 	std::string &path)
1209 {
1210 	if (!openSubModelNamed(filename, path, texmapStream, false, NULL, false))
1211 	{
1212 		LDLFindFileAlert *alert = new LDLFindFileAlert(filename);
1213 
1214 		TCAlertManager::sendAlert(alert, this);
1215 		if (alert->getFileFound())
1216 		{
1217 			path = alert->getFilename();
1218 			openStream(path.c_str(), texmapStream);
1219 		}
1220 		alert->release();
1221 	}
1222 	return texmapStream.is_open() && !texmapStream.fail();
1223 }
1224 
extractData()1225 void LDLModel::extractData()
1226 {
1227 	std::string base64Text;
1228 	int lineCount = m_fileLines->getCount();
1229 	for (int i = 0; i < lineCount; ++i)
1230 	{
1231 		LDLFileLine *fileLine = (*m_fileLines)[i];
1232 		if (fileLine->getLineType() == LDLLineTypeComment)
1233 		{
1234 			LDLCommentLine *commentLine = (LDLCommentLine *)fileLine;
1235 			if (commentLine->isDataRowMeta())
1236 			{
1237 				const char *line = commentLine->getLine();
1238 				std::string base64Line;
1239 				base64Line.reserve(strlen(line));
1240 				for (const char *lineChar = strchr(line, ':') + 1; *lineChar; ++lineChar)
1241 				{
1242 					if (isInBase64Charset(*lineChar))
1243 					{
1244 						base64Line += *lineChar;
1245 					}
1246 				}
1247 				base64Text += base64Line;
1248 			}
1249 		}
1250 	}
1251 	if (!base64Decode(base64Text, m_data))
1252 	{
1253 		reportError(LDLEMetaCommand, *m_dataLine,
1254 			TCLocalStrings::get(_UC("LDLModelDataDecodeError")));
1255 	}
1256 }
1257 
parseTexmapMeta(LDLCommentLine * commentLine)1258 int LDLModel::parseTexmapMeta(LDLCommentLine *commentLine)
1259 {
1260 	if (m_flags.texmapStarted && m_flags.texmapNext)
1261 	{
1262 		reportError(LDLEGeneral, *commentLine,
1263 			TCLocalStrings::get(_UC("LDLModelTexmapCommandAfterNext")));
1264 		endTexmap();
1265 	}
1266 	if (m_flags.texmapStarted)
1267 	{
1268 		if (commentLine->containsTexmapCommand("FALLBACK"))
1269 		{
1270 			if (m_flags.texmapFallback)
1271 			{
1272 				reportError(LDLEGeneral, *commentLine,
1273 					TCLocalStrings::get(_UC("LDLModelTexmapMultipleFallback")));
1274 			}
1275 			else
1276 			{
1277 				if (m_texmapFilename.size() > 0)
1278 				{
1279 					// If the texmap image load failed, we need to display the
1280 					// fallback geometry, so don't go into fallback mode.
1281 					m_flags.texmapFallback = true;
1282 				}
1283 			}
1284 		}
1285 		else if (commentLine->containsTexmapCommand("END"))
1286 		{
1287 			endTexmap();
1288 			if (!m_flags.texmapValid)
1289 			{
1290 				commentLine->setValid(false);
1291 			}
1292 		}
1293 		else
1294 		{
1295 			reportError(LDLEMetaCommand, *commentLine,
1296 				TCLocalStrings::get(_UC("LDLModelTexmapUnexpectedCommand")));
1297 		}
1298 	}
1299 	else
1300 	{
1301 		bool isStart = commentLine->containsTexmapCommand("START");
1302 		bool isNext = commentLine->containsTexmapCommand("NEXT");
1303 
1304 		if (isStart || isNext)
1305 		{
1306 			const char *typeName = commentLine->getWord(2);
1307 			int extraParams = 0;
1308 
1309 			if (typeName == NULL) {
1310 				reportError(LDLEParse, *commentLine,
1311 					TCLocalStrings::get(_UC("LDLModelTexmapParseError")));
1312 				commentLine->setValid(false);
1313 				return -1;
1314 			}
1315 			else if (strcmp(typeName, "PLANAR") == 0)
1316 			{
1317 				m_texmapType = LDLFileLine::TTPlanar;
1318 			}
1319 			else if (strcmp(typeName, "CYLINDRICAL") == 0)
1320 			{
1321 				m_texmapType = LDLFileLine::TTCylindrical;
1322 				extraParams = 1;
1323 			}
1324 			else if (strcmp(typeName, "SPHERICAL") == 0)
1325 			{
1326 				m_texmapType = LDLFileLine::TTSpherical;
1327 				extraParams = 2;
1328 			}
1329 			else
1330 			{
1331 				reportError(LDLEGeneral, *commentLine,
1332 					TCLocalStrings::get(_UC("LDLModelTexmapUnknownMethod")));
1333 				commentLine->setValid(false);
1334 				return -1;
1335 			}
1336 			if (commentLine->getNumWords() < 13 + extraParams)
1337 			{
1338 				reportError(LDLEParse, *commentLine,
1339 					TCLocalStrings::get(_UC("LDLModelTexmapParseError")));
1340 				commentLine->setValid(false);
1341 				return -1;
1342 			}
1343 			for (int i = 0; i < 3; i++)
1344 			{
1345 				for (int j = 0; j < 3; j++)
1346 				{
1347 					m_texmapPoints[i][j] =
1348 						(TCFloat)atof(commentLine->getWord(3 + i * 3 + j));
1349 				}
1350 			}
1351 			for (int i = 0; i < extraParams; ++i)
1352 			{
1353 				m_texmapExtra[i] = (TCFloat)atof(commentLine->getWord(12 + i));
1354 			}
1355 			m_flags.texmapStarted = true;
1356 			m_flags.texmapFallback = false;
1357 			m_flags.texmapValid = true;
1358 			if (isNext)
1359 			{
1360 				m_flags.texmapNext = true;
1361 			}
1362 			// Only load the texture map file if textures are enabled.  Still
1363 			// perform the parsing of everything else either way, but don't
1364 			// load the file when they're disabled.
1365 			if (m_mainModel->getTexmaps())
1366 			{
1367 				std::string filename = commentLine->getWord(12 + extraParams);
1368 				std::string pathFilename = std::string("textures/") + filename;
1369 				bool delayedLoad = false;
1370 				std::string path;
1371 				TCDictionary* subModelDict = getLoadedModels();
1372 				LDLModel *texmapModel = (LDLModel*)subModelDict->objectForKey(filename.c_str());
1373 				if (texmapModel != NULL)
1374 				{
1375 					LDLModel *activeMpd = m_mainModel->m_activeMPDModel;
1376 					if (activeMpd != NULL && activeMpd->m_filename != NULL)
1377 					{
1378 						char *baseName = filenameFromPath(activeMpd->m_filename);
1379 						combinePathParts(path, baseName, ":", filename);
1380 						delete[] baseName;
1381 					}
1382 					else
1383 					{
1384 						combinePathParts(path, "MPD:", filename);
1385 					}
1386 					if (texmapModel->m_data.empty())
1387 					{
1388 						if (m_mpdTexmapModels == NULL)
1389 						{
1390 							m_mpdTexmapModels = new LDLModelArray;
1391 							m_mpdTexmapLines = new LDLCommentLineArray;
1392 							m_mpdTexmapImages = new TCImageArray;
1393 						}
1394 						m_mpdTexmapModels->addObject(texmapModel);
1395 						m_mpdTexmapLines->addObject(commentLine);
1396 						m_mainModel->setHaveMpdTexmaps();
1397 						delayedLoad = true;
1398 					}
1399 				}
1400 				std::ifstream texmapStream;
1401 				if (texmapModel == NULL)
1402 				{
1403 					if (!openTexmap(pathFilename.c_str(), texmapStream, path))
1404 					{
1405 						openTexmap(filename.c_str(), texmapStream, path);
1406 					}
1407 				}
1408 				if ((texmapStream.is_open() && !texmapStream.fail()) ||
1409 					texmapModel != NULL)
1410 				{
1411 					TCImage *image = new TCImage;
1412 					bool loaded = false;
1413 
1414 					image->setLineAlignment(4);
1415 					if (delayedLoad)
1416 					{
1417 						m_mpdTexmapImages->addObject(image);
1418 					}
1419 					else if (texmapModel != NULL)
1420 					{
1421 						loaded = image->loadData(&texmapModel->m_data[0],
1422 							texmapModel->m_data.size());
1423 					}
1424 					else
1425 					{
1426 						texmapStream.close();
1427 						// Image loading from a stream would require loading the
1428 						// entire image into memory and then doing an in-memory
1429 						// load. So close the stream and use the full path that
1430 						// was used to open the stream to load the image.
1431 						loaded = image->loadFile(path.c_str());
1432 					}
1433 					if (loaded || delayedLoad)
1434 					{
1435 						char *cleanPath = cleanedUpPath(path.c_str());
1436 
1437 						m_texmapImage = image;
1438 						convertStringToLower(cleanPath);
1439 						// Since the path is going to be used as a key in a map,
1440 						// we want it to be consistent.  Hence, cleaning it up
1441 						// and making it all lower case.  Files in LDraw cannot
1442 						// have case-sensitive filenames, so this should be
1443 						// kosher.
1444 						m_texmapFilename = cleanPath;
1445 						delete[] cleanPath;
1446 					}
1447 					else
1448 					{
1449 						image->release();
1450 						reportError(LDLEMetaCommand, *commentLine,
1451 							TCLocalStrings::get(_UC("LDLModelTexmapImageLoadError")));
1452 					}
1453 				}
1454 				else
1455 				{
1456 					reportError(LDLEMetaCommand, *commentLine,
1457 						TCLocalStrings::get(_UC("LDLModelTexmapFileNotFound")));
1458 				}
1459 				if (m_texmapImage == NULL)
1460 				{
1461 					commentLine->setValid(false);
1462 					m_flags.texmapValid = false;
1463 				}
1464 			}
1465 		}
1466 		else
1467 		{
1468 			reportError(LDLEMetaCommand, *commentLine,
1469 				TCLocalStrings::get(_UC("LDLModelTexmapUnexpectedCommand")));
1470 		}
1471 	}
1472 	return 0;
1473 }
1474 
loadMpdTexmaps(void)1475 int LDLModel::loadMpdTexmaps(void)
1476 {
1477 	if (m_mpdTexmapModels == NULL)
1478 	{
1479 		return 0;
1480 	}
1481 	int count = m_mpdTexmapModels->getCount();
1482 	for (int i = 0; i < count; ++i)
1483 	{
1484 		LDLModel *texmapModel = (*m_mpdTexmapModels)[i];
1485 		TCImage *image = (*m_mpdTexmapImages)[i];
1486 		if (!image->loadData(&texmapModel->m_data[0],
1487 			texmapModel->m_data.size()))
1488 		{
1489 			LDLCommentLine *commentLine = (*m_mpdTexmapLines)[i];
1490 			reportError(LDLEMetaCommand, *commentLine,
1491 				TCLocalStrings::get(_UC("LDLModelTexmapImageLoadError")));
1492 		}
1493 	}
1494 	m_mpdTexmapModels->release();
1495 	m_mpdTexmapModels = NULL;
1496 	m_mpdTexmapLines->release();
1497 	m_mpdTexmapLines = NULL;
1498 	m_mpdTexmapImages->release();
1499 	m_mpdTexmapImages = NULL;
1500 	return 0;
1501 }
1502 
parseBFCMeta(LDLCommentLine * commentLine)1503 int LDLModel::parseBFCMeta(LDLCommentLine *commentLine)
1504 {
1505 	if (m_flags.bfcInvertNext)
1506 	{
1507 		reportError(LDLEBFCError, *commentLine,
1508 			TCLocalStrings::get(_UC("LDLModelBfcInvert")));
1509 		m_flags.bfcInvertNext = false;
1510 	}
1511 	if (m_flags.bfcCertify == BFCUnknownState)
1512 	{
1513 		if (commentLine->containsBFCCommand("NOCERTIFY"))
1514 		{
1515 			m_flags.bfcCertify = BFCOffState;
1516 			if (m_flags.started)
1517 			{
1518 				reportError(LDLEBFCError, *commentLine,
1519 					TCLocalStrings::get(_UC("LDLModelBfcNoCertFirst")));
1520 			}
1521 		}
1522 		else
1523 		{
1524 			if (m_flags.started)
1525 			{
1526 				m_flags.bfcCertify = BFCOffState;
1527 				reportError(LDLEBFCError, *commentLine,
1528 					TCLocalStrings::get(_UC("LDLModelBfcFirst")));
1529 			}
1530 			else
1531 			{
1532 				// Unless a NOCERTIFY is present, CERTIFY gets turned on by
1533 				// default for any BFC command.
1534 				if (m_flags.part || m_flags.subPart)
1535 				{
1536 					// BFC certified parts force BFC to be on, even if their
1537 					// parent models don't have BFC certification.
1538 					m_flags.bfcCertify = BFCForcedOnState;
1539 				}
1540 				else
1541 				{
1542 					m_flags.bfcCertify = BFCOnState;
1543 				}
1544 				m_flags.bfcClip = true;
1545 			}
1546 		}
1547 	}
1548 	else
1549 	{
1550 		if (commentLine->containsBFCCommand("CERTIFY"))
1551 		{
1552 			if (getBFCOn())
1553 			{
1554 				reportWarning(LDLEBFCWarning, *commentLine,
1555 					TCLocalStrings::get(_UC("LDLModelBfcCertNotFirst")));
1556 			}
1557 			else
1558 			{
1559 				reportError(LDLEBFCError, *commentLine,
1560 					TCLocalStrings::get(_UC("LDLModelBfcCertNoCert")));
1561 			}
1562 		}
1563 		// Not else if below, because each BFC command could potentially
1564 		// have both certify AND nocertify.
1565 		if (commentLine->containsBFCCommand("NOCERTIFY"))
1566 		{
1567 			if (getBFCOn())
1568 			{
1569 				reportError(LDLEBFCError, *commentLine,
1570 					TCLocalStrings::get(_UC("LDLModelBfcNoCertCert")));
1571 			}
1572 			else
1573 			{
1574 				reportWarning(LDLEBFCWarning, *commentLine,
1575 					TCLocalStrings::get(_UC("LDLModelBfcNoCertMulti")));
1576 			}
1577 		}
1578 	}
1579 	if (getBFCOn())
1580 	{
1581 		if (commentLine->containsBFCCommand("CLIP"))
1582 		{
1583 			if (commentLine->containsBFCCommand("NOCLIP"))
1584 			{
1585 				reportError(LDLEBFCError, *commentLine,
1586 					TCLocalStrings::get(_UC("LDLModelBfcClipNoClip")));
1587 			}
1588 			else
1589 			{
1590 				m_flags.bfcClip = true;
1591 			}
1592 		}
1593 		else if (commentLine->containsBFCCommand("NOCLIP"))
1594 		{
1595 			m_flags.bfcClip = false;
1596 		}
1597 		if (commentLine->containsBFCCommand("CCW"))
1598 		{
1599 			if (commentLine->containsBFCCommand("CW"))
1600 			{
1601 				reportError(LDLEBFCError, *commentLine,
1602 					TCLocalStrings::get(_UC("LDLModelBfcCwCcw")));
1603 			}
1604 			else
1605 			{
1606 				m_flags.bfcWindingCCW = true;
1607 			}
1608 		}
1609 		else if (commentLine->containsBFCCommand("CW"))
1610 		{
1611 			m_flags.bfcWindingCCW = false;
1612 		}
1613 		if (commentLine->containsBFCCommand("INVERTNEXT"))
1614 		{
1615 			m_flags.bfcInvertNext = true;
1616 		}
1617 	}
1618 	else
1619 	{
1620 		if (commentLine->containsBFCCommand("CLIP") ||
1621 			commentLine->containsBFCCommand("NOCLIP") ||
1622 			commentLine->containsBFCCommand("CW") ||
1623 			commentLine->containsBFCCommand("CCW") ||
1624 			commentLine->containsBFCCommand("INVERTNEXT"))
1625 		{
1626 			reportError(LDLEBFCError, *commentLine,
1627 				TCLocalStrings::get(_UC("LDLModelBfcAfterNoCert")));
1628 		}
1629 	}
1630 	return 0;
1631 }
1632 
parseComment(int index,LDLCommentLine * commentLine)1633 int LDLModel::parseComment(int index, LDLCommentLine *commentLine)
1634 {
1635 	std::string filename;
1636 
1637 	if (commentLine->getMPDFilename(&filename))
1638 	{
1639 		replaceStringCharacter(&filename[0], '\\', '/');
1640 		return parseMPDMeta(index, filename.c_str());
1641 	}
1642 	else if (commentLine->isBFCMeta())
1643 	{
1644 		return parseBFCMeta(commentLine);
1645 	}
1646 	else if (commentLine->isPartMeta())
1647 	{
1648 		m_stepIndices.push_back(index);
1649 		return 0;
1650 	}
1651 	else if (commentLine->isLDViewMeta())
1652 	{
1653 		if (commentLine->isBBoxIgnoreMeta())
1654 		{
1655 			return parseBBoxIgnoreMeta(commentLine);
1656 		}
1657 		else
1658 		{
1659 			reportWarning(LDLEMetaCommand, *commentLine,
1660 				TCLocalStrings::get(_UC("LDLModelUnknownLDViewMeta")));
1661 		}
1662 	}
1663 	else if (commentLine->isTexmapMeta())
1664 	{
1665 		return parseTexmapMeta(commentLine);
1666 	}
1667 	else if (index == 0)
1668 	{
1669 		delete[] m_description;
1670 		m_description = copyString(&commentLine->getLine()[1]);
1671 		stripLeadingWhitespace(m_description);
1672 		stripTrailingWhitespace(m_description);
1673 	}
1674 	return 0;
1675 }
1676 
1677 /*
1678 void LDLModel::processModelLine(LDLModelLine *modelLine)
1679 {
1680 	m_flags.started = true;
1681 }
1682 */
1683 
parse(void)1684 bool LDLModel::parse(void)
1685 {
1686 	if (m_fileLines)
1687 	{
1688 		if (m_dataLine != NULL)
1689 		{
1690 			extractData();
1691 			// Note: even if extractData fails, that does NOT mean that we
1692 			// failed to parse the file.
1693 			return true;
1694 		}
1695 		int i;
1696 		int count = m_fileLines->getCount();
1697 
1698 		// ********************************************************************
1699 		// NOTE: This for loop does a number of things that aren't normally
1700 		// done (at least by me).  In one place (when a line needs to be
1701 		// replaced by new ones), it inserts new items in the array just after
1702 		// the current spot, changes count, and uses continue.  In another place
1703 		// (when it sees an MPD secondary file), it increases i to skip over all
1704 		// the lines in that secondary file (they get parsed separately).
1705 		// ********************************************************************
1706 		for (i = 0; i < count && !getLoadCanceled(); i++)
1707 		{
1708 			LDLFileLine *fileLine = (*m_fileLines)[i];
1709 			bool checkInvertNext = true;
1710 
1711 			if (fileLine->isActionLine())
1712 			{
1713 				LDLActionLine *actionLine = (LDLActionLine *)fileLine;
1714 
1715 				m_flags.started = true;
1716 				actionLine->setBFCSettings(m_flags.bfcCertify, m_flags.bfcClip,
1717 					m_flags.bfcWindingCCW, m_flags.bfcInvertNext);
1718 				if (m_flags.bboxIgnoreOn)
1719 				{
1720 					actionLine->setBBoxIgnore(true);
1721 					m_mainModel->setBBoxIgnoreUsed(true);
1722 				}
1723 				if (!m_flags.bboxIgnoreBegun)
1724 				{
1725 					m_flags.bboxIgnoreOn = false;
1726 				}
1727 				if (m_flags.texmapStarted)
1728 				{
1729 					if (m_flags.texmapFallback)
1730 					{
1731 						actionLine->setTexmapFallback();
1732 					}
1733 					else
1734 					{
1735 						actionLine->setTexmapSettings(m_texmapType,
1736 							m_texmapFilename, m_texmapImage, m_texmapPoints,
1737 							m_texmapExtra);
1738 						if (m_flags.texmapNext)
1739 						{
1740 							endTexmap();
1741 						}
1742 					}
1743 				}
1744 			}
1745 			else
1746 			{
1747 				checkInvertNext = false;
1748 				if (fileLine->getLineType() == LDLLineTypeComment &&
1749 					m_flags.texmapStarted && !m_flags.texmapFallback)
1750 				{
1751 					((LDLCommentLine *)fileLine)->setTexmapSettings(
1752 						m_texmapType, m_texmapFilename, m_texmapImage,
1753 						m_texmapPoints, m_texmapExtra);
1754 				}
1755 			}
1756 			if (fileLine->parse())
1757 			{
1758 				if (fileLine->isValid())
1759 				{
1760 					if (fileLine->getError())
1761 					{
1762 						sendAlert(fileLine->getError());
1763 					}
1764 				}
1765 				else
1766 				{
1767 					LDLFileLineArray *replacementLines =
1768 						fileLine->getReplacementLines();
1769 
1770 					if (replacementLines)
1771 					{
1772 						int replacementCount = replacementLines->getCount();
1773 						int j;
1774 
1775 						fileLine->setReplaced(true);
1776 						for (j = 0; j < replacementCount; j++)
1777 						{
1778 							m_fileLines->insertObject((*replacementLines)[j],
1779 								i + 1);
1780 						}
1781 						// Note that if we do get here, we haven't gotten past
1782 						// m_activeLineCount, because that parsing gets done in
1783 						// the secondary files themselves.
1784 						m_activeLineCount += replacementCount;
1785 						count += replacementCount;
1786 						if (fileLine->getError())
1787 						{
1788 							sendAlert(fileLine->getError());
1789 						}
1790 						replacementLines->release();
1791 						// ****************************************************
1792 						// Note the use of continue below.  I really shy away
1793 						// from using it, but I'm goint to do so here.
1794 						// ****************************************************
1795 						continue;
1796 						// ****************************************************
1797 						// ****************************************************
1798 					}
1799 					else
1800 					{
1801 						sendAlert(fileLine->getError());
1802 					}
1803 				}
1804 			}
1805 			else
1806 			{
1807 				sendAlert(fileLine->getError());
1808 			}
1809 			switch (fileLine->getLineType())
1810 			{
1811 			case LDLLineTypeComment:
1812 				{
1813 					int skippedLines = parseComment(i,
1814 						(LDLCommentLine *)fileLine);
1815 
1816 					checkInvertNext = false;
1817 					if (skippedLines >= 0)
1818 					{
1819 						// ****************************************************
1820 						// Note that I increment i below to skip over the lines
1821 						// in the MPD secondary file I just encountered.  (The
1822 						// parseComment function will only return a number
1823 						// greater than 0 if it found an MPD secondary file.
1824 						// ****************************************************
1825 						i += skippedLines;
1826 						// ****************************************************
1827 						// ****************************************************
1828 					}
1829 				}
1830 				break;
1831 			case LDLLineTypeModel:
1832 				{
1833 					LDLModel *model = ((LDLModelLine *)fileLine)->getModel();
1834 
1835 					if (model)
1836 					{
1837 						model->calcBoundingBox();
1838 					}
1839 					if (model != NULL && model->hasStuds())
1840 					{
1841 						m_flags.hasStuds = true;
1842 					}
1843 					m_flags.bfcInvertNext = false;
1844 				}
1845 				break;
1846 			default:
1847 				break;
1848 			}
1849 			if (checkInvertNext && m_flags.bfcInvertNext)
1850 			{
1851 				reportError(LDLEBFCError, *fileLine,
1852 					TCLocalStrings::get(_UC("LDLModelBfcInvert")));
1853 				m_flags.bfcInvertNext = false;
1854 			}
1855 			fileLine->setStepIndex((int)m_stepIndices.size());
1856 			reportProgress(LOAD_MESSAGE, (float)i / (float)m_activeLineCount *
1857 				(1.0f - MAIN_READ_FRACTION) + MAIN_READ_FRACTION);
1858 		}
1859 		return !getLoadCanceled();
1860 	}
1861 	else
1862 	{
1863 		// This is in an MPD, and has not yet been fully initialized.
1864 		return true;
1865 	}
1866 }
1867 
cancelLoad(void)1868 void LDLModel::cancelLoad(void)
1869 {
1870 	m_mainModel->cancelLoad();
1871 }
1872 
getLoadCanceled(void)1873 bool LDLModel::getLoadCanceled(void)
1874 {
1875 	return m_mainModel->getLoadCanceled();
1876 }
1877 
sendAlert(LDLError * alert)1878 void LDLModel::sendAlert(LDLError *alert)
1879 {
1880 	if (alert)
1881 	{
1882 		TCAlertManager::sendAlert(alert, this);
1883 		if (alert->getLoadCanceled())
1884 		{
1885 			cancelLoad();
1886 		}
1887 	}
1888 }
1889 
1890 /*
1891 void LDLModel::sendAlert(LDLErrorType type, LDLAlertLevel level,
1892 						 const char* format, va_list argPtr)
1893 {
1894 	LDLError *alert;
1895 
1896 	alert = newError(type, format, argPtr);
1897 	alert->setLevel(level);
1898 	sendAlert(alert);
1899 	alert->release();
1900 }
1901 */
1902 
sendAlert(LDLErrorType type,LDLAlertLevel level,CUCSTR format,va_list argPtr)1903 void LDLModel::sendAlert(LDLErrorType type, LDLAlertLevel level, CUCSTR format,
1904 						 va_list argPtr)
1905 {
1906 	LDLError *alert;
1907 
1908 	alert = newError(type, format, argPtr);
1909 	alert->setLevel(level);
1910 	sendAlert(alert);
1911 	alert->release();
1912 }
1913 
1914 /*
1915 void LDLModel::sendAlert(LDLErrorType type, LDLAlertLevel level,
1916 						 const LDLFileLine &fileLine, const char* format,
1917 						 va_list argPtr)
1918 {
1919 	LDLError *alert;
1920 
1921 	alert = newError(type, fileLine, format, argPtr);
1922 	alert->setLevel(level);
1923 	sendAlert(alert);
1924 	alert->release();
1925 }
1926 */
1927 
sendAlert(LDLErrorType type,LDLAlertLevel level,const LDLFileLine & fileLine,CUCSTR format,va_list argPtr)1928 void LDLModel::sendAlert(LDLErrorType type, LDLAlertLevel level,
1929 						 const LDLFileLine &fileLine, CUCSTR format,
1930 						 va_list argPtr)
1931 {
1932 	LDLError *alert;
1933 
1934 	alert = newError(type, fileLine, format, argPtr);
1935 	alert->setLevel(level);
1936 	sendAlert(alert);
1937 	alert->release();
1938 }
1939 
1940 /*
1941 void LDLModel::reportError(LDLErrorType type, const LDLFileLine &fileLine,
1942 						   const char* format, ...)
1943 {
1944 	va_list argPtr;
1945 
1946 	va_start(argPtr, format);
1947 	sendAlert(type, LDLAError, fileLine, format, argPtr);
1948 	va_end(argPtr);
1949 }
1950 */
1951 
reportError(LDLErrorType type,const LDLFileLine & fileLine,CUCSTR format,...)1952 void LDLModel::reportError(LDLErrorType type, const LDLFileLine &fileLine,
1953 						   CUCSTR format, ...)
1954 {
1955 	va_list argPtr;
1956 
1957 	va_start(argPtr, format);
1958 	sendAlert(type, LDLAError, fileLine, format, argPtr);
1959 	va_end(argPtr);
1960 }
1961 
1962 /*
1963 void LDLModel::reportWarning(LDLErrorType type, const LDLFileLine &fileLine,
1964 							 const char* format, ...)
1965 {
1966 	va_list argPtr;
1967 
1968 	va_start(argPtr, format);
1969 	sendAlert(type, LDLAWarning, fileLine, format, argPtr);
1970 	va_end(argPtr);
1971 }
1972 */
1973 
reportWarning(LDLErrorType type,const LDLFileLine & fileLine,CUCSTR format,...)1974 void LDLModel::reportWarning(LDLErrorType type, const LDLFileLine &fileLine,
1975 							 CUCSTR format, ...)
1976 {
1977 	va_list argPtr;
1978 
1979 	va_start(argPtr, format);
1980 	sendAlert(type, LDLAWarning, fileLine, format, argPtr);
1981 	va_end(argPtr);
1982 }
1983 
1984 /*
1985 void LDLModel::reportError(LDLErrorType type, const char* format, ...)
1986 {
1987 	va_list argPtr;
1988 
1989 	va_start(argPtr, format);
1990 	sendAlert(type, LDLAError, format, argPtr);
1991 	va_end(argPtr);
1992 }
1993 */
1994 
reportError(LDLErrorType type,CUCSTR format,...)1995 void LDLModel::reportError(LDLErrorType type, CUCSTR format, ...)
1996 {
1997 	va_list argPtr;
1998 
1999 	va_start(argPtr, format);
2000 	sendAlert(type, LDLAError, format, argPtr);
2001 	va_end(argPtr);
2002 }
2003 
2004 /*
2005 void LDLModel::reportWarning(LDLErrorType type, const char* format, ...)
2006 {
2007 	va_list argPtr;
2008 
2009 	va_start(argPtr, format);
2010 	sendAlert(type, LDLAWarning, format, argPtr);
2011 	va_end(argPtr);
2012 }
2013 */
2014 
reportWarning(LDLErrorType type,CUCSTR format,...)2015 void LDLModel::reportWarning(LDLErrorType type, CUCSTR format, ...)
2016 {
2017 	va_list argPtr;
2018 
2019 	va_start(argPtr, format);
2020 	sendAlert(type, LDLAWarning, format, argPtr);
2021 	va_end(argPtr);
2022 }
2023 
getLoadedModels(void)2024 TCDictionary *LDLModel::getLoadedModels(void)
2025 {
2026 	return (m_mainModel->getLoadedModels());
2027 }
2028 
print(int indent) const2029 void LDLModel::print(int indent) const
2030 {
2031 	indentPrintf(indent, "LDLModel");
2032 	if (m_flags.part)
2033 	{
2034 		printf(" (Part)");
2035 	}
2036 	if (m_flags.subPart)
2037 	{
2038 		printf(" (SubPart)");
2039 	}
2040 	switch (m_flags.bfcCertify)
2041 	{
2042 	case BFCOffState:
2043 		printf(" (BFC Off)");
2044 		break;
2045 	case BFCOnState:
2046 		printf(" (BFC On)");
2047 		break;
2048 	case BFCForcedOnState:
2049 		printf(" (BFC Forced On)");
2050 		break;
2051 	default:
2052 		break;
2053 	}
2054 	printf(": ");
2055 	if (m_filename)
2056 	{
2057 		printf("%s", m_filename);
2058 	}
2059 	else
2060 	{
2061 		printf("<Unknown>");
2062 	}
2063 	printf("\n");
2064 	if (m_fileLines)
2065 	{
2066 		int i;
2067 		int count = m_fileLines->getCount();
2068 
2069 		for (i = 0; i < count; i++)
2070 		{
2071 			(*m_fileLines)[i]->print(indent + 1);
2072 		}
2073 	}
2074 }
2075 
getLowResStuds(void) const2076 bool LDLModel::getLowResStuds(void) const
2077 {
2078 	return m_mainModel->getLowResStuds();
2079 }
2080 
newError(LDLErrorType type,const LDLFileLine & fileLine,CUCSTR format,va_list argPtr)2081 LDLError *LDLModel::newError(LDLErrorType type, const LDLFileLine &fileLine,
2082 							 CUCSTR format, va_list argPtr)
2083 {
2084 	UCCHAR message[1024];
2085 	UCCHAR** components;
2086 	int componentCount;
2087 	LDLError *error = NULL;
2088 
2089 	vsucprintf(message, COUNT_OF(message), format, argPtr);
2090 	stripCRLF(message);
2091 	components = componentsSeparatedByString(message, _UC("\n"),
2092 		componentCount);
2093 	if (componentCount > 1)
2094 	{
2095 		int i;
2096 #ifdef TC_NO_UNICODE
2097 		TCStringArray *extraInfo = new TCStringArray(componentCount - 1);
2098 
2099 		*strchr(message, '\n') = 0;
2100 		for (i = 1; i < componentCount; i++)
2101 		{
2102 			extraInfo->addString(components[i]);
2103 		}
2104 		error = new LDLError(type, message, m_filename, fileLine,
2105 			fileLine.getFormattedLine(), fileLine.getLineNumber(), extraInfo);
2106 		extraInfo->release();
2107 #else // TC_NO_UNICODE
2108 		ucstringVector extraInfo;
2109 		*wcschr(message, '\n') = 0;
2110 		extraInfo.reserve(componentCount - 1);
2111 		for (i = 1; i < componentCount; i++)
2112 		{
2113 			extraInfo.push_back(components[i]);
2114 		}
2115 		error = new LDLError(type, message, m_filename, fileLine,
2116 			fileLine.getFormattedLine(), fileLine.getLineNumber(), extraInfo);
2117 #endif // TC_NO_UNICODE
2118 	}
2119 	else
2120 	{
2121 		error = new LDLError(type, message, m_filename, fileLine,
2122 			fileLine.getFormattedLine(), fileLine.getLineNumber());
2123 	}
2124 	deleteStringArray(components, componentCount);
2125 	return error;
2126 }
2127 
newError(LDLErrorType type,const LDLFileLine & fileLine,CUCSTR format,...)2128 LDLError *LDLModel::newError(LDLErrorType type, const LDLFileLine &fileLine,
2129 							 CUCSTR format, ...)
2130 {
2131 	va_list argPtr;
2132 	LDLError *retValue;
2133 
2134 	va_start(argPtr, format);
2135 	retValue = newError(type, fileLine, format, argPtr);
2136 	va_end(argPtr);
2137 	return retValue;
2138 }
2139 
newError(LDLErrorType type,CUCSTR format,va_list argPtr)2140 LDLError *LDLModel::newError(LDLErrorType type, CUCSTR format, va_list argPtr)
2141 {
2142 	UCCHAR message[1024];
2143 	UCCHAR** components;
2144 	int componentCount;
2145 	LDLError *error = NULL;
2146 
2147 	vsucprintf(message, COUNT_OF(message), format, argPtr);
2148 	stripCRLF(message);
2149 	components = componentsSeparatedByString(message, _UC("\n"),
2150 		componentCount);
2151 	if (componentCount > 1)
2152 	{
2153 		int i;
2154 #ifdef TC_NO_UNICODE
2155 		TCStringArray *extraInfo = new TCStringArray(componentCount - 1);
2156 
2157 		*strchr(message, '\n') = 0;
2158 		for (i = 1; i < componentCount; i++)
2159 		{
2160 			extraInfo->addString(components[i]);
2161 		}
2162 		error = new LDLError(type, message, m_filename, NULL, NULL, -1,
2163 			extraInfo);
2164 		extraInfo->release();
2165 #else // TC_NO_UNICODE
2166 		ucstringVector extraInfo;
2167 		*wcschr(message, '\n') = 0;
2168 		extraInfo.reserve(componentCount - 1);
2169 		for (i = 1; i < componentCount; i++)
2170 		{
2171 			extraInfo.push_back(components[i]);
2172 		}
2173 		error = new LDLError(type, message, m_filename, NULL, NULL, -1,
2174 			extraInfo);
2175 #endif // TC_NO_UNICODE
2176 	}
2177 	else
2178 	{
2179 		error = new LDLError(type, message, m_filename, NULL, NULL, -1);
2180 	}
2181 	deleteStringArray(components, componentCount);
2182 	return error;
2183 }
2184 
newError(LDLErrorType type,CUCSTR format,...)2185 LDLError *LDLModel::newError(LDLErrorType type, CUCSTR format, ...)
2186 {
2187 	va_list argPtr;
2188 	LDLError *retValue;
2189 
2190 	va_start(argPtr, format);
2191 	retValue = newError(type, format, argPtr);
2192 	va_end(argPtr);
2193 	return retValue;
2194 }
2195 
scanPoints(TCObject * scanner,LDLScanPointCallback scanPointCallback,const TCFloat * matrix,int step,bool watchBBoxIgnore) const2196 void LDLModel::scanPoints(
2197 	TCObject *scanner,
2198 	LDLScanPointCallback scanPointCallback,
2199 	const TCFloat *matrix,
2200 	int step /*= -1*/,
2201 	bool watchBBoxIgnore /*= false*/) const
2202 {
2203 	if (this != m_mainModel && isPart() && m_mainModel->getBoundingBoxesOnly()
2204 		&& m_flags.haveBoundingBox)
2205 	{
2206 		TCVector boxPoints[8];
2207 		TCVector point;
2208 		int i;
2209 
2210 		boxPoints[0] = m_boundingMin;
2211 		boxPoints[4] = m_boundingMax;
2212 		// Bottom square
2213 		boxPoints[1] = boxPoints[0];
2214 		boxPoints[1][0] = boxPoints[4][0];
2215 		boxPoints[2] = boxPoints[0];
2216 		boxPoints[2][1] = boxPoints[4][1];
2217 		boxPoints[3] = boxPoints[4];
2218 		boxPoints[3][2] = boxPoints[0][2];
2219 		// Top square
2220 		boxPoints[5] = boxPoints[4];
2221 		boxPoints[5][0] = boxPoints[0][0];
2222 		boxPoints[6] = boxPoints[4];
2223 		boxPoints[6][1] = boxPoints[0][1];
2224 		boxPoints[7] = boxPoints[0];
2225 		boxPoints[7][2] = boxPoints[4][2];
2226 		for (i = 0; i < 8; i++)
2227 		{
2228 			boxPoints[i].transformPoint(matrix, point);
2229 			((*scanner).*scanPointCallback)(point, NULL);
2230 		}
2231 	}
2232 	else
2233 	{
2234 		int curStep = 0;
2235 		bool emptyStep = true;
2236 
2237 		for (int i = 0; i < m_activeLineCount; i++)
2238 		{
2239 			LDLFileLine *fileLine = (*m_fileLines)[i];
2240 			if (step >= 0 && fileLine->getLineType() == LDLLineTypeComment)
2241 			{
2242 				LDLCommentLine *commentLine = (LDLCommentLine *)fileLine;
2243 
2244 				if (commentLine->isStepMeta() && !emptyStep)
2245 				{
2246 					emptyStep = true;
2247 					if (++curStep > step)
2248 					{
2249 						break;
2250 					}
2251 				}
2252 			}
2253 			if (fileLine->isActionLine())
2254 			{
2255 				LDLActionLine *actionLine = (LDLActionLine *)fileLine;
2256 
2257 				emptyStep = false;
2258 				if (!watchBBoxIgnore || !actionLine->getBBoxIgnore())
2259 				{
2260 					actionLine->scanPoints(scanner, scanPointCallback,
2261 						matrix, watchBBoxIgnore);
2262 				}
2263 			}
2264 		}
2265 	}
2266 }
2267 
getBoundingBox(TCVector & min,TCVector & max) const2268 void LDLModel::getBoundingBox(TCVector &min, TCVector &max) const
2269 {
2270 	calcBoundingBox();
2271 	min = m_boundingMin;
2272 	max = m_boundingMax;
2273 }
2274 
getMaxRadius(const TCVector & center,bool watchBBoxIgnore)2275 TCFloat LDLModel::getMaxRadius(const TCVector &center, bool watchBBoxIgnore)
2276 {
2277 	calcMaxRadius(center, watchBBoxIgnore);
2278 	if (watchBBoxIgnore)
2279 	{
2280 		return m_maxRadius;
2281 	}
2282 	else
2283 	{
2284 		return m_maxFullRadius;
2285 	}
2286 }
2287 
scanBoundingBoxPoint(const TCVector & point,LDLFileLine * pFileLine)2288 void LDLModel::scanBoundingBoxPoint(
2289 	const TCVector &point,
2290 	LDLFileLine *pFileLine)
2291 {
2292 	if (pFileLine == NULL ||
2293 		pFileLine->getLineType() != LDLLineTypeConditionalLine)
2294 	{
2295 		if (m_flags.haveBoundingBox)
2296 		{
2297 			for (int i = 0; i < 3; i++)
2298 			{
2299 				if (point[i] < m_boundingMin[i])
2300 				{
2301 					m_boundingMin[i] = point[i];
2302 				}
2303 				else if (point[i] > m_boundingMax[i])
2304 				{
2305 					m_boundingMax[i] = point[i];
2306 				}
2307 			}
2308 		}
2309 		else
2310 		{
2311 			m_boundingMin = m_boundingMax = point;
2312 			m_flags.haveBoundingBox = true;
2313 		}
2314 	}
2315 }
2316 
calcBoundingBox(void) const2317 void LDLModel::calcBoundingBox(void) const
2318 {
2319 	if (!m_flags.haveBoundingBox)
2320 	{
2321 		TCFloat matrix[16];
2322 
2323 		if (this == m_mainModel && m_mainModel->getBoundingBoxesOnly())
2324 		{
2325 			int i;
2326 
2327 			for (i = 0; i < m_fileLines->getCount(); i++)
2328 			{
2329 				LDLFileLine *fileLine = (*m_fileLines)[i];
2330 
2331 				if (fileLine->getLineType() == LDLLineTypeModel)
2332 				{
2333 					LDLModelLine *modelLine = (LDLModelLine *)fileLine;
2334 					LDLModel *model = modelLine->getModel();
2335 
2336 					if (model != NULL)
2337 					{
2338 						model->calcBoundingBox();
2339 					}
2340 				}
2341 			}
2342 		}
2343 		TCVector::initIdentityMatrix(matrix);
2344 		// NOTE: we cannot compute bounding boxes heirarchically due to
2345 		// rotation of child models.  With their rotation, their bounding
2346 		// boxes can easily stick out of the really minimum bounding box of
2347 		// their parent.
2348 		scanPoints(const_cast<LDLModel *>(this),
2349 			(LDLScanPointCallback)&LDLModel::scanBoundingBoxPoint, matrix, -1, true);
2350 	}
2351 }
2352 
scanRadiusSquaredPoint(const TCVector & point,LDLFileLine * pFileLine)2353 void LDLModel::scanRadiusSquaredPoint(
2354 	const TCVector &point,
2355 	LDLFileLine *pFileLine)
2356 {
2357 	if (pFileLine == NULL ||
2358 		pFileLine->getLineType() != LDLLineTypeConditionalLine)
2359 	{
2360 		TCFloat radius = (m_center - point).lengthSquared();
2361 
2362 		if (m_flags.fullRadius)
2363 		{
2364 			if (!m_flags.haveMaxFullRadius || radius > m_maxFullRadius)
2365 			{
2366 				m_flags.haveMaxFullRadius = true;
2367 				m_maxFullRadius = radius;
2368 			}
2369 		}
2370 		else
2371 		{
2372 			if (!m_flags.haveMaxRadius || radius > m_maxRadius)
2373 			{
2374 				m_flags.haveMaxRadius = true;
2375 				m_maxRadius = radius;
2376 			}
2377 		}
2378 	}
2379 }
2380 
calcMaxRadius(const TCVector & center,bool watchBBoxIgnore)2381 void LDLModel::calcMaxRadius(const TCVector &center, bool watchBBoxIgnore)
2382 {
2383 	if (m_center != center)
2384 	{
2385 		m_flags.haveMaxRadius = false;
2386 		m_flags.haveMaxFullRadius = false;
2387 	}
2388 	if ((watchBBoxIgnore && !m_flags.haveMaxRadius) ||
2389 		(!watchBBoxIgnore && !m_flags.haveMaxFullRadius))
2390 	{
2391 		TCFloat matrix[16];
2392 
2393 		TCVector::initIdentityMatrix(matrix);
2394 		m_center = center;
2395 		m_flags.fullRadius = !watchBBoxIgnore;
2396 		if (watchBBoxIgnore)
2397 		{
2398 			m_maxRadius = 0;
2399 		}
2400 		else
2401 		{
2402 			m_maxFullRadius = 0;
2403 		}
2404 		scanPoints(this,
2405 			(LDLScanPointCallback)&LDLModel::scanRadiusSquaredPoint, matrix, -1,
2406 			watchBBoxIgnore);
2407 		if (watchBBoxIgnore)
2408 		{
2409 			m_maxRadius = (float)sqrt(m_maxRadius);
2410 		}
2411 		else
2412 		{
2413 			m_maxFullRadius = (float)sqrt(m_maxFullRadius);
2414 		}
2415 	}
2416 }
2417 
getAlertSender(void)2418 TCObject *LDLModel::getAlertSender(void)
2419 {
2420 	return m_mainModel->getAlertSender();
2421 }
2422 
hasBoundingBox(void) const2423 bool LDLModel::hasBoundingBox(void) const
2424 {
2425 	return m_flags.haveBoundingBox != false;
2426 }
2427 
getFileLines(bool initialize)2428 LDLFileLineArray *LDLModel::getFileLines(bool initialize /*= false*/)
2429 {
2430 	if (initialize && m_fileLines == NULL)
2431 	{
2432 		m_fileLines = new LDLFileLineArray;
2433 	}
2434 	return m_fileLines;
2435 }
2436 
copyPublicFlags(const LDLModel * src)2437 void LDLModel::copyPublicFlags(const LDLModel *src)
2438 {
2439 	m_flags.part = src->m_flags.part;
2440 	m_flags.subPart = src->m_flags.subPart;
2441 	m_flags.primitive = src->m_flags.primitive;
2442 	m_flags.mpd = src->m_flags.mpd;
2443 	m_flags.noShrink = src->m_flags.noShrink;
2444 	m_flags.official = src->m_flags.official;
2445 	m_flags.hasStuds = src->m_flags.hasStuds;
2446 	m_flags.bfcCertify = src->m_flags.bfcCertify;
2447 }
2448 
copyBoundingBox(const LDLModel * src)2449 void LDLModel::copyBoundingBox(const LDLModel *src)
2450 {
2451 	if (!src->m_flags.haveBoundingBox)
2452 	{
2453 		src->calcBoundingBox();
2454 	}
2455 	m_boundingMin = src->m_boundingMin;
2456 	m_boundingMax = src->m_boundingMax;
2457 	m_flags.haveBoundingBox = true;
2458 }
2459 
searchNext(const std::string & searchString,IntVector & path,int loopEnd,TCULong activeLineTypes) const2460 bool LDLModel::searchNext(
2461 	const std::string &searchString,
2462 	IntVector& path,
2463 	int loopEnd,
2464 	TCULong activeLineTypes) const
2465 {
2466 	if (m_fileLines == NULL || m_activeLineCount == 0)
2467 	{
2468 		path.clear();
2469 		return false;
2470 	}
2471 	IntVector result;
2472 	int count = m_activeLineCount;
2473 	int endIndex = path.empty() && loopEnd != -1 ? loopEnd + 1: count;
2474 	int startIndex = path.empty() ? 0 : path[0];
2475 	for (int i = startIndex; i < endIndex; ++i)
2476 	{
2477 		const LDLFileLine *child = (*m_fileLines)[i];
2478 		if (((1 << child->getLineType()) & activeLineTypes) == 0)
2479 		{
2480 			continue;
2481 		}
2482 		IntVector childPath;
2483 		int lineOffset = 0;
2484 		bool skipText = false;
2485 		if (!path.empty() && i == path[0])
2486 		{
2487 			skipText = true;
2488 			if (path.size() > 1)
2489 			{
2490 				childPath.insert(childPath.end(), path.begin() + 1, path.end());
2491 			}
2492 		}
2493 		if (!skipText)
2494 		{
2495 			const char *match = strcasestr(child->getLine() + lineOffset,
2496 				searchString.c_str());
2497 
2498 			if (match != NULL)
2499 			{
2500 				result.push_back(i);
2501 				path = result;
2502 				return true;
2503 			}
2504 		}
2505 		if (child->getLineType() == LDLLineTypeModel)
2506 		{
2507 			LDLModel *childModel = ((LDLModelLine *)child)->getModel();
2508 
2509 			if (childModel != NULL && childModel->searchNext(searchString,
2510 				childPath, -1, activeLineTypes))
2511 			{
2512 				result.push_back(i);
2513 				result.insert(result.end(), childPath.begin(), childPath.end());
2514 				path = result;
2515 				return true;
2516 			}
2517 		}
2518 	}
2519 	if (!path.empty() && loopEnd != -1)
2520 	{
2521 		path.clear();
2522 		return searchNext(searchString, path, loopEnd, activeLineTypes);
2523 	}
2524 	path.clear();
2525 	return false;
2526 }
2527 
searchPrevious(const std::string & searchString,IntVector & path,int loopEnd,TCULong activeLineTypes) const2528 bool LDLModel::searchPrevious(
2529 	const std::string &searchString,
2530 	IntVector& path,
2531 	int loopEnd,
2532 	TCULong activeLineTypes) const
2533 {
2534 	if (m_fileLines == NULL || m_activeLineCount == 0)
2535 	{
2536 		path.clear();
2537 		return false;
2538 	}
2539 	IntVector result;
2540 	int count = m_activeLineCount;
2541 	int startIndex = path.empty() ? count - 1 : path[0];
2542 	int endIndex = path.empty() && loopEnd != -1 ? loopEnd : 0;
2543 	for (int i = startIndex; i >= endIndex; --i)
2544 	{
2545 		const LDLFileLine *child = (*m_fileLines)[i];
2546 		if (((1 << child->getLineType()) & activeLineTypes) == 0)
2547 		{
2548 			continue;
2549 		}
2550 		IntVector childPath;
2551 		int lineOffset = 0;
2552 		if (!path.empty() && i == path[0])
2553 		{
2554 			if (path.size() == 1)
2555 			{
2556 				continue;
2557 			}
2558 			else
2559 			{
2560 				childPath.insert(childPath.end(), path.begin() + 1, path.end());
2561 			}
2562 		}
2563 		if (child->getLineType() == LDLLineTypeModel)
2564 		{
2565 			LDLModel *childModel = ((LDLModelLine *)child)->getModel();
2566 
2567 			if (childModel != NULL && childModel->searchPrevious(searchString,
2568 				childPath, -1, activeLineTypes))
2569 			{
2570 				result.push_back(i);
2571 				result.insert(result.end(), childPath.begin(), childPath.end());
2572 				path = result;
2573 				return true;
2574 			}
2575 		}
2576 		const char *match = strcasestr(child->getLine() + lineOffset,
2577 			searchString.c_str());
2578 
2579 		if (match != NULL)
2580 		{
2581 			result.push_back(i);
2582 			path = result;
2583 			return true;
2584 		}
2585 	}
2586 	if (!path.empty() && loopEnd != -1)
2587 	{
2588 		path.clear();
2589 		return searchPrevious(searchString, path, loopEnd, activeLineTypes);
2590 	}
2591 	path.clear();
2592 	return false;
2593 }
2594