Coverage Report

Created: 2024-08-02 07:04

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