Coverage Report

Created: 2026-09-14 07:44

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