Coverage Report

Created: 2026-10-28 07:46

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