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