Skip to content

Add TypeScript FBX loader#18483

Draft
PatrickRyanMS wants to merge 3 commits into
BabylonJS:masterfrom
PatrickRyanMS:fbx-loader
Draft

Add TypeScript FBX loader#18483
PatrickRyanMS wants to merge 3 commits into
BabylonJS:masterfrom
PatrickRyanMS:fbx-loader

Conversation

@PatrickRyanMS
Copy link
Copy Markdown
Member

🤖 This PR was created by the create-pr skill.

Summary

  • Add a built-in SDK-free FBX loader with binary/ASCII parsing, scene interpretation, meshes, materials, textures, skeletons, animation groups, cameras, and lights.
  • Register and export the FBX loader through the loaders package, including dynamic loader registration and SceneLoader plugin options.
  • Add focused unit coverage for FBX parsing/interpreter behavior, loader registration, material texture handling, normal-map coordinate options, embedded texture loading, and asset-container ownership.
  • Add specs/fbx-loader documentation covering goals, requirements, architecture, normal-map handling, texture loading, and deferred grayscale bump-map conversion.

Notes

  • FBX normal-map slots default to Y-up tangent-space convention, with an opt-in Y-down loader option.
  • FBX Bump and BumpFactor slots are treated as normal-map-like inputs for compatibility until true grayscale height-to-normal conversion is implemented.
  • Embedded FBX textures use Babylon's delayed texture buffer path; sidecar textures remain supported when no embedded bytes are present.
  • Visual test assets are still being prepared and will be added before this draft is marked ready.

Validation

  • npm run compile -w @dev/loaders
  • npm run test -- packages/dev/loaders/test/unit
  • npm run format:check

npm run lint:check currently fails on pre-existing core tree-shaking manifest/side-effect-stub drift outside the FBX changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@PatrickRyanMS
Copy link
Copy Markdown
Member Author

I added two initial test models for visual testing to the assets repo. I need to build out all of the visual test variations, but wanted to get this in draft PR for review since it's large while I build out the visual test assets.

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

Snapshot stored with reference name:
refs/pull/18483/merge

Test environment:
https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18483/merge/index.html

To test a playground add it to the URL, for example:

https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18483/merge/index.html#WGZLGJ#4600

Links to test your changes to core in the published versions of the Babylon tools (does not contain changes you made to the tools themselves):

https://playground.babylonjs.com/?snapshot=refs/pull/18483/merge
https://sandbox.babylonjs.com/?snapshot=refs/pull/18483/merge
https://gui.babylonjs.com/?snapshot=refs/pull/18483/merge
https://nme.babylonjs.com/?snapshot=refs/pull/18483/merge

To test the snapshot in the playground with a playground ID add it after the snapshot query string:

https://playground.babylonjs.com/?snapshot=refs/pull/18483/merge#BCU1XR#0

If you made changes to the sandbox or playground in this PR, additional comments will be generated soon containing links to the dev versions of those tools.

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

🟢 Memory Leak Test Results

13 passed, 0 leaked out of 13 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (13)
Scenario Package
Core Feature Stack @babylonjs/core
Core Rendering Materials Shadows Stack @babylonjs/core
Core Textures Render Targets PostProcess Stack @babylonjs/core
GUI Fullscreen UI Controls @babylonjs/gui
GUI Mesh ADT Controls @babylonjs/gui
Loaders Boombox Import @babylonjs/loaders
Loaders OBJ Direct Load @babylonjs/loaders
Loaders STL Direct Load @babylonjs/loaders
Materials Library Stack @babylonjs/materials
Serializers glTF Export @babylonjs/serializers
Serializers GLB Export @babylonjs/serializers
PostProcesses Digital Rain Stack @babylonjs/post-processes
Procedural Textures Stack @babylonjs/procedural-textures

@RaananW
Copy link
Copy Markdown
Member

RaananW commented May 20, 2026

Just explaining the reason it fails on native (before fully reviewing this) -

This PR uses BigInt syntax (let nextLegacyId = -1n;), which babylon native doesn't support. if possible we should use the standard number system. if BitInt is needed we should find a good solution for native support.

@deltakosh deltakosh removed request for bghgary and sebavan May 20, 2026 15:22
@deltakosh
Copy link
Copy Markdown
Contributor

@RaananW I'll take care of it. We dont need bigint

deltakosh and others added 2 commits May 20, 2026 08:45
Remove bigint usage from the FBX loader and harden parser validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document SceneLoader callback parameters for FBX public loader methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s).
To prevent this PR from going to the changelog marked it with the "skip changelog" label.

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

Snapshot stored with reference name:
refs/pull/18483/merge

Test environment:
https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18483/merge/index.html

To test a playground add it to the URL, for example:

https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/18483/merge/index.html#WGZLGJ#4600

Links to test your changes to core in the published versions of the Babylon tools (does not contain changes you made to the tools themselves):

https://playground.babylonjs.com/?snapshot=refs/pull/18483/merge
https://sandbox.babylonjs.com/?snapshot=refs/pull/18483/merge
https://gui.babylonjs.com/?snapshot=refs/pull/18483/merge
https://nme.babylonjs.com/?snapshot=refs/pull/18483/merge

To test the snapshot in the playground with a playground ID add it after the snapshot query string:

https://playground.babylonjs.com/?snapshot=refs/pull/18483/merge#BCU1XR#0

If you made changes to the sandbox or playground in this PR, additional comments will be generated soon containing links to the dev versions of those tools.

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

⚡ Performance Test Results

🟢 All performance tests passed — no regressions detected.

@bjsplat
Copy link
Copy Markdown
Collaborator

bjsplat commented May 20, 2026

🟢 Memory Leak Test Results

13 passed, 0 leaked out of 13 scenarios

🟢 All memory leak tests passed — no leaks detected.

Passed Scenarios (13)
Scenario Package
Core Feature Stack @babylonjs/core
Core Rendering Materials Shadows Stack @babylonjs/core
Core Textures Render Targets PostProcess Stack @babylonjs/core
GUI Fullscreen UI Controls @babylonjs/gui
GUI Mesh ADT Controls @babylonjs/gui
Loaders Boombox Import @babylonjs/loaders
Loaders OBJ Direct Load @babylonjs/loaders
Loaders STL Direct Load @babylonjs/loaders
Materials Library Stack @babylonjs/materials
Serializers glTF Export @babylonjs/serializers
Serializers GLB Export @babylonjs/serializers
PostProcesses Digital Rain Stack @babylonjs/post-processes
Procedural Textures Stack @babylonjs/procedural-textures

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the specs folder a thing now? I only see one file in this folder right now for tree shaking.

Copy link
Copy Markdown
Contributor

@Popov72 Popov72 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review comments from Copilot CLI. I left out the quality-gate notes as requested.


if (mapping === "AllSame") {
// All polygons use material index 0
const indices = new Int32Array(polygonCount);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For MappingInformationType === "AllSame", this always assigns material slot 0. FBX files can store a non-zero single slot in Materials[0]; with multiple connected materials that means the mesh renders with the wrong material. Please read the Materials array for the all-same case and preserve a non-zero slot through the later all-same optimization (or assign the resolved material directly).


// Convert indices
let indices: Uint32Array;
if (rawIndices instanceof Int32Array) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASCII FBX shorthand arrays are parsed as Float64Array, so shape Indexes from ASCII files will not pass this Int32Array/Uint32Array check and the morph target is silently dropped. Please accept/convert numeric arrays here the same way geometry and skeleton index arrays do.

return [];
}

const keyTimes = toInt64Array(keyTimeNode.properties[0]?.value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ASCII FBX files, KeyAttrFlags and KeyAttrRefCount are parsed as Float64Array, but the helpers below only accept Int32Array. That causes constant/cubic interpolation flags to be ignored and curves default to linear. Please convert the ASCII numeric arrays before interpreting key attributes.

if (model.geometry && model.subType === "Mesh") {
// Create mesh
if (nameFilter && !nameFilter(model.name)) {
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning here skips the entire subtree when a parent mesh does not match meshesNames. If a requested mesh is nested under an unrequested mesh, importMeshAsync will never recurse far enough to import it. Please continue traversing children (likely with an uncreated/placeholder parent or adjusted hierarchy handling) instead of returning immediately.

*/

/** Individual property value within an FBX node */
export type FBXPropertyValue = boolean | number | string | Float32Array | Float64Array | Int32Array | Uint8Array;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FBX object IDs are 64-bit values, but the shared IR stores all int64 values as JS number. IDs above Number.MAX_SAFE_INTEGER can lose precision and collide in connection maps, which can miswire materials, skins, animations, and model hierarchy. Please preserve 64-bit IDs exactly (for example as bigint/string for IDs) or reject unsafe IDs with a clear parse error.

throw new Error("Not a valid binary FBX file");
}

const version = view.getUint32(23, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A truncated binary file that contains the 21-byte magic but is shorter than the 27-byte header reaches getUint32(23, true) and throws a raw RangeError. Please validate buffer.byteLength >= HEADER_SIZE before decoding the version so malformed input produces a controlled FBX parse error.

@@ -0,0 +1,236 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { NullEngine } from "core/Engines";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import trips the repo lint rule babylonjs/no-directory-barrel-imports because core/Engines resolves through a directory index. Please import NullEngine from core/Engines/nullEngine instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants