Coverage Report

Created: 2025-06-22 07:30

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