1 | /* |
2 | --------------------------------------------------------------------------- |
3 | Open Asset Import Library (assimp) |
4 | --------------------------------------------------------------------------- |
5 | |
6 | Copyright (c) 2006-2017, assimp team |
7 | |
8 | |
9 | All rights reserved. |
10 | |
11 | Redistribution and use of this software in source and binary forms, |
12 | with or without modification, are permitted provided that the following |
13 | conditions 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 | |
29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
30 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
31 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
32 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
33 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
34 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
35 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
36 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
38 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
39 | OF 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 | |
65 | using namespace Assimp; |
66 | |
67 | static 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 |
82 | void 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 |
132 | LWSImporter::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 |
145 | LWSImporter::~LWSImporter() |
146 | { |
147 | // nothing to do here |
148 | } |
149 | |
150 | // ------------------------------------------------------------------------------------------------ |
151 | // Returns whether the class can handle the format of the given file. |
152 | bool 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 |
170 | const aiImporterDesc* LWSImporter::GetInfo () const |
171 | { |
172 | return &desc; |
173 | } |
174 | |
175 | // ------------------------------------------------------------------------------------------------ |
176 | // Setup configuration properties |
177 | void 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 |
199 | void 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 |
268 | void 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 | |
306 | unexpected_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 |
312 | void 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 |
336 | void 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 |
472 | std::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 |
511 | void 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 | |