From 36c3a19aa853d75c7cb2bb843dd75468f001ab66 Mon Sep 17 00:00:00 2001 From: Kyungjoon Ko Date: Fri, 6 Feb 2026 21:34:23 +0900 Subject: [PATCH 01/10] Fix invalid verifying in OpenDDLParser::parseStringLiteral (#6314) Co-authored-by: Kim Kulling --- contrib/openddlparser/code/OpenDDLParser.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/openddlparser/code/OpenDDLParser.cpp b/contrib/openddlparser/code/OpenDDLParser.cpp index 1e14f5fd0..e065f9410 100644 --- a/contrib/openddlparser/code/OpenDDLParser.cpp +++ b/contrib/openddlparser/code/OpenDDLParser.cpp @@ -796,10 +796,13 @@ char *OpenDDLParser::parseStringLiteral(char *in, char *end, Value **stringData) if (*start == '\"') { ++start; ++in; - while (*in != '\"' && in != end) { + while (in != end && *in != '\"') { ++in; ++len; } + if (in == end) { + return in; + } *stringData = ValueAllocator::allocPrimData(Value::ValueType::ddl_string, len); ::strncpy((char *)(*stringData)->m_data, start, len); From e38451ff6cd1e9e581d022de6d50c18217532804 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 10 Feb 2026 20:26:07 +0100 Subject: [PATCH 02/10] Add developer quickstart section to Readme (#6474) * Add developer quickstart section to Readme -Added a quickstart guide for developers to clone and build the project. - closes https://github.com/assimp/assimp/issues/6469 * Change headings from H3 to H2 in Readme.md --- Readme.md | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 34f9caf21..928698f82 100644 --- a/Readme.md +++ b/Readme.md @@ -3,7 +3,7 @@ Open Asset Import Library (assimp) Open Asset Import Library is a library that loads various 3D file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export. -### Current project status +## Current project status ![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9973693b7bdd4543b07084d5d9cf4745)](https://www.codacy.com/gh/assimp/assimp/dashboard?utm_source=github.com&utm_medium=referral&utm_content=assimp/assimp&utm_campaign=Badge_Grade) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=assimp_assimp&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=assimp_assimp) @@ -19,29 +19,38 @@ Additionally, assimp features various __mesh post-processing tools__: normals an ## Project activity ![Alt](https://repobeats.axiom.co/api/embed/997f84e5f9fcf772da1e687f3a4f3a8afdbf4cf0.svg "Repobeats analytics image") -### Documentation +## Developer quickstart +```bash +git clone https://github.com/assimp/assimp +cd assimp +cmake -G Ninja -DASSIMP_BUILD_TESTS=off -DASSIMP_INSTALL=off -S . -B build +cd build +ninja +``` + +## Documentation Read [our latest documentation](https://the-asset-importer-lib-documentation.readthedocs.io/en/latest/). -### Pre-built binaries +## Pre-built binaries Download binaries from [our Itch Projectspace](https://kimkulling.itch.io/the-asset-importer-lib). -### Test data +## Test data Clone [our model database for testing purposes](https://github.com/assimp/assimp-mdb). -### Communities +## Communities - Ask questions at [the Assimp Discussion Board](https://github.com/assimp/assimp/discussions). - Find us on [discord](https://discord.gg/kKazXMXDy2) - Ask [the Assimp community on Reddit](https://www.reddit.com/r/Assimp/). - Ask on [StackOverflow with the assimp-tag](http://stackoverflow.com/questions/tagged/assimp?sort=newest). - Nothing has worked? File a question or an issue report at [The Assimp-Issue Tracker](https://github.com/assimp/assimp/issues) -#### Supported file formats +## Supported file formats See [the complete list of supported formats](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md). -### Building +## Building Start by reading [our build instructions](https://github.com/assimp/assimp/blob/master/Build.md). We are available in vcpkg, and our build system is CMake; if you used CMake before there is a good chance you know what to do. -### Ports +## Ports * [Android](port/AndroidJNI/README.md) * [Python](port/PyAssimp/README.md) * [.NET](https://github.com/Saalvage/AssimpNetter) @@ -54,11 +63,11 @@ Start by reading [our build instructions](https://github.com/assimp/assimp/blob/ * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port. * [Rust](https://github.com/jkvargas/russimp) -### Other tools +## Other tools [Qt5-ModelViewer](https://github.com/sharjith/ModelViewer-Qt5) is a powerful viewer based on Qt5 and Assimp's import and export abilities.
[Assimp-Viewer](https://github.com/assimp/assimp_view) is an experimental implementation for an Asset-Viewer based on ImGUI and Assimp (experimental). -#### Repository structure #### +### Repository structure Open Asset Import Library is implemented in C++. The directory structure looks like this: ```txt @@ -85,7 +94,7 @@ test Unit- and regression tests, test suite of models tools Tools (old assimp viewer, command line `assimp`) samples Small number of samples to illustrate possible use cases for Assimp ``` -### Contributing +## Contributing We would greatly appreciate for you to contribute to assimp. The easiest way to get involved is to submit a pull request with your changes against the main repository's `master` branch. @@ -113,7 +122,7 @@ You can support the project with your organization. Your logo will show up here -### License ### +## License ### Our license is based on the modified, __3-clause BSD__-License. An _informal_ summary is: do whatever you want, but include Assimp's license text with your product - From b4c7912d2112ed21662cb11f9497425e712dabce Mon Sep 17 00:00:00 2001 From: ZhangJY <60053759+zhangjy1014@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:19:12 +0800 Subject: [PATCH 03/10] Fix heap-buffer-overflow in OpenGEXImporter::handleIndexArrayNode() (#6470) The mColors[0] array was allocated with m_numColors elements but indexed up to mNumVertices (= numFaces * 3), causing an out-of-bounds write when the color count is less than the vertex count. Allocate mColors[0] with mNumVertices (matching mNormals and mTextureCoords), and add a bounds check on idx before reading from the source color array. Fixes #6468 Co-authored-by: Kim Kulling --- code/AssetLib/OpenGEX/OpenGEXImporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/OpenGEX/OpenGEXImporter.cpp b/code/AssetLib/OpenGEX/OpenGEXImporter.cpp index 4edbcc9ed..b377a191e 100644 --- a/code/AssetLib/OpenGEX/OpenGEXImporter.cpp +++ b/code/AssetLib/OpenGEX/OpenGEXImporter.cpp @@ -892,7 +892,7 @@ void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * m_currentMesh->mVertices = new aiVector3D[m_currentMesh->mNumVertices]; bool hasColors(false); if (m_currentVertices.m_numColors > 0) { - m_currentMesh->mColors[0] = new aiColor4D[m_currentVertices.m_numColors]; + m_currentMesh->mColors[0] = new aiColor4D[m_currentMesh->mNumVertices]; hasColors = true; } bool hasNormalCoords(false); @@ -924,7 +924,7 @@ void OpenGEXImporter::handleIndexArrayNode(ODDLParser::DDLNode *node, aiScene * ai_assert(index < m_currentMesh->mNumVertices); aiVector3D &pos = (m_currentVertices.m_vertices[idx]); m_currentMesh->mVertices[index].Set(pos.x, pos.y, pos.z); - if (hasColors) { + if (hasColors && static_cast(idx) < m_currentVertices.m_numColors) { aiColor4D &col = m_currentVertices.m_colors[idx]; m_currentMesh->mColors[0][index] = col; } From 8acd2c964ebb533c7053f64f232212966b54c740 Mon Sep 17 00:00:00 2001 From: Oliver Chang Date: Tue, 17 Feb 2026 20:57:40 +1100 Subject: [PATCH 04/10] glTF2: Fix heap-buffer-overflow in Accessor validation and size calculation (#6473) * glTF2: Fix heap-buffer-overflow in Accessor validation and size calculation This patch fixes a heap-buffer-overflow in `glTF2::Accessor::ExtractData` caused by incorrect bounds validation and available size reporting. The vulnerability stemmed from two issues in `glTF2Asset.inl`: 1. **Underestimated validation in `Accessor::Read`**: The logic used `GetBytesPerComponent() * count` to validate the required buffer size. This failed to account for the actual `stride`, allowing accessors to pass validation even if their total footprint (including stride) exceeded the buffer view. 2. **Incorrect size reporting in `Accessor::GetMaxByteSize`**: The function returned the total `bufferView->byteLength` while ignoring the `byteOffset`. Since the accessor data starts at `byteOffset`, the actual available space is `byteLength - byteOffset`. This led `ExtractData` to permit reads that extended beyond the end of the allocated buffer. Changes: * Modified `Accessor::Read` to use `GetStride() * count` for length validation. * Updated `Accessor::GetMaxByteSize` to correctly return `bufferView->byteLength - byteOffset` for standard accessors and `sparse->data.size()` for sparse accessors. Co-authored-by: CodeMender Fixes: https://issues.oss-fuzz.com/issues/483102963 * address comment --------- Co-authored-by: CodeMender Co-authored-by: Kim Kulling --- code/AssetLib/glTF2/glTF2Asset.inl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 569ce9972..9a67dbf66 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -888,7 +888,9 @@ inline void Accessor::Read(Value &obj, Asset &r) { if (bufferView) { // Check length - unsigned long long byteLength = (unsigned long long)GetBytesPerComponent() * (unsigned long long)count; + unsigned long long byteLength = count > 0 + ? (unsigned long long)GetStride() * (unsigned long long)(count - 1) + (unsigned long long)GetElementSize() + : 0; // handle integer overflow if (byteLength < count) { @@ -1004,7 +1006,15 @@ inline size_t Accessor::GetMaxByteSize() { if (decodedBuffer) return decodedBuffer->byteLength; - return (bufferView ? bufferView->byteLength : sparse->data.size()); + if (sparse) { + return sparse->data.size(); + } + + if (bufferView) { + return byteOffset <= bufferView->byteLength ? bufferView->byteLength - byteOffset : 0; + } + + return 0; } template From 970f8691dd5e54bd7667438efe5f2cd8e261d199 Mon Sep 17 00:00:00 2001 From: Oliver Chang Date: Wed, 18 Feb 2026 05:16:53 +1100 Subject: [PATCH 05/10] FBXExporter: Fix stack-use-after-scope in WriteObjects (#6472) The FBX exporter was unconditionally dereferencing a map iterator (`tp_elem`) even when the key was not found (i.e., `tp_elem == tpath_by_image.end()`). This resulted in a stack-use-after-scope error when accessing `tp_elem->second` to populate "FileName" and "RelativeFilename" nodes, as dereferencing the end iterator of the map accessed invalid stack memory (the map's sentinel). The code already correctly initialized a local `tfile_path` variable based on whether the iterator was valid. This patch updates the `AddChild` calls to use `tfile_path` instead of dereferencing the potentially invalid iterator. Fixes: https://issues.oss-fuzz.com/issues/465494996 Co-authored-by: CodeMender Co-authored-by: Kim Kulling --- code/AssetLib/FBX/FBXExporter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/FBX/FBXExporter.cpp b/code/AssetLib/FBX/FBXExporter.cpp index edb78a764..90ce22a75 100644 --- a/code/AssetLib/FBX/FBXExporter.cpp +++ b/code/AssetLib/FBX/FBXExporter.cpp @@ -1766,8 +1766,8 @@ void FBXExporter::WriteObjects () { // can't easily determine which texture path will be correct, // so just store what we have in every field. // these being incorrect is a common problem with FBX anyway. - tnode.AddChild("FileName", tp_elem->second); - tnode.AddChild("RelativeFilename", tp_elem->second); + tnode.AddChild("FileName", tfile_path); + tnode.AddChild("RelativeFilename", tfile_path); tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0)); tnode.AddChild("ModelUVScaling", double(1.0), double(1.0)); tnode.AddChild("Texture_Alpha_Source", "None"); From 21607dfb756a1119a9607bf19cc2de5fba3e2d5f Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Sat, 21 Feb 2026 10:19:02 -0800 Subject: [PATCH 06/10] Obj: Fix heap-buffer-overflow in getFace via vertical tabs (#6540) The `ObjFileParser::getFace` method failed to recognize the vertical tab character (`\v`, 0x0b) as a separator. While the `IsSpaceOrNewLine` utility handles most whitespace (space, tab, CR, LF, FF), it excludes `\v`. When encountering a vertical tab, the parser fell through to an `else` block that calls `::atoi(&(*m_DataIt))`. Because `atoi` treats `\v` as whitespace per the C standard, it skips the character and continues reading. If `\v` is located at the end of the buffer (e.g., followed by a newline at the buffer boundary), `atoi` can read past the allocated memory, triggering a heap-buffer-overflow. This fix explicitly checks for `\v` and treats it as a separator, resetting the position counter and preventing the invalid `atoi` call. Verified with AddressSanitizer and confirmed that all 584 existing unit tests pass. Fixes: https://issues.oss-fuzz.com/issues/476180586 Signed-off-by: Bill Wendling Co-authored-by: Meder Kydyraliev Co-authored-by: CodeMender --- code/AssetLib/Obj/ObjFileParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/Obj/ObjFileParser.cpp b/code/AssetLib/Obj/ObjFileParser.cpp index 921ae6c3d..cc843468d 100644 --- a/code/AssetLib/Obj/ObjFileParser.cpp +++ b/code/AssetLib/Obj/ObjFileParser.cpp @@ -467,7 +467,7 @@ void ObjFileParser::getFace(aiPrimitiveType type) { ASSIMP_LOG_ERROR("Obj: Separator unexpected in point statement"); } iPos++; - } else if (IsSpaceOrNewLine(*m_DataIt)) { + } else if (IsSpaceOrNewLine(*m_DataIt) || *m_DataIt == '\v') { iPos = 0; } else { //OBJ USES 1 Base ARRAYS!!!! From 3e188e6768739d48a58465fdbf339674c4bc70de Mon Sep 17 00:00:00 2001 From: Oliver Chang Date: Mon, 23 Feb 2026 07:27:05 +1100 Subject: [PATCH 07/10] HL1MDLLoader: fix segfault in destructors (#6537) In `HL1MDLLoader::read_animations`, the arrays for `scene_->mAnimations` and `scene_animation->mChannels` were allocated using `new T*[count]`. This performs default initialization, which leaves the pointer elements with indeterminate (garbage) values. If an exception (such as a `DeadlyImportError` from malformed input) is thrown during the loop populating these arrays, the `aiScene` or `aiAnimation` destructors are invoked during stack unwinding. These destructors iterate through the allocated arrays and call `delete` on each element. Because the arrays contained garbage values for indices not yet reached by the loader, the destructor attempted to delete invalid memory addresses, leading to a segmentation fault. This patch changes the allocations to use value-initialization (`new T*[count]()`), ensuring all pointers are initialized to `nullptr`. Since `delete nullptr` is a safe no-op, the destructors can now safely clean up partially initialized objects during an exception. Fixes: https://issues.oss-fuzz.com/issues/483188619 Co-authored-by: CodeMender Co-authored-by: Kim Kulling --- code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp index 3949f76a2..e2dce6cf9 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -971,7 +971,7 @@ void HL1MDLLoader::read_animations() { pseqdesc = get_buffer_data(header_->seqindex, header_->numseq); - aiAnimation **scene_animations_ptr = scene_->mAnimations = new aiAnimation *[scene_->mNumAnimations]; + aiAnimation **scene_animations_ptr = scene_->mAnimations = new aiAnimation *[scene_->mNumAnimations](); for (int sequence = 0; sequence < header_->numseq; ++sequence, ++pseqdesc) { pseqgroup = get_buffer_data(header_->seqgroupindex + pseqdesc->seqgroup * sizeof(SequenceGroup_HL1), 1); @@ -992,7 +992,7 @@ void HL1MDLLoader::read_animations() { scene_animation->mTicksPerSecond = pseqdesc->fps; scene_animation->mDuration = static_cast(pseqdesc->fps) * pseqdesc->numframes; scene_animation->mNumChannels = static_cast(header_->numbones); - scene_animation->mChannels = new aiNodeAnim *[scene_animation->mNumChannels]; + scene_animation->mChannels = new aiNodeAnim *[scene_animation->mNumChannels](); for (int bone = 0; bone < header_->numbones; bone++, ++pbone, ++panim) { aiNodeAnim *node_anim = scene_animation->mChannels[bone] = new aiNodeAnim(); From 3e672ff856b0bad35f478cc11acdd903674066ee Mon Sep 17 00:00:00 2001 From: Carter Rennick Date: Mon, 23 Feb 2026 15:26:18 -0500 Subject: [PATCH 08/10] Fixed compiler error on platforms where unsigned int is not 32 bit (#6541) Co-authored-by: Kim Kulling --- code/PostProcessing/TriangulateProcess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index cc92b441d..6c9bc033c 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -234,7 +234,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) { ++numOut; } else { numOut += face.mNumIndices-2; - max_out = std::max(max_out,face.mNumIndices); + max_out = std::max(max_out,face.mNumIndices); } } From 10be273aea51421aedf214f793b0db52dd151537 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 25 Feb 2026 11:13:44 +0100 Subject: [PATCH 09/10] Update supported version in SECURITY.md (#6459) * Update supported version in SECURITY.md - Preparation for bugfix release v6.0.5 * Update CMakeLists.txt * Update aiGetVersionPatch test to expect version 5 --- CMakeLists.txt | 2 +- SECURITY.md | 2 +- test/unit/utVersion.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b603b2ef4..a61cd4d9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,7 +104,7 @@ IF(ASSIMP_HUNTER_ENABLED) ADD_DEFINITIONS(-DASSIMP_USE_HUNTER) ENDIF() -PROJECT(Assimp VERSION 6.0.4 +PROJECT(Assimp VERSION 6.0.5 LANGUAGES C CXX DESCRIPTION "Open Asset Import Library (Assimp) is a library to import various well-known 3D model formats in a uniform manner." ) diff --git a/SECURITY.md b/SECURITY.md index b5cd16831..987e899ef 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ The current version of Assimp that's being supported with security updates: | Version | Supported | | ------- | ------------------ | -| 6.0.2 | :white_check_mark: | +| 6.0.5 | :white_check_mark: | ## Reporting a Vulnerability diff --git a/test/unit/utVersion.cpp b/test/unit/utVersion.cpp index 7f0a373e3..4eb133e29 100644 --- a/test/unit/utVersion.cpp +++ b/test/unit/utVersion.cpp @@ -61,7 +61,7 @@ TEST_F( utVersion, aiGetVersionMinorTest ) { } TEST_F( utVersion, aiGetVersionPatchTest ) { - EXPECT_EQ(aiGetVersionPatch(), 4U ); + EXPECT_EQ(aiGetVersionPatch(), 5U ); } TEST_F( utVersion, aiGetCompileFlagsTest ) { From 8ef1461cb899e273ec30ee680df7e840e7f62834 Mon Sep 17 00:00:00 2001 From: EarendelArc <69977172+EarendelArc@users.noreply.github.com> Date: Thu, 26 Feb 2026 22:33:03 +0800 Subject: [PATCH 10/10] glTF2: Preserve interpolation type and CubicSpline tangent data in animation keys (#6543) fix(gltf2): preserve interpolation type and CubicSpline tangents The glTF2 importer previously ignored the mInterpolation field and discarded CubicSpline tangent data, leading to incorrect animation playback. - Implement MapInterpolation() to map glTF2 interpolation types to Assimp. - Set mInterpolation for all aiVectorKey and aiQuatKey instances. - For CUBICSPLINE samplers, store [in-tangent, value, out-tangent] triplets (N x 3 keys) instead of discarding tangents. - Fixes rendering consistency for InterpolationTest.glb where STEP, LINEAR, and CUBICSPLINE rows previously rendered identically. Affected channels: mPositionKeys, mRotationKeys, mScalingKeys. Co-authored-by: Kim Kulling --- code/AssetLib/glTF2/glTF2Importer.cpp | 113 ++++++++++++++++++++------ 1 file changed, 88 insertions(+), 25 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 13c8bf090..a83526a27 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -1316,6 +1316,15 @@ struct AnimationSamplers { struct vec4f { float x, y, z, w; }; + +static aiAnimInterpolation MapInterpolation(Interpolation interp) { + switch (interp) { + case Interpolation_STEP: return aiAnimInterpolation_Step; + case Interpolation_CUBICSPLINE: return aiAnimInterpolation_Cubic_Spline; + default: return aiAnimInterpolation_Linear; + } +} + aiNodeAnim *CreateNodeAnim(glTF2::Asset &, Node &node, AnimationSamplers &samplers) { aiNodeAnim *anim = new aiNodeAnim(); @@ -1327,7 +1336,6 @@ aiNodeAnim *CreateNodeAnim(glTF2::Asset &, Node &node, AnimationSamplers &sample if (samplers.translation && samplers.translation->input && samplers.translation->output) { float *times = nullptr; samplers.translation->input->ExtractData(times); - //aiVector3D *values = nullptr; vec4f *tmp_values = nullptr; size_t numItems = samplers.translation->output->ExtractData(tmp_values); aiVector3D *values = new aiVector3D[numItems]; @@ -1338,13 +1346,31 @@ aiNodeAnim *CreateNodeAnim(glTF2::Asset &, Node &node, AnimationSamplers &sample } delete[] tmp_values; - anim->mNumPositionKeys = static_cast(samplers.translation->input->count); - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - unsigned int ii = (samplers.translation->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; - for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { - anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mPositionKeys[i].mValue = values[ii]; - ii += (samplers.translation->interpolation == Interpolation_CUBICSPLINE) ? 3 : 1; + const bool isCubic = (samplers.translation->interpolation == Interpolation_CUBICSPLINE); + const aiAnimInterpolation interpType = MapInterpolation(samplers.translation->interpolation); + const unsigned int numLogicalKeys = static_cast(samplers.translation->input->count); + + if (isCubic) { + // Store as triplets: in-tangent, value, out-tangent per logical key + anim->mNumPositionKeys = numLogicalKeys * 3; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (unsigned int i = 0; i < numLogicalKeys; ++i) { + unsigned int srcBase = i * 3; + unsigned int dstBase = i * 3; + for (unsigned int t = 0; t < 3; ++t) { + anim->mPositionKeys[dstBase + t].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[dstBase + t].mValue = values[srcBase + t]; + anim->mPositionKeys[dstBase + t].mInterpolation = interpType; + } + } + } else { + anim->mNumPositionKeys = numLogicalKeys; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[i].mValue = values[i]; + anim->mPositionKeys[i].mInterpolation = interpType; + } } delete[] times; delete[] values; @@ -1362,16 +1388,36 @@ aiNodeAnim *CreateNodeAnim(glTF2::Asset &, Node &node, AnimationSamplers &sample samplers.rotation->input->ExtractData(times); aiQuaternion *values = nullptr; samplers.rotation->output->ExtractData(values); - anim->mNumRotationKeys = static_cast(samplers.rotation->input->count); - anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; - unsigned int ii = (samplers.rotation->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; - for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { - anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mRotationKeys[i].mValue.x = values[ii].w; - anim->mRotationKeys[i].mValue.y = values[ii].x; - anim->mRotationKeys[i].mValue.z = values[ii].y; - anim->mRotationKeys[i].mValue.w = values[ii].z; - ii += (samplers.rotation->interpolation == Interpolation_CUBICSPLINE) ? 3 : 1; + const bool isCubic = (samplers.rotation->interpolation == Interpolation_CUBICSPLINE); + const aiAnimInterpolation interpType = MapInterpolation(samplers.rotation->interpolation); + const unsigned int numLogicalKeys = static_cast(samplers.rotation->input->count); + + if (isCubic) { + anim->mNumRotationKeys = numLogicalKeys * 3; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + for (unsigned int i = 0; i < numLogicalKeys; ++i) { + unsigned int srcBase = i * 3; + unsigned int dstBase = i * 3; + for (unsigned int t = 0; t < 3; ++t) { + anim->mRotationKeys[dstBase + t].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[dstBase + t].mValue.x = values[srcBase + t].w; + anim->mRotationKeys[dstBase + t].mValue.y = values[srcBase + t].x; + anim->mRotationKeys[dstBase + t].mValue.z = values[srcBase + t].y; + anim->mRotationKeys[dstBase + t].mValue.w = values[srcBase + t].z; + anim->mRotationKeys[dstBase + t].mInterpolation = interpType; + } + } + } else { + anim->mNumRotationKeys = numLogicalKeys; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[i].mValue.x = values[i].w; + anim->mRotationKeys[i].mValue.y = values[i].x; + anim->mRotationKeys[i].mValue.z = values[i].y; + anim->mRotationKeys[i].mValue.w = values[i].z; + anim->mRotationKeys[i].mInterpolation = interpType; + } } delete[] times; delete[] values; @@ -1390,13 +1436,30 @@ aiNodeAnim *CreateNodeAnim(glTF2::Asset &, Node &node, AnimationSamplers &sample samplers.scale->input->ExtractData(times); aiVector3D *values = nullptr; samplers.scale->output->ExtractData(values); - anim->mNumScalingKeys = static_cast(samplers.scale->input->count); - anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; - unsigned int ii = (samplers.scale->interpolation == Interpolation_CUBICSPLINE) ? 1 : 0; - for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { - anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mScalingKeys[i].mValue = values[ii]; - ii += (samplers.scale->interpolation == Interpolation_CUBICSPLINE) ? 3 : 1; + const bool isCubic = (samplers.scale->interpolation == Interpolation_CUBICSPLINE); + const aiAnimInterpolation interpType = MapInterpolation(samplers.scale->interpolation); + const unsigned int numLogicalKeys = static_cast(samplers.scale->input->count); + + if (isCubic) { + anim->mNumScalingKeys = numLogicalKeys * 3; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + for (unsigned int i = 0; i < numLogicalKeys; ++i) { + unsigned int srcBase = i * 3; + unsigned int dstBase = i * 3; + for (unsigned int t = 0; t < 3; ++t) { + anim->mScalingKeys[dstBase + t].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[dstBase + t].mValue = values[srcBase + t]; + anim->mScalingKeys[dstBase + t].mInterpolation = interpType; + } + } + } else { + anim->mNumScalingKeys = numLogicalKeys; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { + anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[i].mValue = values[i]; + anim->mScalingKeys[i].mInterpolation = interpType; + } } delete[] times; delete[] values;