1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2012, assimp team
7 
8 All rights reserved.
9 
10 Redistribution and use of this software in source and binary forms,
11 with or without modification, are permitted provided that the following
12 conditions are met:
13 
14 * Redistributions of source code must retain the above
15   copyright notice, this list of conditions and the
16   following disclaimer.
17 
18 * Redistributions in binary form must reproduce the above
19   copyright notice, this list of conditions and the
20   following disclaimer in the documentation and/or other
21   materials provided with the distribution.
22 
23 * Neither the name of the assimp team, nor the names of its
24   contributors may be used to endorse or promote products
25   derived from this software without specific prior
26   written permission of the assimp team.
27 
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 ---------------------------------------------------------------------------
40 */
41 
42 /** @file  LWSLoader.cpp
43  *  @brief Implementation of the LWS importer class
44  */
45 
46 #include "AssimpPCH.h"
47 
48 #include "LWSLoader.h"
49 #include "ParsingUtils.h"
50 #include "fast_atof.h"
51 
52 #include "SceneCombiner.h"
53 #include "GenericProperty.h"
54 #include "SkeletonMeshBuilder.h"
55 #include "ConvertToLHProcess.h"
56 #include "Importer.h"
57 
58 using namespace Assimp;
59 
60 // ------------------------------------------------------------------------------------------------
61 // Recursive parsing of LWS files
Parse(const char * & buffer)62 void LWS::Element::Parse (const char*& buffer)
63 {
64 	for (;SkipSpacesAndLineEnd(&buffer);SkipLine(&buffer)) {
65 
66 		// begin of a new element with children
67 		bool sub = false;
68 		if (*buffer == '{') {
69 			++buffer;
70 			SkipSpaces(&buffer);
71 			sub = true;
72 		}
73 		else if (*buffer == '}')
74 			return;
75 
76 		children.push_back(Element());
77 
78 		// copy data line - read token per token
79 
80 		const char* cur = buffer;
81 		while (!IsSpaceOrNewLine(*buffer)) ++buffer;
82 		children.back().tokens[0] = std::string(cur,(size_t) (buffer-cur));
83 		SkipSpaces(&buffer);
84 
85 		if (children.back().tokens[0] == "Plugin")
86 		{
87 			DefaultLogger::get()->debug("LWS: Skipping over plugin-specific data");
88 
89 			// strange stuff inside Plugin/Endplugin blocks. Needn't
90 			// follow LWS syntax, so we skip over it
91 			for (;SkipSpacesAndLineEnd(&buffer);SkipLine(&buffer)) {
92 				if (!::strncmp(buffer,"EndPlugin",9)) {
93 					//SkipLine(&buffer);
94 					break;
95 				}
96 			}
97 			continue;
98 		}
99 
100 		cur = buffer;
101 		while (!IsLineEnd(*buffer)) ++buffer;
102 		children.back().tokens[1] = std::string(cur,(size_t) (buffer-cur));
103 
104 		// parse more elements recursively
105 		if (sub)
106 			children.back().Parse(buffer);
107 	}
108 }
109 
110 // ------------------------------------------------------------------------------------------------
111 // Constructor to be privately used by Importer
LWSImporter()112 LWSImporter::LWSImporter()
113 {
114 	// nothing to do here
115 }
116 
117 // ------------------------------------------------------------------------------------------------
118 // Destructor, private as well
~LWSImporter()119 LWSImporter::~LWSImporter()
120 {
121 	// nothing to do here
122 }
123 
124 // ------------------------------------------------------------------------------------------------
125 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const126 bool LWSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler,bool checkSig) const
127 {
128 	const std::string extension = GetExtension(pFile);
129 	if (extension == "lws" || extension == "mot")
130 		return true;
131 
132 	// if check for extension is not enough, check for the magic tokens LWSC and LWMO
133 	if (!extension.length() || checkSig) {
134 		uint32_t tokens[2];
135 		tokens[0] = AI_MAKE_MAGIC("LWSC");
136 		tokens[1] = AI_MAKE_MAGIC("LWMO");
137 		return CheckMagicToken(pIOHandler,pFile,tokens,2);
138 	}
139 	return false;
140 }
141 
142 // ------------------------------------------------------------------------------------------------
143 // Get list of file extensions
GetExtensionList(std::set<std::string> & extensions)144 void LWSImporter::GetExtensionList(std::set<std::string>& extensions)
145 {
146 	extensions.insert("lws");
147 	extensions.insert("mot");
148 }
149 
150 // ------------------------------------------------------------------------------------------------
151 // Setup configuration properties
SetupProperties(const Importer * pImp)152 void LWSImporter::SetupProperties(const Importer* pImp)
153 {
154 	// AI_CONFIG_FAVOUR_SPEED
155 	configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
156 
157 	// AI_CONFIG_IMPORT_LWS_ANIM_START
158 	first = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_START,
159 		150392 /* magic hack */);
160 
161 	// AI_CONFIG_IMPORT_LWS_ANIM_END
162 	last = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_END,
163 		150392 /* magic hack */);
164 
165 	if (last < first) {
166 		std::swap(last,first);
167 	}
168 }
169 
170 // ------------------------------------------------------------------------------------------------
171 // Read an envelope description
ReadEnvelope(const LWS::Element & dad,LWO::Envelope & fill)172 void LWSImporter::ReadEnvelope(const LWS::Element& dad, LWO::Envelope& fill )
173 {
174 	if (dad.children.empty()) {
175 		DefaultLogger::get()->error("LWS: Envelope descriptions must not be empty");
176 		return;
177 	}
178 
179 	// reserve enough storage
180 	std::list< LWS::Element >::const_iterator it = dad.children.begin();;
181 	fill.keys.reserve(strtoul10(it->tokens[1].c_str()));
182 
183 	for (++it; it != dad.children.end(); ++it) {
184 		const char* c = (*it).tokens[1].c_str();
185 
186 		if ((*it).tokens[0] == "Key") {
187 			fill.keys.push_back(LWO::Key());
188 			LWO::Key& key = fill.keys.back();
189 
190 			float f;
191 			SkipSpaces(&c);
192 			c = fast_atoreal_move<float>(c,key.value);
193 			SkipSpaces(&c);
194 			c = fast_atoreal_move<float>(c,f);
195 
196 			key.time = f;
197 
198 			unsigned int span = strtoul10(c,&c), num = 0;
199 			switch (span) {
200 
201 				case 0:
202 					key.inter = LWO::IT_TCB;
203 					num = 5;
204 					break;
205 				case 1:
206 				case 2:
207 					key.inter = LWO::IT_HERM;
208 					num = 5;
209 					break;
210 				case 3:
211 					key.inter = LWO::IT_LINE;
212 					num = 0;
213 					break;
214 				case 4:
215 					key.inter = LWO::IT_STEP;
216 					num = 0;
217 					break;
218 				case 5:
219 					key.inter = LWO::IT_BEZ2;
220 					num = 4;
221 					break;
222 				default:
223 					DefaultLogger::get()->error("LWS: Unknown span type");
224 			}
225 			for (unsigned int i = 0; i < num;++i) {
226 				SkipSpaces(&c);
227 				c = fast_atoreal_move<float>(c,key.params[i]);
228 			}
229 		}
230 		else if ((*it).tokens[0] == "Behaviors") {
231 			SkipSpaces(&c);
232 			fill.pre = (LWO::PrePostBehaviour) strtoul10(c,&c);
233 			SkipSpaces(&c);
234 			fill.post = (LWO::PrePostBehaviour) strtoul10(c,&c);
235 		}
236 	}
237 }
238 
239 // ------------------------------------------------------------------------------------------------
240 // Read animation channels in the old LightWave animation format
ReadEnvelope_Old(std::list<LWS::Element>::const_iterator & it,const std::list<LWS::Element>::const_iterator & end,LWS::NodeDesc & nodes,unsigned int)241 void LWSImporter::ReadEnvelope_Old(
242 	std::list< LWS::Element >::const_iterator& it,
243 	const std::list< LWS::Element >::const_iterator& end,
244 	LWS::NodeDesc& nodes,
245 	unsigned int /*version*/)
246 {
247 	unsigned int num,sub_num;
248 	if (++it == end)goto unexpected_end;
249 
250 	num = strtoul10((*it).tokens[0].c_str());
251 	for (unsigned int i = 0; i < num; ++i) {
252 
253 		nodes.channels.push_back(LWO::Envelope());
254 		LWO::Envelope& envl = nodes.channels.back();
255 
256 		envl.index = i;
257 		envl.type  = (LWO::EnvelopeType)(i+1);
258 
259 		if (++it == end)goto unexpected_end;
260 		sub_num = strtoul10((*it).tokens[0].c_str());
261 
262 		for (unsigned int n = 0; n < sub_num;++n) {
263 
264 			if (++it == end)goto unexpected_end;
265 
266 			// parse value and time, skip the rest for the moment.
267 			LWO::Key key;
268 			const char* c = fast_atoreal_move<float>((*it).tokens[0].c_str(),key.value);
269 			SkipSpaces(&c);
270 			float f;
271 			fast_atoreal_move<float>((*it).tokens[0].c_str(),f);
272 			key.time = f;
273 
274 			envl.keys.push_back(key);
275 		}
276 	}
277 	return;
278 
279 unexpected_end:
280 	DefaultLogger::get()->error("LWS: Encountered unexpected end of file while parsing object motion");
281 }
282 
283 // ------------------------------------------------------------------------------------------------
284 // Setup a nice name for a node
SetupNodeName(aiNode * nd,LWS::NodeDesc & src)285 void LWSImporter::SetupNodeName(aiNode* nd, LWS::NodeDesc& src)
286 {
287 	const unsigned int combined = src.number | ((unsigned int)src.type) << 28u;
288 
289 	// the name depends on the type. We break LWS's strange naming convention
290 	// and return human-readable, but still machine-parsable and unique, strings.
291 	if (src.type == LWS::NodeDesc::OBJECT)	{
292 
293 		if (src.path.length()) {
294 			std::string::size_type s = src.path.find_last_of("\\/");
295 			if (s == std::string::npos)
296 				s = 0;
297 			else ++s;
298             std::string::size_type t = src.path.substr(s).find_last_of(".");
299 
300 			nd->mName.length = ::sprintf(nd->mName.data,"%s_(%08X)",src.path.substr(s).substr(0,t).c_str(),combined);
301 			return;
302 		}
303 	}
304 	nd->mName.length = ::sprintf(nd->mName.data,"%s_(%08X)",src.name,combined);
305 }
306 
307 // ------------------------------------------------------------------------------------------------
308 // Recursively build the scenegraph
BuildGraph(aiNode * nd,LWS::NodeDesc & src,std::vector<AttachmentInfo> & attach,BatchLoader & batch,aiCamera ** & camOut,aiLight ** & lightOut,std::vector<aiNodeAnim * > & animOut)309 void LWSImporter::BuildGraph(aiNode* nd, LWS::NodeDesc& src, std::vector<AttachmentInfo>& attach,
310 	BatchLoader& batch,
311 	aiCamera**& camOut,
312 	aiLight**& lightOut,
313 	std::vector<aiNodeAnim*>& animOut)
314 {
315 	// Setup a very cryptic name for the node, we want the user to be happy
316 	SetupNodeName(nd,src);
317     aiNode* ndAnim = nd;
318 
319 	// If the node is an object
320 	if (src.type == LWS::NodeDesc::OBJECT) {
321 
322 		// If the object is from an external file, get it
323 		aiScene* obj = NULL;
324         if (src.path.length() ) {
325             obj = batch.GetImport(src.id);
326             if (!obj) {
327                 DefaultLogger::get()->error("LWS: Failed to read external file " + src.path);
328             }
329             else {
330                 if (obj->mRootNode->mNumChildren == 1) {
331 
332                     //If the pivot is not set for this layer, get it from the external object
333                     if (!src.isPivotSet) {
334                         src.pivotPos.x = +obj->mRootNode->mTransformation.a4;
335                         src.pivotPos.y = +obj->mRootNode->mTransformation.b4;
336                         src.pivotPos.z = -obj->mRootNode->mTransformation.c4; //The sign is the RH to LH back conversion
337                     }
338 
339                     //Remove first node from obj (the old pivot), reset transform of second node (the mesh node)
340                     aiNode* newRootNode = obj->mRootNode->mChildren[0];
341 					obj->mRootNode->mChildren[0] = NULL;
342 					delete obj->mRootNode;
343 
344                     obj->mRootNode = newRootNode;
345                     obj->mRootNode->mTransformation.a4 = 0.0;
346                     obj->mRootNode->mTransformation.b4 = 0.0;
347                     obj->mRootNode->mTransformation.c4 = 0.0;
348                 }
349             }
350         }
351 
352 		//Setup the pivot node (also the animation node), the one we received
353         nd->mName = std::string("Pivot:") + nd->mName.data;
354 		ndAnim = nd;
355 
356         //Add the attachment node to it
357         nd->mNumChildren = 1;
358         nd->mChildren = new aiNode*[1];
359         nd->mChildren[0] = new aiNode();
360         nd->mChildren[0]->mParent = nd;
361         nd->mChildren[0]->mTransformation.a4 = -src.pivotPos.x;
362         nd->mChildren[0]->mTransformation.b4 = -src.pivotPos.y;
363         nd->mChildren[0]->mTransformation.c4 = -src.pivotPos.z;
364 		SetupNodeName(nd->mChildren[0], src);
365 
366 		//Update the attachment node
367 		nd = nd->mChildren[0];
368 
369         //Push attachment, if the object came from an external file
370         if (obj) {
371             attach.push_back(AttachmentInfo(obj,nd));
372         }
373     }
374 
375 	// If object is a light source - setup a corresponding ai structure
376 	else if (src.type == LWS::NodeDesc::LIGHT) {
377 		aiLight* lit = *lightOut++ = new aiLight();
378 
379 		// compute final light color
380 		lit->mColorDiffuse = lit->mColorSpecular = src.lightColor*src.lightIntensity;
381 
382 		// name to attach light to node -> unique due to LWs indexing system
383 		lit->mName = nd->mName;
384 
385 		// detemine light type and setup additional members
386 		if (src.lightType == 2) { /* spot light */
387 
388 			lit->mType = aiLightSource_SPOT;
389 			lit->mAngleInnerCone = (float)AI_DEG_TO_RAD( src.lightConeAngle );
390 			lit->mAngleOuterCone = lit->mAngleInnerCone+(float)AI_DEG_TO_RAD( src.lightEdgeAngle );
391 
392 		}
393 		else if (src.lightType == 1) { /* directional light source */
394 			lit->mType = aiLightSource_DIRECTIONAL;
395 		}
396 		else lit->mType = aiLightSource_POINT;
397 
398 		// fixme: no proper handling of light falloffs yet
399 		if (src.lightFalloffType == 1)
400 			lit->mAttenuationConstant = 1.f;
401 		else if (src.lightFalloffType == 1)
402 			lit->mAttenuationLinear = 1.f;
403 		else
404 			lit->mAttenuationQuadratic = 1.f;
405 	}
406 
407 	// If object is a camera - setup a corresponding ai structure
408 	else if (src.type == LWS::NodeDesc::CAMERA) {
409 		aiCamera* cam = *camOut++ = new aiCamera();
410 
411 		// name to attach cam to node -> unique due to LWs indexing system
412 		cam->mName = nd->mName;
413 	}
414 
415 	// Get the node transformation from the LWO key
416 	LWO::AnimResolver resolver(src.channels,fps);
417 	resolver.ExtractBindPose(ndAnim->mTransformation);
418 
419 	// .. and construct animation channels
420 	aiNodeAnim* anim = NULL;
421 
422 	if (first != last) {
423 		resolver.SetAnimationRange(first,last);
424 		resolver.ExtractAnimChannel(&anim,AI_LWO_ANIM_FLAG_SAMPLE_ANIMS|AI_LWO_ANIM_FLAG_START_AT_ZERO);
425 		if (anim) {
426 			anim->mNodeName = ndAnim->mName;
427 			animOut.push_back(anim);
428 		}
429 	}
430 
431 	// Add children
432 	if (src.children.size()) {
433 		nd->mChildren = new aiNode*[src.children.size()];
434 		for (std::list<LWS::NodeDesc*>::iterator it = src.children.begin(); it != src.children.end(); ++it) {
435 			aiNode* ndd = nd->mChildren[nd->mNumChildren++] = new aiNode();
436 			ndd->mParent = nd;
437 
438 			BuildGraph(ndd,**it,attach,batch,camOut,lightOut,animOut);
439 		}
440 	}
441 }
442 
443 // ------------------------------------------------------------------------------------------------
444 // Determine the exact location of a LWO file
FindLWOFile(const std::string & in)445 std::string LWSImporter::FindLWOFile(const std::string& in)
446 {
447 	// insert missing directory seperator if necessary
448 	std::string tmp;
449 	if (in.length() > 3 && in[1] == ':'&& in[2] != '\\' && in[2] != '/')
450 	{
451 		tmp = in[0] + ":\\" + in.substr(2);
452 	}
453 	else tmp = in;
454 
455 	if (io->Exists(tmp)) {
456 		return in;
457 	}
458 
459 	// file is not accessible for us ... maybe it's packed by
460 	// LightWave's 'Package Scene' command?
461 
462 	// Relevant for us are the following two directories:
463 	// <folder>\Objects\<hh>\<*>.lwo
464 	// <folder>\Scenes\<hh>\<*>.lws
465 	// where <hh> is optional.
466 
467 	std::string test = ".." + io->getOsSeparator() + tmp;
468 	if (io->Exists(test))
469 		return test;
470 
471 	test = ".." + io->getOsSeparator() + test;
472 	if (io->Exists(test)) {
473 		return test;
474 	}
475 
476 
477 	// return original path, maybe the IOsystem knows better
478 	return tmp;
479 }
480 
481 // ------------------------------------------------------------------------------------------------
482 // Read file into given scene data structure
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)483 void LWSImporter::InternReadFile( const std::string& pFile, aiScene* pScene,
484 	IOSystem* pIOHandler)
485 {
486 	io = pIOHandler;
487 	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
488 
489 	// Check whether we can read from the file
490 	if( file.get() == NULL) {
491 		throw DeadlyImportError( "Failed to open LWS file " + pFile + ".");
492 	}
493 
494 	// Allocate storage and copy the contents of the file to a memory buffer
495 	std::vector< char > mBuffer;
496 	TextFileToBuffer(file.get(),mBuffer);
497 
498 	// Parse the file structure
499 	LWS::Element root; const char* dummy = &mBuffer[0];
500 	root.Parse(dummy);
501 
502 	// Construct a Batchimporter to read more files recursively
503 	BatchLoader batch(pIOHandler);
504 //	batch.SetBasePath(pFile);
505 
506 	// Construct an array to receive the flat output graph
507 	std::list<LWS::NodeDesc> nodes;
508 
509 	unsigned int cur_light = 0, cur_camera = 0, cur_object = 0;
510 	unsigned int num_light = 0, num_camera = 0, num_object = 0;
511 
512 	// check magic identifier, 'LWSC'
513 	bool motion_file = false;
514 	std::list< LWS::Element >::const_iterator it = root.children.begin();
515 
516 	if ((*it).tokens[0] == "LWMO")
517 		motion_file = true;
518 
519 	if ((*it).tokens[0] != "LWSC" && !motion_file)
520 		throw DeadlyImportError("LWS: Not a LightWave scene, magic tag LWSC not found");
521 
522 	// get file format version and print to log
523 	++it;
524 	unsigned int version = strtoul10((*it).tokens[0].c_str());
525 	DefaultLogger::get()->info("LWS file format version is " + (*it).tokens[0]);
526 	first = 0.;
527 	last  = 60.;
528 	fps   = 25.; /* seems to be a good default frame rate */
529 
530 	// Now read all elements in a very straghtforward manner
531 	for (; it != root.children.end(); ++it) {
532 		const char* c = (*it).tokens[1].c_str();
533 
534 		// 'FirstFrame': begin of animation slice
535 		if ((*it).tokens[0] == "FirstFrame") {
536 			if (150392. != first           /* see SetupProperties() */)
537 				first = strtoul10(c,&c)-1.; /* we're zero-based */
538 		}
539 
540 		// 'LastFrame': end of animation slice
541 		else if ((*it).tokens[0] == "LastFrame") {
542 			if (150392. != last      /* see SetupProperties() */)
543 				last = strtoul10(c,&c)-1.; /* we're zero-based */
544 		}
545 
546 		// 'FramesPerSecond': frames per second
547 		else if ((*it).tokens[0] == "FramesPerSecond") {
548 			fps = strtoul10(c,&c);
549 		}
550 
551 		// 'LoadObjectLayer': load a layer of a specific LWO file
552 		else if ((*it).tokens[0] == "LoadObjectLayer") {
553 
554 			// get layer index
555 			const int layer = strtoul10(c,&c);
556 
557 			// setup the layer to be loaded
558 			BatchLoader::PropertyMap props;
559 			SetGenericProperty(props.ints,AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY,layer);
560 
561 			// add node to list
562 			LWS::NodeDesc d;
563 			d.type = LWS::NodeDesc::OBJECT;
564 			if (version >= 4) { // handle LWSC 4 explicit ID
565 				SkipSpaces(&c);
566 				d.number = strtoul16(c,&c) & AI_LWS_MASK;
567 			}
568 			else d.number = cur_object++;
569 
570 			// and add the file to the import list
571 			SkipSpaces(&c);
572 			std::string path = FindLWOFile( c );
573 			d.path = path;
574 			d.id = batch.AddLoadRequest(path,0,&props);
575 
576 			nodes.push_back(d);
577 			num_object++;
578 		}
579 		// 'LoadObject': load a LWO file into the scenegraph
580 		else if ((*it).tokens[0] == "LoadObject") {
581 
582 			// add node to list
583 			LWS::NodeDesc d;
584 			d.type = LWS::NodeDesc::OBJECT;
585 
586 			if (version >= 4) { // handle LWSC 4 explicit ID
587 				d.number = strtoul16(c,&c) & AI_LWS_MASK;
588 				SkipSpaces(&c);
589 			}
590 			else d.number = cur_object++;
591 			std::string path = FindLWOFile( c );
592 			d.id = batch.AddLoadRequest(path,0,NULL);
593 
594 			d.path = path;
595 			nodes.push_back(d);
596 			num_object++;
597 		}
598 		// 'AddNullObject': add a dummy node to the hierarchy
599 		else if ((*it).tokens[0] == "AddNullObject") {
600 
601 			// add node to list
602 			LWS::NodeDesc d;
603 			d.type = LWS::NodeDesc::OBJECT;
604 			if (version >= 4) { // handle LWSC 4 explicit ID
605 				d.number = strtoul16(c,&c) & AI_LWS_MASK;
606 				SkipSpaces(&c);
607 			}
608 			else d.number = cur_object++;
609             d.name = c;
610 			nodes.push_back(d);
611 
612 			num_object++;
613 		}
614 		// 'NumChannels': Number of envelope channels assigned to last layer
615 		else if ((*it).tokens[0] == "NumChannels") {
616 			// ignore for now
617 		}
618 		// 'Channel': preceedes any envelope description
619 		else if ((*it).tokens[0] == "Channel") {
620 			if (nodes.empty()) {
621 				if (motion_file) {
622 
623 					// LightWave motion file. Add dummy node
624 					LWS::NodeDesc d;
625 					d.type = LWS::NodeDesc::OBJECT;
626 					d.name = c;
627 					d.number = cur_object++;
628 					nodes.push_back(d);
629 				}
630 				else DefaultLogger::get()->error("LWS: Unexpected keyword: \'Channel\'");
631 			}
632 
633 			// important: index of channel
634 			nodes.back().channels.push_back(LWO::Envelope());
635 			LWO::Envelope& env = nodes.back().channels.back();
636 
637 			env.index = strtoul10(c);
638 
639 			// currently we can just interpret the standard channels 0...9
640 			// (hack) assume that index-i yields the binary channel type from LWO
641 			env.type = (LWO::EnvelopeType)(env.index+1);
642 
643 		}
644 		// 'Envelope': a single animation channel
645 		else if ((*it).tokens[0] == "Envelope") {
646 			if (nodes.empty() || nodes.back().channels.empty())
647 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'Envelope\'");
648 			else {
649 				ReadEnvelope((*it),nodes.back().channels.back());
650 			}
651 		}
652 		// 'ObjectMotion': animation information for older lightwave formats
653 		else if (version < 3  && ((*it).tokens[0] == "ObjectMotion" ||
654 			(*it).tokens[0] == "CameraMotion" ||
655 			(*it).tokens[0] == "LightMotion")) {
656 
657 			if (nodes.empty())
658 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'<Light|Object|Camera>Motion\'");
659 			else {
660 				ReadEnvelope_Old(it,root.children.end(),nodes.back(),version);
661 			}
662 		}
663 		// 'Pre/PostBehavior': pre/post animation behaviour for LWSC 2
664 		else if (version == 2 && (*it).tokens[0] == "Pre/PostBehavior") {
665 			if (nodes.empty())
666 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'Pre/PostBehavior'");
667 			else {
668 				for (std::list<LWO::Envelope>::iterator it = nodes.back().channels.begin(); it != nodes.back().channels.end(); ++it) {
669 					// two ints per envelope
670 					LWO::Envelope& env = *it;
671 					env.pre  = (LWO::PrePostBehaviour) strtoul10(c,&c); SkipSpaces(&c);
672 					env.post = (LWO::PrePostBehaviour) strtoul10(c,&c); SkipSpaces(&c);
673 				}
674 			}
675 		}
676 		// 'ParentItem': specifies the parent of the current element
677 		else if ((*it).tokens[0] == "ParentItem") {
678 			if (nodes.empty())
679 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'ParentItem\'");
680 
681 			else nodes.back().parent = strtoul16(c,&c);
682 		}
683 		// 'ParentObject': deprecated one for older formats
684 		else if (version < 3 && (*it).tokens[0] == "ParentObject") {
685 			if (nodes.empty())
686 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'ParentObject\'");
687 
688 			else {
689 				nodes.back().parent = strtoul10(c,&c) | (1u << 28u);
690 			}
691 		}
692 		// 'AddCamera': add a camera to the scenegraph
693 		else if ((*it).tokens[0] == "AddCamera") {
694 
695 			// add node to list
696 			LWS::NodeDesc d;
697 			d.type = LWS::NodeDesc::CAMERA;
698 
699 			if (version >= 4) { // handle LWSC 4 explicit ID
700 				d.number = strtoul16(c,&c) & AI_LWS_MASK;
701 			}
702 			else d.number = cur_camera++;
703 			nodes.push_back(d);
704 
705 			num_camera++;
706 		}
707 		// 'CameraName': set name of currently active camera
708 		else if ((*it).tokens[0] == "CameraName") {
709 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::CAMERA)
710 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'CameraName\'");
711 
712 			else nodes.back().name = c;
713 		}
714 		// 'AddLight': add a light to the scenegraph
715 		else if ((*it).tokens[0] == "AddLight") {
716 
717 			// add node to list
718 			LWS::NodeDesc d;
719 			d.type = LWS::NodeDesc::LIGHT;
720 
721 			if (version >= 4) { // handle LWSC 4 explicit ID
722 				d.number = strtoul16(c,&c) & AI_LWS_MASK;
723 			}
724 			else d.number = cur_light++;
725 			nodes.push_back(d);
726 
727 			num_light++;
728 		}
729 		// 'LightName': set name of currently active light
730 		else if ((*it).tokens[0] == "LightName") {
731 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
732 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightName\'");
733 
734 			else nodes.back().name = c;
735 		}
736 		// 'LightIntensity': set intensity of currently active light
737 		else if ((*it).tokens[0] == "LightIntensity" || (*it).tokens[0] == "LgtIntensity" ) {
738 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
739 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightIntensity\'");
740 
741 			else fast_atoreal_move<float>(c, nodes.back().lightIntensity );
742 
743 		}
744 		// 'LightType': set type of currently active light
745 		else if ((*it).tokens[0] == "LightType") {
746 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
747 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightType\'");
748 
749 			else nodes.back().lightType = strtoul10(c);
750 
751 		}
752 		// 'LightFalloffType': set falloff type of currently active light
753 		else if ((*it).tokens[0] == "LightFalloffType") {
754 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
755 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightFalloffType\'");
756 
757 			else nodes.back().lightFalloffType = strtoul10(c);
758 
759 		}
760 		// 'LightConeAngle': set cone angle of currently active light
761 		else if ((*it).tokens[0] == "LightConeAngle") {
762 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
763 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightConeAngle\'");
764 
765 			else nodes.back().lightConeAngle = fast_atof(c);
766 
767 		}
768 		// 'LightEdgeAngle': set area where we're smoothing from min to max intensity
769 		else if ((*it).tokens[0] == "LightEdgeAngle") {
770 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
771 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightEdgeAngle\'");
772 
773 			else nodes.back().lightEdgeAngle = fast_atof(c);
774 
775 		}
776 		// 'LightColor': set color of currently active light
777 		else if ((*it).tokens[0] == "LightColor") {
778 			if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT)
779 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightColor\'");
780 
781 			else {
782 				c = fast_atoreal_move<float>(c, (float&) nodes.back().lightColor.r );
783 				SkipSpaces(&c);
784 				c = fast_atoreal_move<float>(c, (float&) nodes.back().lightColor.g );
785 				SkipSpaces(&c);
786 				c = fast_atoreal_move<float>(c, (float&) nodes.back().lightColor.b );
787 			}
788 		}
789 
790 		// 'PivotPosition': position of local transformation origin
791 		else if ((*it).tokens[0] == "PivotPosition" || (*it).tokens[0] == "PivotPoint") {
792 			if (nodes.empty())
793 				DefaultLogger::get()->error("LWS: Unexpected keyword: \'PivotPosition\'");
794 			else {
795 				c = fast_atoreal_move<float>(c, (float&) nodes.back().pivotPos.x );
796 				SkipSpaces(&c);
797 				c = fast_atoreal_move<float>(c, (float&) nodes.back().pivotPos.y );
798 				SkipSpaces(&c);
799 				c = fast_atoreal_move<float>(c, (float&) nodes.back().pivotPos.z );
800                 // Mark pivotPos as set
801                 nodes.back().isPivotSet = true;
802 			}
803 		}
804 	}
805 
806 	// resolve parenting
807 	for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
808 
809 		// check whether there is another node which calls us a parent
810 		for (std::list<LWS::NodeDesc>::iterator dit = nodes.begin(); dit != nodes.end(); ++dit) {
811 			if (dit != it && *it == (*dit).parent) {
812 				if ((*dit).parent_resolved) {
813 					// fixme: it's still possible to produce an overflow due to cross references ..
814 					DefaultLogger::get()->error("LWS: Found cross reference in scenegraph");
815 					continue;
816 				}
817 
818 				(*it).children.push_back(&*dit);
819 				(*dit).parent_resolved = &*it;
820 			}
821 		}
822 	}
823 
824 	// find out how many nodes have no parent yet
825 	unsigned int no_parent = 0;
826 	for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
827 		if (!(*it).parent_resolved)
828 			++ no_parent;
829 	}
830 	if (!no_parent)
831 		throw DeadlyImportError("LWS: Unable to find scene root node");
832 
833 
834 	// Load all subsequent files
835 	batch.LoadAll();
836 
837 	// and build the final output graph by attaching the loaded external
838 	// files to ourselves. first build a master graph
839 	aiScene* master = new aiScene();
840 	aiNode* nd = master->mRootNode = new aiNode();
841 
842 	// allocate storage for cameras&lights
843 	if (num_camera) {
844 		master->mCameras = new aiCamera*[master->mNumCameras = num_camera];
845 	}
846 	aiCamera** cams = master->mCameras;
847 	if (num_light) {
848 		master->mLights = new aiLight*[master->mNumLights = num_light];
849 	}
850 	aiLight** lights = master->mLights;
851 
852 	std::vector<AttachmentInfo> attach;
853 	std::vector<aiNodeAnim*> anims;
854 
855 	nd->mName.Set("<LWSRoot>");
856 	nd->mChildren = new aiNode*[no_parent];
857 	for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) {
858 		if (!(*it).parent_resolved) {
859 			aiNode* ro = nd->mChildren[ nd->mNumChildren++ ] = new aiNode();
860 			ro->mParent = nd;
861 
862 			// ... and build the scene graph. If we encounter object nodes,
863 			// add then to our attachment table.
864 			BuildGraph(ro,*it, attach, batch, cams, lights, anims);
865 		}
866 	}
867 
868 	// create a master animation channel for us
869 	if (anims.size()) {
870 		master->mAnimations = new aiAnimation*[master->mNumAnimations = 1];
871 		aiAnimation* anim = master->mAnimations[0] = new aiAnimation();
872 		anim->mName.Set("LWSMasterAnim");
873 
874 		// LWS uses seconds as time units, but we convert to frames
875 		anim->mTicksPerSecond = fps;
876 		anim->mDuration = last-(first-1); /* fixme ... zero or one-based?*/
877 
878 		anim->mChannels = new aiNodeAnim*[anim->mNumChannels = anims.size()];
879 		std::copy(anims.begin(),anims.end(),anim->mChannels);
880 	}
881 
882 	// convert the master scene to RH
883 	MakeLeftHandedProcess monster_cheat;
884 	monster_cheat.Execute(master);
885 
886 	// .. ccw
887 	FlipWindingOrderProcess flipper;
888 	flipper.Execute(master);
889 
890 	// OK ... finally build the output graph
891 	SceneCombiner::MergeScenes(&pScene,master,attach,
892 		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES    | (!configSpeedFlag ? (
893 		AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : 0));
894 
895 	// Check flags
896 	if (!pScene->mNumMeshes || !pScene->mNumMaterials) {
897 		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
898 
899 		if (pScene->mNumAnimations) {
900 			// construct skeleton mesh
901 			SkeletonMeshBuilder builder(pScene);
902 		}
903 	}
904 
905 }
906