Coverage Report

Created: 2025-06-22 07:30

/src/assimp/code/AssetLib/Collada/ColladaHelper.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2025, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
copyright notice, this list of conditions and the
15
following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
copyright notice, this list of conditions and the
19
following disclaimer in the documentation and/or other
20
materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
contributors may be used to endorse or promote products
24
derived from this software without specific prior
25
written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
42
/** Helper structures for the Collada loader */
43
44
#ifndef AI_COLLADAHELPER_H_INC
45
#define AI_COLLADAHELPER_H_INC
46
47
#include <assimp/light.h>
48
#include <assimp/material.h>
49
#include <assimp/mesh.h>
50
51
#include <cstdint>
52
#include <map>
53
#include <set>
54
#include <vector>
55
56
struct aiMaterial;
57
58
namespace Assimp {
59
namespace Collada {
60
61
/// Collada file versions which evolved during the years ...
62
enum FormatVersion {
63
    FV_1_5_n,
64
    FV_1_4_n,
65
    FV_1_3_n
66
};
67
68
/// Transformation types that can be applied to a node
69
enum TransformType {
70
    TF_LOOKAT,
71
    TF_ROTATE,
72
    TF_TRANSLATE,
73
    TF_SCALE,
74
    TF_SKEW,
75
    TF_MATRIX
76
};
77
78
/// Different types of input data to a vertex or face
79
enum InputType {
80
    IT_Invalid,
81
    IT_Vertex, // special type for per-index data referring to the <vertices> element carrying the per-vertex data.
82
    IT_Position,
83
    IT_Normal,
84
    IT_Texcoord,
85
    IT_Color,
86
    IT_Tangent,
87
    IT_Bitangent
88
};
89
90
/// Supported controller types
91
enum ControllerType {
92
    Skin,
93
    Morph
94
};
95
96
/// Supported morph methods
97
enum MorphMethod {
98
    Normalized,
99
    Relative
100
};
101
102
/// Common metadata keys as <Collada, Assimp>
103
using MetaKeyPair = std::pair<std::string, std::string>;
104
using MetaKeyPairVector = std::vector<MetaKeyPair>;
105
106
/// Collada as lower_case (native)
107
const MetaKeyPairVector &GetColladaAssimpMetaKeys();
108
109
// Collada as CamelCase (used by Assimp for consistency)
110
const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase();
111
112
/// Convert underscore_separated to CamelCase "authoring_tool" becomes "AuthoringTool"
113
void ToCamelCase(std::string &text);
114
115
/// Contains all data for one of the different transformation types
116
struct Transform {
117
    std::string mID; ///< SID of the transform step, by which anim channels address their target node
118
    TransformType mType;
119
    ai_real f[16]; ///< Interpretation of data depends on the type of the transformation
120
};
121
122
/// A collada camera.
123
struct Camera {
124
    Camera() :
125
0
            mOrtho(false),
126
0
            mHorFov(10e10f),
127
0
            mVerFov(10e10f),
128
0
            mAspect(10e10f),
129
0
            mZNear(0.1f),
130
0
            mZFar(1000.f) {}
131
132
    /// Name of camera
133
    std::string mName;
134
135
    /// True if it is an orthographic camera
136
    bool mOrtho;
137
138
    /// Horizontal field of view in degrees
139
    ai_real mHorFov;
140
141
    /// Vertical field of view in degrees
142
    ai_real mVerFov;
143
144
    /// Screen aspect
145
    ai_real mAspect;
146
147
    /// Near& far z
148
    ai_real mZNear, mZFar;
149
};
150
151
0
#define ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET 1e9f
152
153
/** A collada light source. */
154
struct Light {
155
    Light() :
156
0
            mType(aiLightSource_UNDEFINED),
157
0
            mAttConstant(1.f),
158
0
            mAttLinear(0.f),
159
0
            mAttQuadratic(0.f),
160
0
            mFalloffAngle(180.f),
161
0
            mFalloffExponent(0.f),
162
0
            mPenumbraAngle(ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET),
163
0
            mOuterAngle(ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET),
164
0
            mIntensity(1.f) {}
165
166
    /// Type of the light source aiLightSourceType + ambient
167
    unsigned int mType;
168
169
    /// Color of the light
170
    aiColor3D mColor;
171
172
    /// Light attenuation
173
    ai_real mAttConstant, mAttLinear, mAttQuadratic;
174
175
    /// Spot light falloff
176
    ai_real mFalloffAngle;
177
    ai_real mFalloffExponent;
178
179
    // -----------------------------------------------------
180
    // FCOLLADA extension from here
181
182
    /// ... related stuff from maja and max extensions
183
    ai_real mPenumbraAngle;
184
    ai_real mOuterAngle;
185
186
    /// Common light intensity
187
    ai_real mIntensity;
188
};
189
190
/** Short vertex index description */
191
struct InputSemanticMapEntry {
192
    InputSemanticMapEntry() :
193
0
            mSet(0),
194
0
            mType(IT_Invalid) {}
195
196
    /// Index of set, optional
197
    unsigned int mSet;
198
199
    /// Type of referenced vertex input
200
    InputType mType;
201
};
202
203
/// Table to map from effect to vertex input semantics
204
struct SemanticMappingTable {
205
    /// Name of material
206
    std::string mMatName;
207
208
    /// List of semantic map commands, grouped by effect semantic name
209
    using InputSemanticMap = std::map<std::string, InputSemanticMapEntry>;
210
    InputSemanticMap mMap;
211
212
    /// For std::find
213
0
    bool operator==(const std::string &s) const {
214
0
        return s == mMatName;
215
0
    }
216
};
217
218
/// A reference to a mesh inside a node, including materials assigned to the various subgroups.
219
/// The ID refers to either a mesh or a controller which specifies the mesh
220
struct MeshInstance {
221
    ///< ID of the mesh or controller to be instanced
222
    std::string mMeshOrController;
223
224
    ///< Map of materials by the subgroup ID they're applied to
225
    std::map<std::string, SemanticMappingTable> mMaterials;
226
};
227
228
/// A reference to a camera inside a node
229
struct CameraInstance {
230
    ///< ID of the camera
231
    std::string mCamera;
232
};
233
234
/// A reference to a light inside a node
235
struct LightInstance {
236
    ///< ID of the camera
237
    std::string mLight;
238
};
239
240
/// A reference to a node inside a node
241
struct NodeInstance {
242
    ///< ID of the node
243
    std::string mNode;
244
};
245
246
/// A node in a scene hierarchy
247
struct Node {
248
    std::string mName;
249
    std::string mID;
250
    std::string mSID;
251
    Node *mParent;
252
    std::vector<Node *> mChildren;
253
254
    /// Operations in order to calculate the resulting transformation to parent.
255
    std::vector<Transform> mTransforms;
256
257
    /// Meshes at this node
258
    std::vector<MeshInstance> mMeshes;
259
260
    /// Lights at this node
261
    std::vector<LightInstance> mLights;
262
263
    /// Cameras at this node
264
    std::vector<CameraInstance> mCameras;
265
266
    /// Node instances at this node
267
    std::vector<NodeInstance> mNodeInstances;
268
269
    /// Root-nodes: Name of primary camera, if any
270
    std::string mPrimaryCamera;
271
272
    /// Constructor. Begin with a zero parent
273
    Node() :
274
0
            mParent(nullptr) {
275
        // empty
276
0
    }
277
278
    /// Destructor: delete all children subsequently
279
0
    ~Node() {
280
0
        for (std::vector<Node *>::iterator it = mChildren.begin(); it != mChildren.end(); ++it) {
281
0
            delete *it;
282
0
        }
283
0
    }
284
};
285
286
/// Data source array: either floats or strings
287
struct Data {
288
    bool mIsStringArray;
289
    std::vector<ai_real> mValues;
290
    std::vector<std::string> mStrings;
291
};
292
293
/// Accessor to a data array
294
struct Accessor {
295
    size_t mCount; // in number of objects
296
    size_t mSize; // size of an object, in elements (floats or strings, mostly 1)
297
    size_t mOffset; // in number of values
298
    size_t mStride; // Stride in number of values
299
    std::vector<std::string> mParams; // names of the data streams in the accessors. Empty string tells to ignore.
300
    size_t mSubOffset[4]; // Sub-offset inside the object for the common 4 elements. For a vector, that's XYZ, for a color RGBA and so on.
301
            // For example, SubOffset[0] denotes which of the values inside the object is the vector X component.
302
    std::string mSource; // URL of the source array
303
    mutable const Data *mData; // Pointer to the source array, if resolved. nullptr else
304
305
0
    Accessor() {
306
0
        mCount = 0;
307
0
        mSize = 0;
308
0
        mOffset = 0;
309
0
        mStride = 0;
310
0
        mData = nullptr;
311
0
        mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0;
312
0
    }
313
};
314
315
/// A single face in a mesh
316
struct Face {
317
    std::vector<size_t> mIndices;
318
};
319
320
/// An input channel for mesh data, referring to a single accessor
321
struct InputChannel {
322
    InputType mType; // Type of the data
323
    size_t mIndex; // Optional index, if multiple sets of the same data type are given
324
    size_t mOffset; // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better.
325
    std::string mAccessor; // ID of the accessor where to read the actual values from.
326
    mutable const Accessor *mResolved; // Pointer to the accessor, if resolved. nullptr else
327
328
0
    InputChannel() {
329
0
        mType = IT_Invalid;
330
0
        mIndex = 0;
331
0
        mOffset = 0;
332
0
        mResolved = nullptr;
333
0
    }
334
};
335
336
/// Subset of a mesh with a certain material
337
struct SubMesh {
338
    std::string mMaterial; ///< subgroup identifier
339
    size_t mNumFaces; ///< number of faces in this sub-mesh
340
};
341
342
/// Contains data for a single mesh
343
struct Mesh {
344
    Mesh(const std::string &id) :
345
0
            mId(id) {
346
0
        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
347
0
            mNumUVComponents[i] = 2;
348
0
        }
349
0
    }
350
351
    const std::string mId;
352
    std::string mName;
353
354
    // just to check if there's some sophisticated addressing involved...
355
    // which we don't support, and therefore should warn about.
356
    std::string mVertexID;
357
358
    // Vertex data addressed by vertex indices
359
    std::vector<InputChannel> mPerVertexData;
360
361
    // actual mesh data, assembled on encounter of a <p> element. Verbose format, not indexed
362
    std::vector<aiVector3D> mPositions;
363
    std::vector<aiVector3D> mNormals;
364
    std::vector<aiVector3D> mTangents;
365
    std::vector<aiVector3D> mBitangents;
366
    std::vector<aiVector3D> mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
367
    std::vector<aiColor4D> mColors[AI_MAX_NUMBER_OF_COLOR_SETS];
368
369
    unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS];
370
371
    // Faces. Stored are only the number of vertices for each face.
372
    // 1 == point, 2 == line, 3 == triangle, 4+ == poly
373
    std::vector<size_t> mFaceSize;
374
375
    // Position indices for all faces in the sequence given in mFaceSize -
376
    // necessary for bone weight assignment
377
    std::vector<size_t> mFacePosIndices;
378
379
    // Sub-meshes in this mesh, each with a given material
380
    std::vector<SubMesh> mSubMeshes;
381
};
382
383
/// Which type of primitives the ReadPrimitives() function is going to read
384
enum PrimitiveType {
385
    Prim_Invalid,
386
    Prim_Lines,
387
    Prim_LineStrip,
388
    Prim_Triangles,
389
    Prim_TriStrips,
390
    Prim_TriFans,
391
    Prim_Polylist,
392
    Prim_Polygon
393
};
394
395
/// A skeleton controller to deform a mesh with the use of joints
396
struct Controller {
397
    // controller type
398
    ControllerType mType;
399
400
    // Morphing method if type is Morph
401
    MorphMethod mMethod;
402
403
    // the URL of the mesh deformed by the controller.
404
    std::string mMeshId;
405
406
    // accessor URL of the joint names
407
    std::string mJointNameSource;
408
409
    ///< The bind shape matrix, as array of floats. I'm not sure what this matrix actually describes, but it can't be ignored in all cases
410
    ai_real mBindShapeMatrix[16];
411
412
    // accessor URL of the joint inverse bind matrices
413
    std::string mJointOffsetMatrixSource;
414
415
    // input channel: joint names.
416
    InputChannel mWeightInputJoints;
417
    // input channel: joint weights
418
    InputChannel mWeightInputWeights;
419
420
    // Number of weights per vertex.
421
    std::vector<size_t> mWeightCounts;
422
423
    // JointIndex-WeightIndex pairs for all vertices
424
    std::vector<std::pair<size_t, size_t>> mWeights;
425
426
    std::string mMorphTarget;
427
    std::string mMorphWeight;
428
};
429
430
/// A collada material. Pretty much the only member is a reference to an effect.
431
struct Material {
432
    std::string mName;
433
    std::string mEffect;
434
};
435
436
/// Type of the effect param
437
enum ParamType {
438
    Param_Sampler,
439
    Param_Surface
440
};
441
442
/// A param for an effect. Might be of several types, but they all just refer to each other, so I summarize them
443
struct EffectParam {
444
    ParamType mType;
445
    std::string mReference; // to which other thing the param is referring to.
446
};
447
448
/// Shading type supported by the standard effect spec of Collada
449
enum ShadeType {
450
    Shade_Invalid,
451
    Shade_Constant,
452
    Shade_Lambert,
453
    Shade_Phong,
454
    Shade_Blinn
455
};
456
457
/// Represents a texture sampler in collada
458
struct Sampler {
459
    Sampler() :
460
0
            mWrapU(true),
461
0
            mWrapV(true),
462
            mMirrorU(),
463
            mMirrorV(),
464
0
            mOp(aiTextureOp_Multiply),
465
            mUVId(UINT_MAX),
466
0
            mWeighting(1.f),
467
0
            mMixWithPrevious(1.f) {}
468
469
    /// Name of image reference
470
    std::string mName;
471
472
    /// Wrap U?
473
    bool mWrapU;
474
475
    /// Wrap V?
476
    bool mWrapV;
477
478
    /// Mirror U?
479
    bool mMirrorU;
480
481
    /// Mirror V?
482
    bool mMirrorV;
483
484
    /// Blend mode
485
    aiTextureOp mOp;
486
487
    /// UV transformation
488
    aiUVTransform mTransform;
489
490
    /// Name of source UV channel
491
    std::string mUVChannel;
492
493
    /// Resolved UV channel index or UINT_MAX if not known
494
    unsigned int mUVId;
495
496
    // OKINO/MAX3D extensions from here
497
    // -------------------------------------------------------
498
499
    /// Weighting factor
500
    ai_real mWeighting;
501
502
    /// Mixing factor from OKINO
503
    ai_real mMixWithPrevious;
504
};
505
506
/// A collada effect. Can contain about anything according to the Collada spec,
507
/// but we limit our version to a reasonable subset.
508
struct Effect {
509
    /// Shading mode
510
    ShadeType mShadeType;
511
512
    /// Colors
513
    aiColor4D mEmissive, mAmbient, mDiffuse, mSpecular,
514
            mTransparent, mReflective;
515
516
    /// Textures
517
    Sampler mTexEmissive, mTexAmbient, mTexDiffuse, mTexSpecular,
518
            mTexTransparent, mTexBump, mTexReflective;
519
520
    /// Scalar factory
521
    ai_real mShininess, mRefractIndex, mReflectivity;
522
    ai_real mTransparency;
523
    bool mHasTransparency;
524
    bool mRGBTransparency;
525
    bool mInvertTransparency;
526
527
    /// local params referring to each other by their SID
528
    using ParamLibrary = std::map<std::string, Collada::EffectParam>;
529
    ParamLibrary mParams;
530
531
    // MAX3D extensions
532
    // ---------------------------------------------------------
533
    // Double-sided?
534
    bool mDoubleSided, mWireframe, mFaceted;
535
536
    Effect() :
537
0
            mShadeType(Shade_Phong),
538
0
            mEmissive(0, 0, 0, 1),
539
0
            mAmbient(0.1f, 0.1f, 0.1f, 1),
540
0
            mDiffuse(0.6f, 0.6f, 0.6f, 1),
541
0
            mSpecular(0.4f, 0.4f, 0.4f, 1),
542
0
            mTransparent(0, 0, 0, 1),
543
0
            mShininess(10.0f),
544
0
            mRefractIndex(1.f),
545
0
            mReflectivity(0.f),
546
0
            mTransparency(1.f),
547
0
            mHasTransparency(false),
548
0
            mRGBTransparency(false),
549
0
            mInvertTransparency(false),
550
0
            mDoubleSided(false),
551
0
            mWireframe(false),
552
0
            mFaceted(false) {
553
0
    }
554
};
555
556
/// An image, meaning texture
557
struct Image {
558
    std::string mFileName;
559
560
    /// Embedded image data
561
    std::vector<uint8_t> mImageData;
562
563
    /// File format hint of embedded image data
564
    std::string mEmbeddedFormat;
565
};
566
567
/// An animation channel.
568
struct AnimationChannel {
569
    /// URL of the data to animate. Could be about anything, but we support only the
570
    /// "NodeID/TransformID.SubElement" notation
571
    std::string mTarget;
572
573
    /// Source URL of the time values. Collada calls them "input". Meh.
574
    std::string mSourceTimes;
575
    /// Source URL of the value values. Collada calls them "output".
576
    std::string mSourceValues;
577
    /// Source URL of the IN_TANGENT semantic values.
578
    std::string mInTanValues;
579
    /// Source URL of the OUT_TANGENT semantic values.
580
    std::string mOutTanValues;
581
    /// Source URL of the INTERPOLATION semantic values.
582
    std::string mInterpolationValues;
583
};
584
585
/// An animation. Container for 0-x animation channels or 0-x animations
586
struct Animation {
587
    /// Anim name
588
    std::string mName;
589
590
    /// the animation channels, if any
591
    std::vector<AnimationChannel> mChannels;
592
593
    /// the sub-animations, if any
594
    std::vector<Animation *> mSubAnims;
595
596
    /// Destructor
597
1
    ~Animation() {
598
1
        for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it) {
599
0
            delete *it;
600
0
        }
601
1
    }
602
603
    /// Collect all channels in the animation hierarchy into a single channel list.
604
0
    void CollectChannelsRecursively(std::vector<AnimationChannel> &channels) {
605
0
        channels.insert(channels.end(), mChannels.begin(), mChannels.end());
606
607
0
        for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it) {
608
0
            Animation *pAnim = (*it);
609
0
            pAnim->CollectChannelsRecursively(channels);
610
0
        }
611
0
    }
612
613
    /// Combine all single-channel animations' channel into the same (parent) animation channel list.
614
0
    void CombineSingleChannelAnimations() {
615
0
        CombineSingleChannelAnimationsRecursively(this);
616
0
    }
617
618
0
    void CombineSingleChannelAnimationsRecursively(Animation *pParent) {
619
0
        std::set<std::string> childrenTargets;
620
0
        bool childrenAnimationsHaveDifferentChannels = true;
621
622
0
        for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) {
623
0
            Animation *anim = *it;
624
            // Assign the first animation name to the parent if empty.
625
            // This prevents the animation name from being lost when animations are combined
626
0
            if (mName.empty()) {
627
0
              mName = anim->mName;
628
0
            }
629
0
            CombineSingleChannelAnimationsRecursively(anim);
630
631
0
            if (childrenAnimationsHaveDifferentChannels && anim->mChannels.size() == 1 &&
632
0
                    childrenTargets.find(anim->mChannels[0].mTarget) == childrenTargets.end()) {
633
0
                childrenTargets.insert(anim->mChannels[0].mTarget);
634
0
            } else {
635
0
                childrenAnimationsHaveDifferentChannels = false;
636
0
            }
637
638
0
            ++it;
639
0
        }
640
641
        // We only want to combine animations if they have different channels
642
0
        if (childrenAnimationsHaveDifferentChannels) {
643
0
            for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) {
644
0
                Animation *anim = *it;
645
646
0
                pParent->mChannels.push_back(anim->mChannels[0]);
647
648
0
                it = pParent->mSubAnims.erase(it);
649
650
0
                delete anim;
651
0
                continue;
652
0
            }
653
0
        }
654
0
    }
655
};
656
657
/// Description of a collada animation channel which has been determined to affect the current node
658
struct ChannelEntry {
659
    const Collada::AnimationChannel *mChannel; ///< the source channel
660
    std::string mTargetId;
661
    std::string mTransformId; // the ID of the transformation step of the node which is influenced
662
    size_t mTransformIndex; // Index into the node's transform chain to apply the channel to
663
    size_t mSubElement; // starting index inside the transform data
664
665
    // resolved data references
666
    const Collada::Accessor *mTimeAccessor; ///> Collada accessor to the time values
667
    const Collada::Data *mTimeData; ///> Source data array for the time values
668
    const Collada::Accessor *mValueAccessor; ///> Collada accessor to the key value values
669
    const Collada::Data *mValueData; ///> Source data array for the key value values
670
671
    ChannelEntry() :
672
            mChannel(),
673
            mTransformIndex(),
674
            mSubElement(),
675
            mTimeAccessor(),
676
            mTimeData(),
677
            mValueAccessor(),
678
0
            mValueData() {}
679
};
680
681
} // end of namespace Collada
682
} // end of namespace Assimp
683
684
#endif // AI_COLLADAHELPER_H_INC