babylonjs-loaders 9.12.0 → 9.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7215,6 +7215,14 @@ declare namespace BABYLON {
7215
7215
  * @returns a promise containing the loaded meshes, particles, skeletons and animations
7216
7216
  */
7217
7217
  importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, _onProgress?: (event: ISceneLoaderProgressEvent) => void, _fileName?: string): Promise<ISceneLoaderAsyncResult>;
7218
+ /**
7219
+ * Detects a PlayCanvas-style `lod-meta.json` payload and, if found, creates a streaming mesh for it.
7220
+ * @param scene hosting scene
7221
+ * @param data loaded file data
7222
+ * @param rootUrl root url the metadata's relative paths resolve against
7223
+ * @returns the streaming mesh, or null when the data is not SOG LOD metadata
7224
+ */
7225
+ private _tryCreateLODStream;
7218
7226
  private static _BuildPointCloud;
7219
7227
  private static _BuildMesh;
7220
7228
  private _unzipWithFFlateAsync;
@@ -7410,11 +7418,511 @@ declare namespace BABYLON {
7410
7418
  * @param dataOrFiles Either the SOGRootData or a Map of filenames to Uint8Array file data (including meta.json)
7411
7419
  * @param rootUrl Base URL to load webp files from (if dataOrFiles is SOGRootData)
7412
7420
  * @param scene The Babylon.js scene
7421
+ * @param computeCpuPositions When true (default), means_l/means_u are read back on the CPU and `pack.positions`
7422
+ * is decoded for the sort worker / bounding box. Pass false when the caller will instead read the decoded
7423
+ * centers back from the GPU work buffer — then every attribute (including means) uses the fast direct
7424
+ * ImageBitmap upload (no `getImageData` readback) and `pack.positions` is left empty.
7413
7425
  * @returns Parsed splat info with `sogTextures` populated.
7414
7426
  */
7415
- export function ParseSogMetaAsTextures(dataOrFiles: SOGRootData | Map<string, Uint8Array>, rootUrl: string, scene: Scene): Promise<IParsedSplat>;
7427
+ export function ParseSogMetaAsTextures(dataOrFiles: SOGRootData | Map<string, Uint8Array>, rootUrl: string, scene: Scene, computeCpuPositions?: boolean): Promise<IParsedSplat>;
7428
+
7429
+
7430
+
7431
+
7432
+ /** This file must only contain pure code and pure imports */
7433
+ /**
7434
+ * Shared shader names for the SOG -> decoded work-buffer copy pass.
7435
+ */
7436
+ export const GaussianSplattingWorkBufferShaderName = "gsSogDecodeToWorkBuffer";
7437
+ /**
7438
+ * Pass-through vertex shader (GLSL): the geometry is a fullscreen triangle already in NDC.
7439
+ */
7440
+ export const GaussianSplattingWorkBufferVertexShaderGLSL = "precision highp float;\nattribute vec3 position;\nvoid main() {\n gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n";
7441
+ /**
7442
+ * Fragment shader (GLSL/WebGL2): decodes one SOG source file into the decoded GS work-buffer layout,
7443
+ * writing each splat into its allocated pixel. Mirrors the USE_SOG decode in ShadersInclude/gaussianSplatting.fx
7444
+ * but outputs the decoded MRT (center, covA, covB, color) consumed by the standard (non-SOG) draw path.
7445
+ *
7446
+ * MRT layout: 0 = center (x,y,z,1), 1 = covA (Sigma00,01,02,11), 2 = covB (Sigma12,22,0,0), 3 = color (rgba).
7447
+ */
7448
+ export const GaussianSplattingWorkBufferFragmentShaderGLSL = "precision highp float;\nprecision highp int;\n\nuniform sampler2D sogMeansLTex;\nuniform sampler2D sogMeansUTex;\nuniform sampler2D sogScalesTex;\nuniform sampler2D sogQuatsTex;\nuniform sampler2D sogSh0Tex;\nuniform sampler2D sogCodebookTex;\n\nuniform vec3 sogMeansMin;\nuniform vec3 sogMeansMax;\nuniform vec3 sogScalesMin;\nuniform vec3 sogScalesMax;\nuniform vec4 sogSh0Min;\nuniform vec4 sogSh0Max;\nuniform int uVersion;\nuniform int uOffset;\nuniform int uCount;\nuniform int uDestWidth;\nuniform int uSrcWidth;\n\nlayout(location = 0) out vec4 glFragData[4];\n\nmat3 transposeM(mat3 m) {\n return mat3(m[0][0], m[1][0], m[2][0], m[0][1], m[1][1], m[2][1], m[0][2], m[1][2], m[2][2]);\n}\n\nvoid main() {\n ivec2 p = ivec2(gl_FragCoord.xy);\n int global = p.y * uDestWidth + p.x;\n if (global < uOffset || global >= uOffset + uCount) {\n discard;\n }\n int k = global - uOffset;\n ivec2 src = ivec2(k - (k / uSrcWidth) * uSrcWidth, k / uSrcWidth);\n\n vec3 mL = texelFetch(sogMeansLTex, src, 0).xyz;\n vec3 mU = texelFetch(sogMeansUTex, src, 0).xyz;\n vec3 sRaw = texelFetch(sogScalesTex, src, 0).xyz;\n vec4 qRaw = texelFetch(sogQuatsTex, src, 0);\n vec4 c0 = texelFetch(sogSh0Tex, src, 0);\n\n // Position: q16 = (u<<8)|l normalized; n = lerp(min,max,q16); pos = sign(n)*(exp(|n|)-1)\n vec3 q16 = (mU * 256.0 + mL) * (255.0 / 65535.0);\n vec3 nPos = mix(sogMeansMin, sogMeansMax, q16);\n vec3 center = sign(nPos) * (exp(abs(nPos)) - vec3(1.0));\n\n // Scale (v1: lerp+exp ; v2: codebook lookup)\n vec3 splatScale;\n if (uVersion == 2) {\n vec3 sIdx = floor(sRaw * 255.0 + 0.5);\n splatScale.x = exp(texelFetch(sogCodebookTex, ivec2(int(sIdx.x), 0), 0).r);\n splatScale.y = exp(texelFetch(sogCodebookTex, ivec2(int(sIdx.y), 0), 0).r);\n splatScale.z = exp(texelFetch(sogCodebookTex, ivec2(int(sIdx.z), 0), 0).r);\n } else {\n splatScale = exp(mix(sogScalesMin, sogScalesMax, sRaw));\n }\n\n // Quaternion (largest-omitted, mode in alpha as 252 + omitted-index)\n const float invSqrt2 = 0.70710678118;\n vec3 qabc = (qRaw.xyz - vec3(0.5)) * 2.0 * invSqrt2;\n int qMode = int(qRaw.w * 255.0 + 0.5) - 252;\n float qd = sqrt(max(0.0, 1.0 - dot(qabc, qabc)));\n vec4 quat;\n if (qMode == 0) {\n quat = vec4(qd, qabc.x, qabc.y, qabc.z);\n } else if (qMode == 1) {\n quat = vec4(qabc.x, qd, qabc.y, qabc.z);\n } else if (qMode == 2) {\n quat = vec4(qabc.x, qabc.y, qd, qabc.z);\n } else {\n quat = vec4(qabc.x, qabc.y, qabc.z, qd);\n }\n\n float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;\n mat3 R = mat3(\n 1.0 - 2.0 * (qy * qy + qz * qz), 2.0 * (qx * qy + qw * qz), 2.0 * (qx * qz - qw * qy),\n 2.0 * (qx * qy - qw * qz), 1.0 - 2.0 * (qx * qx + qz * qz), 2.0 * (qy * qz + qw * qx),\n 2.0 * (qx * qz + qw * qy), 2.0 * (qy * qz - qw * qx), 1.0 - 2.0 * (qx * qx + qy * qy)\n );\n mat3 S2 = mat3(\n 4.0 * splatScale.x * splatScale.x, 0.0, 0.0,\n 0.0, 4.0 * splatScale.y * splatScale.y, 0.0,\n 0.0, 0.0, 4.0 * splatScale.z * splatScale.z\n );\n mat3 Sigma = R * S2 * transposeM(R);\n\n // Color (sh0)\n const float SH_C0 = 0.28209479177387814;\n vec3 colRgb;\n float colA;\n if (uVersion == 2) {\n vec3 c3;\n c3.x = texelFetch(sogCodebookTex, ivec2(256 + int(c0.x * 255.0 + 0.5), 0), 0).r;\n c3.y = texelFetch(sogCodebookTex, ivec2(256 + int(c0.y * 255.0 + 0.5), 0), 0).r;\n c3.z = texelFetch(sogCodebookTex, ivec2(256 + int(c0.z * 255.0 + 0.5), 0), 0).r;\n colRgb = vec3(0.5) + c3 * SH_C0;\n colA = c0.w;\n } else {\n vec4 cLerp = mix(sogSh0Min, sogSh0Max, c0);\n colRgb = vec3(0.5) + cLerp.xyz * SH_C0;\n colA = 1.0 / (1.0 + exp(-cLerp.w));\n }\n\n glFragData[0] = vec4(center, 1.0);\n glFragData[1] = vec4(Sigma[0][0], Sigma[0][1], Sigma[0][2], Sigma[1][1]);\n glFragData[2] = vec4(Sigma[1][2], Sigma[2][2], 0.0, 0.0);\n glFragData[3] = vec4(colRgb, colA);\n}\n";
7449
+ /**
7450
+ * Pass-through vertex shader (WGSL).
7451
+ */
7452
+ export const GaussianSplattingWorkBufferVertexShaderWGSL = "\nattribute position : vec3<f32>;\n@vertex\nfn main(input : VertexInputs) -> FragmentInputs {\n vertexOutputs.position = vec4<f32>(input.position.xy, 0.0, 1.0);\n}\n";
7453
+ /**
7454
+ * Fragment shader (WGSL/WebGPU) — same decode as the GLSL variant, writing 4 MRT attachments.
7455
+ */
7456
+ export const GaussianSplattingWorkBufferFragmentShaderWGSL = "\nvar sogMeansLTexSampler : sampler;\nvar sogMeansLTex : texture_2d<f32>;\nvar sogMeansUTexSampler : sampler;\nvar sogMeansUTex : texture_2d<f32>;\nvar sogScalesTexSampler : sampler;\nvar sogScalesTex : texture_2d<f32>;\nvar sogQuatsTexSampler : sampler;\nvar sogQuatsTex : texture_2d<f32>;\nvar sogSh0TexSampler : sampler;\nvar sogSh0Tex : texture_2d<f32>;\nvar sogCodebookTexSampler : sampler;\nvar sogCodebookTex : texture_2d<f32>;\n\nuniform sogMeansMin : vec3<f32>;\nuniform sogMeansMax : vec3<f32>;\nuniform sogScalesMin : vec3<f32>;\nuniform sogScalesMax : vec3<f32>;\nuniform sogSh0Min : vec4<f32>;\nuniform sogSh0Max : vec4<f32>;\nuniform uVersion : i32;\nuniform uOffset : i32;\nuniform uCount : i32;\nuniform uDestWidth : i32;\nuniform uSrcWidth : i32;\n\n@fragment\nfn main(input : FragmentInputs) -> FragmentOutputs {\n let p : vec2<i32> = vec2<i32>(i32(fragmentInputs.position.x), i32(fragmentInputs.position.y));\n let global : i32 = p.y * uniforms.uDestWidth + p.x;\n if (global < uniforms.uOffset || global >= uniforms.uOffset + uniforms.uCount) {\n discard;\n }\n let k : i32 = global - uniforms.uOffset;\n let src : vec2<i32> = vec2<i32>(k - (k / uniforms.uSrcWidth) * uniforms.uSrcWidth, k / uniforms.uSrcWidth);\n\n let mL : vec3<f32> = textureLoad(sogMeansLTex, src, 0).xyz;\n let mU : vec3<f32> = textureLoad(sogMeansUTex, src, 0).xyz;\n let sRaw : vec3<f32> = textureLoad(sogScalesTex, src, 0).xyz;\n let qRaw : vec4<f32> = textureLoad(sogQuatsTex, src, 0);\n let c0 : vec4<f32> = textureLoad(sogSh0Tex, src, 0);\n\n let q16 : vec3<f32> = (mU * 256.0 + mL) * (255.0 / 65535.0);\n let nPos : vec3<f32> = mix(uniforms.sogMeansMin, uniforms.sogMeansMax, q16);\n let center : vec3<f32> = sign(nPos) * (exp(abs(nPos)) - vec3<f32>(1.0));\n\n var splatScale : vec3<f32>;\n if (uniforms.uVersion == 2) {\n let sIdx : vec3<f32> = floor(sRaw * 255.0 + 0.5);\n splatScale.x = exp(textureLoad(sogCodebookTex, vec2<i32>(i32(sIdx.x), 0), 0).r);\n splatScale.y = exp(textureLoad(sogCodebookTex, vec2<i32>(i32(sIdx.y), 0), 0).r);\n splatScale.z = exp(textureLoad(sogCodebookTex, vec2<i32>(i32(sIdx.z), 0), 0).r);\n } else {\n splatScale = exp(mix(uniforms.sogScalesMin, uniforms.sogScalesMax, sRaw));\n }\n\n let invSqrt2 : f32 = 0.70710678118;\n let qabc : vec3<f32> = (qRaw.xyz - vec3<f32>(0.5)) * 2.0 * invSqrt2;\n let qMode : i32 = i32(qRaw.w * 255.0 + 0.5) - 252;\n let qd : f32 = sqrt(max(0.0, 1.0 - dot(qabc, qabc)));\n var quat : vec4<f32>;\n if (qMode == 0) {\n quat = vec4<f32>(qd, qabc.x, qabc.y, qabc.z);\n } else if (qMode == 1) {\n quat = vec4<f32>(qabc.x, qd, qabc.y, qabc.z);\n } else if (qMode == 2) {\n quat = vec4<f32>(qabc.x, qabc.y, qd, qabc.z);\n } else {\n quat = vec4<f32>(qabc.x, qabc.y, qabc.z, qd);\n }\n\n let qw : f32 = quat.x;\n let qx : f32 = quat.y;\n let qy : f32 = quat.z;\n let qz : f32 = quat.w;\n let R : mat3x3<f32> = mat3x3<f32>(\n 1.0 - 2.0 * (qy * qy + qz * qz), 2.0 * (qx * qy + qw * qz), 2.0 * (qx * qz - qw * qy),\n 2.0 * (qx * qy - qw * qz), 1.0 - 2.0 * (qx * qx + qz * qz), 2.0 * (qy * qz + qw * qx),\n 2.0 * (qx * qz + qw * qy), 2.0 * (qy * qz - qw * qx), 1.0 - 2.0 * (qx * qx + qy * qy)\n );\n let S2 : mat3x3<f32> = mat3x3<f32>(\n 4.0 * splatScale.x * splatScale.x, 0.0, 0.0,\n 0.0, 4.0 * splatScale.y * splatScale.y, 0.0,\n 0.0, 0.0, 4.0 * splatScale.z * splatScale.z\n );\n let Sigma : mat3x3<f32> = R * S2 * transpose(R);\n\n let SH_C0 : f32 = 0.28209479177387814;\n var colRgb : vec3<f32>;\n var colA : f32;\n if (uniforms.uVersion == 2) {\n var c3 : vec3<f32>;\n c3.x = textureLoad(sogCodebookTex, vec2<i32>(256 + i32(c0.x * 255.0 + 0.5), 0), 0).r;\n c3.y = textureLoad(sogCodebookTex, vec2<i32>(256 + i32(c0.y * 255.0 + 0.5), 0), 0).r;\n c3.z = textureLoad(sogCodebookTex, vec2<i32>(256 + i32(c0.z * 255.0 + 0.5), 0), 0).r;\n colRgb = vec3<f32>(0.5) + c3 * SH_C0;\n colA = c0.w;\n } else {\n let cLerp : vec4<f32> = mix(uniforms.sogSh0Min, uniforms.sogSh0Max, c0);\n colRgb = vec3<f32>(0.5) + cLerp.xyz * SH_C0;\n colA = 1.0 / (1.0 + exp(-cLerp.w));\n }\n\n fragmentOutputs.fragData0 = vec4<f32>(center, 1.0);\n fragmentOutputs.fragData1 = vec4<f32>(Sigma[0][0], Sigma[0][1], Sigma[0][2], Sigma[1][1]);\n fragmentOutputs.fragData2 = vec4<f32>(Sigma[1][2], Sigma[2][2], 0.0, 0.0);\n fragmentOutputs.fragData3 = vec4<f32>(colRgb, colA);\n}\n";
7457
+
7458
+
7459
+ /**
7460
+ * A unified, GPU-decoded Gaussian Splatting work buffer.
7461
+ *
7462
+ * Holds a square MRT texture set (centers / covA / covB / colors) sized to a fixed splat capacity
7463
+ * (PlayCanvas-style: `ceil(sqrt(capacity))`). Each streamed SOG file is decoded directly on the GPU
7464
+ * (no CPU readback) into its allocated pixel range. The decoded textures are consumed unchanged by the
7465
+ * standard (non-SOG) Gaussian Splatting draw path.
7466
+ *
7467
+ * @experimental
7468
+ */
7469
+ export class GaussianSplattingWorkBuffer {
7470
+ private readonly _scene;
7471
+ private readonly _mrt;
7472
+ private readonly _textureSize;
7473
+ private readonly _shaderLanguage;
7474
+ private readonly _material;
7475
+ private readonly _quad;
7476
+ private _disposed;
7477
+ private _readFbo;
7478
+ /**
7479
+ * True when the engine supports the non-blocking GPU readback used by {@link readCentersRangeAsync}:
7480
+ * WebGL2 (PBO + fence) or WebGPU (copyTextureToBuffer + mapAsync). When false (e.g. WebGL1), callers must
7481
+ * decode positions on the CPU instead.
7482
+ */
7483
+ get supportsAsyncCentersReadback(): boolean;
7484
+ /**
7485
+ * Square edge length (in pixels) of the work-buffer textures.
7486
+ */
7487
+ get textureSize(): number;
7488
+ /**
7489
+ * The decoded work-buffer textures: [centers, covA, covB, colors].
7490
+ */
7491
+ get textures(): Texture[];
7492
+ /**
7493
+ * Creates a work buffer sized to hold `capacity` splats.
7494
+ * @param scene hosting scene
7495
+ * @param capacity total number of splats the work buffer must address
7496
+ */
7497
+ constructor(scene: Scene, capacity: number);
7498
+ /**
7499
+ * Decodes one SOG file into the work buffer at the given splat offset (accumulating; previously
7500
+ * decoded files are preserved). Resolves once the GPU decode has been issued. The caller may
7501
+ * dispose the source pack textures after this resolves.
7502
+ * @param pack the SOG texture pack (GPU source textures + per-file decode parameters)
7503
+ * @param offset first splat index (pixel offset) for this file in the work buffer
7504
+ */
7505
+ decodeAsync(pack: ISogTexturePack, offset: number): Promise<void>;
7506
+ /**
7507
+ * Asynchronously reads back the decoded splat centers (stride-4 xyzw, w=1) for a contiguous splat range
7508
+ * from the work buffer's centers texture, using a non-blocking GPU readback (WebGL2 PBO + fence, or WebGPU
7509
+ * copyTextureToBuffer + mapAsync) so it never stalls the frame the way a CPU image decode does. The centers
7510
+ * texture already holds the GPU-decoded positions (identical to the CPU decode), so this replaces decoding
7511
+ * positions on the CPU from the means images. Returns null when async readback is unsupported (caller should
7512
+ * fall back to CPU decoding).
7513
+ * @param splatOffset first splat index of the range
7514
+ * @param splatCount number of splats in the range
7515
+ * @returns a stride-4 Float32Array of length `splatCount * 4`, or null when unsupported/failed
7516
+ */
7517
+ readCentersRangeAsync(splatOffset: number, splatCount: number): Promise<Nullable<Float32Array>>;
7518
+ /**
7519
+ * Disposes the work buffer and its decode resources.
7520
+ */
7521
+ dispose(): void;
7522
+ private _createQuad;
7523
+ private _createMaterial;
7524
+ private _applyPack;
7525
+ }
7416
7526
 
7417
7527
 
7528
+ /**
7529
+ * A single LOD variant of a tree node: a contiguous splat range inside one streamed SOG file.
7530
+ */
7531
+ interface ISOGLODEntry {
7532
+ /** Index into {@link ISOGLODMetadata.filenames}. */
7533
+ file: number;
7534
+ /** First splat index inside that file. */
7535
+ offset: number;
7536
+ /** Number of splats. */
7537
+ count: number;
7538
+ }
7539
+ /**
7540
+ * A node of the PlayCanvas-style SOG LOD octree. Internal nodes have `children`; leaves have `lods`.
7541
+ */
7542
+ interface ISOGLODNode {
7543
+ bound: {
7544
+ min: number[];
7545
+ max: number[];
7546
+ };
7547
+ children?: ISOGLODNode[];
7548
+ lods?: {
7549
+ [level: string]: ISOGLODEntry;
7550
+ };
7551
+ /** LOD level currently streamed/rendered for this node, or undefined until its base LOD is ready. */
7552
+ activeLod?: number;
7553
+ /** Distance-based ideal LOD level for this node, recomputed per frame. */
7554
+ optimalLod?: number;
7555
+ /** Available LOD levels for this leaf, sorted ascending (0 = finest). Set during the tree walk. */
7556
+ availableLevels?: number[];
7557
+ /** Coarsest available level (= max key), always streamed as the permanent base layer. */
7558
+ baseLod?: number;
7559
+ /** Final LOD level the node should stream/render (distance optimal, capped by maxDetailLod). */
7560
+ targetLevel?: number;
7561
+ /** Frames remaining before this node may switch LOD again (oscillation damping). */
7562
+ lodCooldown?: number;
7563
+ /** True when the node's bounding box currently intersects the camera frustum. Drives the LOD bias that
7564
+ * pushes off-screen nodes to the coarsest level (they stay rendered, not hidden). */
7565
+ inFrustum?: boolean;
7566
+ /** Cached local-space bounding info used for the per-node frustum test (created once per leaf). */
7567
+ cullBounds?: BoundingInfo;
7568
+ }
7569
+ /**
7570
+ * Parsed contents of a PlayCanvas-style `lod-meta.json` file.
7571
+ */
7572
+ export interface ISOGLODMetadata {
7573
+ /** Number of LOD levels (0 = highest detail). */
7574
+ lodLevels: number;
7575
+ /** SOG `meta.json` paths, relative to the metadata file, indexed by `ISOGLODEntry.file`. */
7576
+ filenames: string[];
7577
+ /** Optional always-on environment `.sog` bundle, relative to the metadata file. */
7578
+ environment?: string;
7579
+ /** Root of the LOD octree. */
7580
+ tree: ISOGLODNode;
7581
+ }
7582
+ /**
7583
+ * Selects which LOD value drives the {@link GaussianSplattingStream} debug wireframe colors.
7584
+ */
7585
+ export type GaussianSplattingStreamDebugLodSource = "optimal" | "current";
7586
+ /**
7587
+ * Options for {@link GaussianSplattingStream}.
7588
+ */
7589
+ export interface IGaussianSplattingStreamOptions {
7590
+ /** URL of the fflate UMD module used to unzip `.sog` environment bundles. */
7591
+ deflateURL?: string;
7592
+ /** Pre-loaded fflate module. */
7593
+ fflate?: any;
7594
+ /** When true, renders a wireframe box per LOD node, colored by the node's LOD level. */
7595
+ debugDisplay?: boolean;
7596
+ /** Which LOD value drives the debug wireframe colors. Defaults to `"optimal"`. */
7597
+ debugLodSource?: GaussianSplattingStreamDebugLodSource;
7598
+ /** Distance (in local units) of the first LOD transition. PlayCanvas default `5`. */
7599
+ lodBaseDistance?: number;
7600
+ /** Geometric ratio between successive LOD transition distances. PlayCanvas default `3`. */
7601
+ lodMultiplier?: number;
7602
+ /** Distance multiplier applied to nodes behind the camera (`1` = no penalty). PlayCanvas default `1`. */
7603
+ lodBehindPenalty?: number;
7604
+ /** Lowest LOD index the optimal-LOD heuristic may select. Defaults to `0`. */
7605
+ lodRangeMin?: number;
7606
+ /** Highest LOD index the optimal-LOD heuristic may select. Defaults to `lodLevels - 1`. */
7607
+ lodRangeMax?: number;
7608
+ /** Maximum number of LOD source files to GPU-decode per frame (spreads work to avoid hitches). Defaults to `1`. */
7609
+ maxDecodesPerFrame?: number;
7610
+ /** Frames a node must wait after switching LOD before it may switch again (oscillation damping). Defaults to `10`. */
7611
+ lodCooldownFrames?: number;
7612
+ /** Minimum number of frames between LOD re-evaluations (throttles per-frame work during motion). Defaults to `4`. */
7613
+ lodUpdateInterval?: number;
7614
+ /** Minimum camera movement (world units) required to re-evaluate LODs. Defaults to `0.5`. */
7615
+ lodUpdateDistance?: number;
7616
+ /**
7617
+ * Finest (most detailed) LOD level any node is allowed to render. `0` allows full detail (level 0);
7618
+ * `1` caps detail at the next-coarser level, and so on. Higher values force a coarser maximum detail.
7619
+ */
7620
+ maxDetailLod?: number;
7621
+ /**
7622
+ * When true (default), LOD nodes outside the camera frustum are biased to their coarsest LOD rather than
7623
+ * rendered at full detail. They stay in the sort/render set so they appear instantly (at low detail) when
7624
+ * the camera turns toward them, then refine. Set to `false` to render every node at its distance LOD.
7625
+ */
7626
+ frustumCulling?: boolean;
7627
+ }
7628
+ /**
7629
+ * Streams a PlayCanvas-style SOG LOD scene (`lod-meta.json`) into a single Gaussian Splatting mesh.
7630
+ *
7631
+ * Each selected SOG file (plus the environment) is loaded directly as GPU textures and decoded on the
7632
+ * GPU into one unified, PlayCanvas-style square work buffer (no CPU splat decode or `updateData`). Only
7633
+ * the splats of each node's currently-selected LOD are rendered/sorted via the mesh's interval filter.
7634
+ *
7635
+ * The coarsest (least-detail) LOD of every node is streamed first as a permanent base layer so the whole
7636
+ * scene is visible quickly with no holes. A distance-based "optimal" LOD is then computed per node (see
7637
+ * {@link evaluateOptimalLods}); finer LOD source files are streamed on demand and a node only switches to
7638
+ * a finer LOD once that file is decoded, so transitions never flash or leave gaps.
7639
+ *
7640
+ * @experimental
7641
+ */
7642
+ export class GaussianSplattingStream extends GaussianSplattingMesh {
7643
+ private readonly _metadata;
7644
+ private readonly _rootUrl;
7645
+ private readonly _streamOptions;
7646
+ private readonly _leafNodes;
7647
+ private _lodBaseDistance;
7648
+ private _lodMultiplier;
7649
+ private _lodBehindPenalty;
7650
+ private _lodRangeMin;
7651
+ private _lodRangeMax;
7652
+ private _maxDecodesPerFrame;
7653
+ private _lodCooldownFrames;
7654
+ private _lodUpdateInterval;
7655
+ private _lodUpdateDistance;
7656
+ private _maxDetailLod;
7657
+ private _frustumCulling;
7658
+ private readonly _frustumPlanes;
7659
+ private readonly _cullViewProj;
7660
+ private _workBuffer;
7661
+ private _useGpuPositionReadback;
7662
+ private _readbackCandidate;
7663
+ private _readbackProbed;
7664
+ private readonly _fileBaseSplat;
7665
+ private readonly _fileCounts;
7666
+ private readonly _fileMeta;
7667
+ private readonly _decodedFiles;
7668
+ private readonly _loadingFiles;
7669
+ private readonly _decodeQueue;
7670
+ private _environmentRange;
7671
+ private _environmentFiles;
7672
+ private _lodObserver;
7673
+ private _baseLayerReady;
7674
+ private _framesSinceLodUpdate;
7675
+ private readonly _lastLodCamPos;
7676
+ private _forceLodUpdate;
7677
+ private readonly _boundsMin;
7678
+ private readonly _boundsMax;
7679
+ private _debugDisplay;
7680
+ private _debugLodSource;
7681
+ private _debugMesh;
7682
+ private _debugObserver;
7683
+ private _debugColorData;
7684
+ private _debugSignature;
7685
+ private _disposed;
7686
+ /**
7687
+ * Returns true when the parsed JSON looks like a PlayCanvas-style `lod-meta.json` payload.
7688
+ * @param data parsed JSON
7689
+ * @returns whether the data is SOG LOD metadata
7690
+ */
7691
+ static IsLODMetadata(data: unknown): data is ISOGLODMetadata;
7692
+ /**
7693
+ * Creates a new SOG LOD streaming mesh and immediately starts streaming (non-blocking).
7694
+ * @param name mesh name
7695
+ * @param metadata parsed `lod-meta.json`
7696
+ * @param rootUrl base URL the metadata's relative paths resolve against
7697
+ * @param scene hosting scene
7698
+ * @param options streaming options
7699
+ */
7700
+ constructor(name: string, metadata: ISOGLODMetadata, rootUrl: string, scene: Scene, options?: IGaussianSplattingStreamOptions);
7701
+ getClassName(): string;
7702
+ /**
7703
+ * Finest (most detailed) LOD level any node is allowed to render. `0` allows full detail (level 0);
7704
+ * `1` caps detail at the next-coarser level, and so on. Nodes already coarser than this cap (by
7705
+ * distance) are unaffected. Changes take effect in real time.
7706
+ */
7707
+ get maxDetailLod(): number;
7708
+ set maxDetailLod(value: number);
7709
+ /**
7710
+ * Coarsest LOD level index in the scene (number of LOD levels minus one). Useful as the upper bound
7711
+ * for {@link maxDetailLod}.
7712
+ */
7713
+ get maxLodLevel(): number;
7714
+ /**
7715
+ * When true (default), nodes whose bounding box is outside the camera frustum are biased to the coarsest
7716
+ * LOD instead of being hidden. They stay in the sort/render set (their off-screen splats are clipped), so
7717
+ * turning the camera toward them shows low detail immediately with no invisible frames, then refines.
7718
+ * Changes take effect in real time.
7719
+ */
7720
+ get frustumCulling(): boolean;
7721
+ set frustumCulling(value: boolean);
7722
+ /**
7723
+ * When true, renders a wireframe box per LOD node, colored by the LOD level selected by {@link debugLodSource}.
7724
+ */
7725
+ get debugDisplay(): boolean;
7726
+ set debugDisplay(value: boolean);
7727
+ /**
7728
+ * Selects which LOD value drives the debug wireframe colors: the distance-based `"optimal"` LOD
7729
+ * (default, recomputed as the camera moves) or the `"current"` streamed/rendered LOD.
7730
+ */
7731
+ get debugLodSource(): GaussianSplattingStreamDebugLodSource;
7732
+ set debugLodSource(value: GaussianSplattingStreamDebugLodSource);
7733
+ dispose(doNotRecurse?: boolean): void;
7734
+ /**
7735
+ * Re-evaluates the optimal LOD for every node based on the camera position. The result is stored in
7736
+ * each node's `optimalLod`. Rendering is unaffected; this currently drives only diagnostics and the
7737
+ * debug wireframe display.
7738
+ * @param camera camera to evaluate against (defaults to the scene's active camera)
7739
+ */
7740
+ evaluateOptimalLods(camera?: Nullable<Camera>): void;
7741
+ /**
7742
+ * The LOD level used to color a node's debug box, per {@link debugLodSource}.
7743
+ * @param node leaf node
7744
+ * @returns the displayed LOD level
7745
+ */
7746
+ private _displayedLodLevel;
7747
+ /**
7748
+ * Rebuilds the debug wireframe (evaluating the optimal LOD first when needed) and wires up the per-frame
7749
+ * recolor observer. The observer runs for both LOD sources: "optimal" colors track the camera, and
7750
+ * "current" colors track LOD levels as they stream in/out.
7751
+ */
7752
+ private _refreshDebugDisplay;
7753
+ /**
7754
+ * Per-frame debug update: recolors the existing wireframe in place whenever the displayed LOD levels
7755
+ * change. For the "optimal" source the optimal LOD is recomputed first (it tracks the camera); for the
7756
+ * "current" source the levels are driven by the streaming loop, so no recomputation is needed here. The
7757
+ * geometry is never rebuilt, which avoids the dispose/recreate flicker while the camera moves.
7758
+ */
7759
+ private _onDebugFrame;
7760
+ /**
7761
+ * Builds the LOD-node wireframe boxes once (one box per leaf node), colored by the displayed LOD level.
7762
+ * The color vertex buffer is created updatable so subsequent recolors can happen in place.
7763
+ */
7764
+ private _buildDebugMesh;
7765
+ /**
7766
+ * Recolors the existing wireframe in place from the current displayed LOD levels, without rebuilding geometry.
7767
+ */
7768
+ private _updateDebugColors;
7769
+ /**
7770
+ * Computes a cheap 32-bit rolling hash of every leaf's displayed LOD level, used to detect when the
7771
+ * debug wireframe needs recoloring. Avoids per-frame string allocation in the render loop.
7772
+ * @returns a numeric signature of the current displayed LOD levels
7773
+ */
7774
+ private _computeDebugSignature;
7775
+ /**
7776
+ * Disposes the LOD-node wireframe boxes and stops live debug updates.
7777
+ */
7778
+ private _clearDebugDisplay;
7779
+ /**
7780
+ * Walks the LOD tree and records every leaf that carries renderable LOD entries, capturing the set of
7781
+ * available levels and the coarsest (base) level for each.
7782
+ * @param node current tree node
7783
+ */
7784
+ private _collectLodEntries;
7785
+ /**
7786
+ * Streams the scene: learns every source file's splat count, allocates one unified GPU work buffer
7787
+ * sized for all LOD files, decodes the environment and the coarsest LOD of every node as a permanent
7788
+ * base layer, then installs the per-frame loop that streams finer LODs on demand.
7789
+ */
7790
+ private _streamAllAsync;
7791
+ /**
7792
+ * Collects the unique set of source file indices referenced by any LOD of any leaf, sorted ascending.
7793
+ * @returns sorted unique file indices
7794
+ */
7795
+ private _collectAllFileIds;
7796
+ /**
7797
+ * Fetches the environment bundle and every referenced file's metadata to learn splat counts, caching
7798
+ * each file's parsed metadata for the later on-demand decode. Metadata fetches run in parallel.
7799
+ * @param fileIds file indices to fetch metadata for
7800
+ * @returns the environment splat count (0 when there is no environment)
7801
+ */
7802
+ private _gatherCountsAsync;
7803
+ /**
7804
+ * Queues a file for on-demand decode if it isn't already decoded, in flight, or already queued.
7805
+ * @param fileId file index to decode
7806
+ */
7807
+ private _enqueueDecode;
7808
+ /**
7809
+ * Starts up to {@link _maxDecodesPerFrame} queued decodes for this frame. Decodes run asynchronously
7810
+ * and promote any waiting nodes once they complete.
7811
+ */
7812
+ private _pumpDecodeQueue;
7813
+ /**
7814
+ * Writes a decoded splat range's positions into the shared buffer, expands the bounds, and incrementally
7815
+ * patches the sort worker.
7816
+ * @param positions stride-4 positions for the range
7817
+ * @param base first splat index of the range in the work buffer
7818
+ * @param count number of splats in the range
7819
+ */
7820
+ private _applyPositions;
7821
+ /**
7822
+ * One-time validation of GPU position readback: reads a sample of the just-decoded range back from the work
7823
+ * buffer and compares it to the CPU-decoded positions. Enables {@link _useGpuPositionReadback} only on an
7824
+ * exact (within float tolerance) match, so an unsupported or incorrect readback (e.g. a backend without the
7825
+ * required texture usage, or an orientation mismatch) safely keeps the CPU decode path.
7826
+ * @param base first splat index of the validated range
7827
+ * @param count number of splats in the range
7828
+ * @param cpuPositions the CPU-decoded stride-4 positions for the range (ground truth)
7829
+ */
7830
+ private _probeReadbackAsync;
7831
+ /**
7832
+ * Resolves the decoded positions for a splat range and applies them. Once GPU readback has been validated,
7833
+ * positions are read back from the work buffer (non-blocking) and `pack.positions` is empty; otherwise the
7834
+ * CPU-decoded `pack.positions` are used, and — on the first such decode — the GPU readback is validated
7835
+ * against them so subsequent decodes can use the fast path.
7836
+ * @param pack the parsed SOG pack (its `positions` is populated only on the CPU path)
7837
+ * @param base first splat index of the range in the work buffer
7838
+ * @param count number of splats in the range
7839
+ * @returns whether positions were applied
7840
+ */
7841
+ private _applyDecodedPositionsAsync;
7842
+ /**
7843
+ * Decodes the always-on environment bundle into its work-buffer block and activates its range.
7844
+ */
7845
+ private _decodeEnvironmentAsync;
7846
+ /**
7847
+ * Loads one LOD source file as GPU textures, decodes it into its fixed work-buffer block, records its
7848
+ * CPU centers for sorting, frees the source textures, then promotes any nodes that were waiting for it.
7849
+ * Concurrent or repeat requests for the same file are ignored.
7850
+ * @param fileId file index to decode
7851
+ */
7852
+ private _decodeFileAsync;
7853
+ /**
7854
+ * Snaps a desired LOD level to the nearest level the node provides, while never selecting a level finer
7855
+ * than {@link maxDetailLod} (i.e. with an index below the cap). Ties prefer the finer allowed level. If
7856
+ * the node has no level at or coarser than the cap, its coarsest available level is used.
7857
+ * @param node leaf node
7858
+ * @param desired desired LOD level
7859
+ * @returns the chosen available level
7860
+ */
7861
+ private _cappedLevelForNode;
7862
+ /**
7863
+ * Computes each node's {@link ISOGLODNode.targetLevel}: the distance-based optimal level snapped to an
7864
+ * available level, capped so no node renders finer (more detailed) than {@link maxDetailLod}.
7865
+ */
7866
+ private _computeTargetLevels;
7867
+ /**
7868
+ * Applies each node's {@link ISOGLODNode.targetLevel}: switches a node to its target level when that
7869
+ * level's file is already decoded, otherwise queues the file and leaves the node on its current LOD (so
7870
+ * nothing ever disappears). Nodes within their post-switch cooldown are left untouched to damp oscillation.
7871
+ * @returns true when at least one node changed LOD (callers should refresh the active ranges)
7872
+ */
7873
+ private _applyDesiredLods;
7874
+ /**
7875
+ * Per-frame LOD streaming loop. Ticks cooldowns and pumps the decode queue every frame, and runs the
7876
+ * cheap per-node frustum test every frame so the off-screen LOD bias tracks camera rotation. The LOD
7877
+ * re-evaluation is throttled to at most every {@link _lodUpdateInterval} frames once the camera has
7878
+ * translated far enough, but also runs immediately whenever a node enters/leaves the frustum (so its
7879
+ * detail upgrades/downgrades promptly) or a cap change forces it. Active ranges rebuild on any LOD change.
7880
+ */
7881
+ private _onLodFrame;
7882
+ /**
7883
+ * Updates each leaf node's {@link ISOGLODNode.inFrustum} flag from a per-node frustum test against the
7884
+ * active camera. When {@link frustumCulling} is disabled (or there is no camera) every node is marked
7885
+ * in-frustum. Bounds are static (from the LOD tree), so flags are valid for all nodes regardless of
7886
+ * decode state. Returns true when any node's in-frustum state changed (so the LOD bias must be re-applied).
7887
+ * @returns whether any node's in-frustum state changed
7888
+ */
7889
+ private _updateNodeFrustum;
7890
+ /**
7891
+ * Reads the splat count from SOG metadata.
7892
+ * @param data SOG metadata
7893
+ * @returns the splat count
7894
+ */
7895
+ private static _GetSplatCount;
7896
+ /**
7897
+ * Disposes all GPU source textures of a SOG pack (they are only needed for the one decode pass).
7898
+ * @param pack the SOG texture pack
7899
+ */
7900
+ private static _DisposePack;
7901
+ /**
7902
+ * Expands the running splat-center bounds with a newly decoded file's centers and updates the
7903
+ * mesh bounding info so the GS is correctly frustum-culled and pickable.
7904
+ * @param positions stride-4 splat centers for the new file
7905
+ * @param count number of splats
7906
+ */
7907
+ private _updateBounds;
7908
+ /**
7909
+ * Rebuilds the active interval set from the environment plus each node's currently-selected LOD entry,
7910
+ * coalesces adjacent ranges, and pushes the result to the sort worker.
7911
+ */
7912
+ private _refreshActiveRanges;
7913
+ /**
7914
+ * Sorts and merges adjacent/overlapping ranges to keep the interval list compact.
7915
+ * @param ranges raw ranges
7916
+ * @returns coalesced ranges
7917
+ */
7918
+ private static _CoalesceRanges;
7919
+ /**
7920
+ * Unzips a `.sog` bundle into a name -> bytes map, loading fflate on demand.
7921
+ * @param data zipped bytes
7922
+ * @returns map of entry name to bytes
7923
+ */
7924
+ private _unzipAsync;
7925
+ }
7418
7926
 
7419
7927
 
7420
7928
  /**
@@ -7816,6 +8324,974 @@ declare namespace BABYLON {
7816
8324
 
7817
8325
 
7818
8326
 
8327
+ /**
8328
+ * Defines the FBX loader plugin metadata.
8329
+ */
8330
+ export var FBXFileLoaderMetadata: {
8331
+ readonly name: "fbx";
8332
+ readonly extensions: {
8333
+ readonly ".fbx": {
8334
+ readonly isBinary: true;
8335
+ };
8336
+ };
8337
+ };
8338
+
8339
+
8340
+ /**
8341
+ * Source convention for tangent-space normal maps loaded from FBX normal-map slots.
8342
+ */
8343
+ export type FBXNormalMapCoordinateSystem = "y-up" | "y-down";
8344
+ /**
8345
+ * Defines options for the FBX loader.
8346
+ */
8347
+ export interface FBXFileLoaderOptions {
8348
+ /**
8349
+ * Source convention for tangent-space normal maps connected through FBX normal-map slots.
8350
+ * FBX does not standardize this convention, so the loader defaults to the glTF/USD-style Y-up convention.
8351
+ * Set to "y-down" for assets authored with inverted green/Y normal maps.
8352
+ */
8353
+ normalMapCoordinateSystem?: FBXNormalMapCoordinateSystem;
8354
+ }
8355
+ interface SceneLoaderPluginOptions {
8356
+ /**
8357
+ * Defines options for the FBX loader.
8358
+ */
8359
+ [FBXFileLoaderMetadata.name]: FBXFileLoaderOptions;
8360
+ }
8361
+ /**
8362
+ * FBX file loader plugin for Babylon.js.
8363
+ * Pure TypeScript implementation — no Autodesk FBX SDK dependency.
8364
+ */
8365
+ export class FBXFileLoader implements ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
8366
+ /**
8367
+ * Defines the name of the plugin.
8368
+ */
8369
+ readonly name: "fbx";
8370
+ /**
8371
+ * Defines the extension the plugin is able to load.
8372
+ */
8373
+ readonly extensions: {
8374
+ readonly ".fbx": {
8375
+ readonly isBinary: true;
8376
+ };
8377
+ };
8378
+ private readonly _options;
8379
+ private readonly _bindRestBones;
8380
+ private readonly _sourceBonesBySkeleton;
8381
+ private readonly _scaleCompensationHelpersBySkeleton;
8382
+ /**
8383
+ * Creates a new FBX loader.
8384
+ * @param options - Options controlling FBX loading behavior
8385
+ */
8386
+ constructor(options?: FBXFileLoaderOptions);
8387
+ /**
8388
+ * Creates an FBX loader plugin instance with options from SceneLoader.
8389
+ * @param options - Scene loader plugin options
8390
+ * @returns The configured FBX loader
8391
+ */
8392
+ createPlugin(options: SceneLoaderPluginOptions): ISceneLoaderPluginAsync;
8393
+ /**
8394
+ * Imports meshes from an FBX file and adds them to the scene.
8395
+ * @param meshesNames - A string or array of mesh names to import, or null/undefined to import all meshes
8396
+ * @param scene - The scene to add imported meshes to
8397
+ * @param data - The FBX data to load
8398
+ * @param rootUrl - Root URL used to resolve external resources
8399
+ * @param _onProgress - Callback called while the file is loading
8400
+ * @param _fileName - Name of the file being loaded
8401
+ * @returns A promise containing the loaded meshes, particle systems, skeletons, animation groups, transform nodes, geometries, and lights
8402
+ */
8403
+ importMeshAsync(meshesNames: string | readonly string[] | null | undefined, scene: Scene, data: unknown, rootUrl: string, _onProgress?: (event: ISceneLoaderProgressEvent) => void, _fileName?: string): Promise<ISceneLoaderAsyncResult>;
8404
+ /**
8405
+ * Loads all FBX content into the scene.
8406
+ * @param scene - The scene to load the FBX content into
8407
+ * @param data - The FBX data to load
8408
+ * @param rootUrl - Root URL used to resolve external resources
8409
+ * @param _onProgress - Callback called while the file is loading
8410
+ * @param _fileName - Name of the file being loaded
8411
+ * @returns A promise that resolves when loading is complete
8412
+ */
8413
+ loadAsync(scene: Scene, data: unknown, rootUrl: string, _onProgress?: (event: ISceneLoaderProgressEvent) => void, _fileName?: string): Promise<void>;
8414
+ /**
8415
+ * Loads all FBX content into an asset container.
8416
+ * @param scene - The scene used to create the asset container
8417
+ * @param data - The FBX data to load
8418
+ * @param rootUrl - Root URL used to resolve external resources
8419
+ * @param _onProgress - Callback called while the file is loading
8420
+ * @param _fileName - Name of the file being loaded
8421
+ * @returns A promise containing the loaded asset container
8422
+ */
8423
+ loadAssetContainerAsync(scene: Scene, data: unknown, rootUrl: string, _onProgress?: (event: ISceneLoaderProgressEvent) => void, _fileName?: string): Promise<AssetContainer>;
8424
+ private _parse;
8425
+ private _parseFromArrayBuffer;
8426
+ private _buildScene;
8427
+ private _addMaterialToContainer;
8428
+ private _addTextureToContainer;
8429
+ private _setAssetContainer;
8430
+ private static _computeFBXAxisConversionMatrix;
8431
+ private _buildModel;
8432
+ private static _modelSubtreeMatchesNameFilter;
8433
+ private static _applyModelMetadata;
8434
+ private _createMesh;
8435
+ /**
8436
+ * Apply multi-material to a mesh by creating sub-meshes grouped by material index.
8437
+ * Reorders the index buffer so that triangles sharing the same material are contiguous.
8438
+ */
8439
+ private _applyMultiMaterial;
8440
+ private static _collectCullingConflictMaterialIds;
8441
+ private static _getModelMaterial;
8442
+ private _applyMaterialUVSetCoordinates;
8443
+ private _applyStandardMaterialUVSetCoordinates;
8444
+ /**
8445
+ * Babylon multiplies vertex colors by material diffuse color. Use per-mesh
8446
+ * material clones so vertex-colored geometry can render unmodulated without
8447
+ * changing shared materials used by non-vertex-colored meshes.
8448
+ */
8449
+ private _useUnmodulatedVertexColorMaterials;
8450
+ /**
8451
+ * Build per-polygon-vertex bone indices and weights from the control-point-based skin data.
8452
+ * The geometry expands control points to per-polygon-vertex, so we need to look up
8453
+ * each polygon-vertex's control point index.
8454
+ */
8455
+ private _buildSkinningData;
8456
+ private _createMaterial;
8457
+ private _configureNormalTexture;
8458
+ private _getNormalMapTangentHandednessScale;
8459
+ private static _isSupportedMaterialTextureSlot;
8460
+ private static _isNormalMapTextureSlot;
8461
+ private static _createTexture;
8462
+ private static _createExternalTexture;
8463
+ private static _buildTextureFallbackUrls;
8464
+ private static _getTextureCreationOptions;
8465
+ private static _getExternalTextureUrls;
8466
+ private static _getTextureSourceName;
8467
+ private static _getTextureSourceNameFromPath;
8468
+ private static _isSafeRelativeTexturePath;
8469
+ private static _getForcedExtension;
8470
+ private static _getMimeType;
8471
+ /**
8472
+ * Apply blend shape (morph target) deformers to meshes.
8473
+ * FBX Shape vertices are stored as absolute positions for sparse control points.
8474
+ * We compute deltas relative to the base mesh positions.
8475
+ */
8476
+ private _applyBlendShapes;
8477
+ private _createCamera;
8478
+ private _createLight;
8479
+ private _createSkeleton;
8480
+ private _getSourceBone;
8481
+ private _getScaleCompensationHelper;
8482
+ private static _computeFBXAbsoluteMatrices;
8483
+ private static _computeFBXRuntimeLocalMatrix;
8484
+ private static _applyParentScaleCompensation;
8485
+ private static _splitParentScaleCompensatedLocalMatrix;
8486
+ private static _safeInverseScale;
8487
+ private static _getInverseScaleVector;
8488
+ private static _shouldUseBindMatricesAsRest;
8489
+ private static _getMaxScaleRatio;
8490
+ private static _getScaleRatio;
8491
+ private static _computeFBXGeometricMatrix;
8492
+ private static _computeFBXGeometricDeltaMatrix;
8493
+ private static _computeFBXGeometricNormalMatrix;
8494
+ /**
8495
+ * Compute the full FBX local transform matrix:
8496
+ * M = T * Roff * Rp * Rpre * R * Rpost^-1 * Rp^-1 * Soff * Sp * S * Sp^-1
8497
+ *
8498
+ * In row-vector convention: v' = v * M
8499
+ */
8500
+ private static _computeFBXLocalMatrix;
8501
+ /**
8502
+ * Apply the FBX transform chain to a Babylon TransformNode or Mesh.
8503
+ * Decomposes the full local matrix into position/rotation/scale.
8504
+ */
8505
+ private static _applyFBXTransform;
8506
+ private static _computeFBXModelLocalMatrix;
8507
+ private static _getBoneReferenceWorldMatrix;
8508
+ private static _applyMatrixToTransform;
8509
+ private _createAnimationGroup;
8510
+ private _buildInheritedRigBoneAnimations;
8511
+ private static _pushMatrixKeys;
8512
+ /**
8513
+ * Build animations for a non-bone node, correctly handling pivots.
8514
+ * Computes the full FBX transform matrix at each keyframe and decomposes into TRS.
8515
+ */
8516
+ private _buildNodeAnimations;
8517
+ private _isVector3KeysConstant;
8518
+ private _sampleModelLocalMatrix;
8519
+ private _sampleModelScale;
8520
+ /**
8521
+ * Build matrix-baked bone animation from full FBX local transforms.
8522
+ * The bind matrix carries the skinning offset, so animation curves drive
8523
+ * the same FBX local transform chain as the source skeleton.
8524
+ */
8525
+ private _buildBoneAnimations;
8526
+ private _buildNameFilter;
8527
+ }
8528
+
8529
+
8530
+ /**
8531
+ * Intermediate representation for parsed FBX data.
8532
+ * Both binary and ASCII parsers produce this same structure.
8533
+ */
8534
+ /** Individual property value within an FBX node */
8535
+ export type FBXPropertyValue = boolean | number | string | Float32Array | Float64Array | Int32Array | Uint8Array;
8536
+ /** Parsed FBX property type identifier. */
8537
+ export type FBXPropertyType = "boolean" | "int16" | "int32" | "int64" | "float32" | "float64" | "string" | "raw" | "float32[]" | "float64[]" | "int32[]" | "int64[]" | "boolean[]";
8538
+ /** Individual property within an FBX node. */
8539
+ export interface FBXProperty {
8540
+ /** Parsed property type. */
8541
+ type: FBXPropertyType;
8542
+ /** Parsed property value. */
8543
+ value: FBXPropertyValue;
8544
+ }
8545
+ /** A node in the FBX document tree */
8546
+ export interface FBXNode {
8547
+ /** Node name. */
8548
+ name: string;
8549
+ /** Node properties. */
8550
+ properties: FBXProperty[];
8551
+ /** Child nodes. */
8552
+ children: FBXNode[];
8553
+ }
8554
+ /** Top-level parsed FBX document */
8555
+ export interface FBXDocument {
8556
+ /** FBX file version. */
8557
+ version: number;
8558
+ /** Top-level document nodes. */
8559
+ nodes: FBXNode[];
8560
+ }
8561
+ /** Helper to find a child node by name */
8562
+ export function findChildByName(node: FBXNode, name: string): FBXNode | undefined;
8563
+ /** Helper to find all children with a given name */
8564
+ export function findChildrenByName(node: FBXNode, name: string): FBXNode[];
8565
+ /** Helper to find a top-level node in a document */
8566
+ export function findDocumentNode(doc: FBXDocument, name: string): FBXNode | undefined;
8567
+ /** Extract a property value by index, with type narrowing */
8568
+ export function getPropertyValue<T extends FBXPropertyValue>(node: FBXNode, index: number): T | undefined;
8569
+ /**
8570
+ * Converts an FBX object ID value to a safe JavaScript number.
8571
+ * @param value - Parsed FBX object ID value
8572
+ * @returns The object ID, or undefined when the value is not numeric
8573
+ */
8574
+ export function getSafeFBXObjectId(value: unknown): number | undefined;
8575
+ /** Get the numeric ID from a node (first property is typically the int64 UID) */
8576
+ export function getNodeId(node: FBXNode): number | undefined;
8577
+ /**
8578
+ * Clean FBX object names.
8579
+ * FBX names may contain:
8580
+ * - A "Class::" prefix (e.g. "Model::valkyrie_mesh") — strip it
8581
+ * - A binary null/control-character class suffix — strip it
8582
+ */
8583
+ export function cleanFBXName(fbxName: string): string;
8584
+
8585
+
8586
+ /**
8587
+ * Inflate a zlib-wrapped deflate stream.
8588
+ *
8589
+ * This implementation is intentionally scoped to FBX binary array payloads: one-shot,
8590
+ * synchronous zlib streams with the exact uncompressed length known up front.
8591
+ */
8592
+ export function inflateZlib(input: Uint8Array, expectedLength: number): Uint8Array;
8593
+
8594
+
8595
+ /**
8596
+ * Parse a binary FBX file into an FBXDocument.
8597
+ * Supports FBX versions 7.0–7.7 (v7.5+ uses 64-bit node headers).
8598
+ */
8599
+ export function parseBinaryFBX(buffer: ArrayBuffer): FBXDocument;
8600
+
8601
+
8602
+ /**
8603
+ * Parse an ASCII FBX file into an FBXDocument.
8604
+ */
8605
+ export function parseAsciiFBX(text: string): FBXDocument;
8606
+
8607
+
8608
+ export type FBXVector3 = [number, number, number];
8609
+ export interface FBXTransformComponents {
8610
+ translation: FBXVector3;
8611
+ rotation: FBXVector3;
8612
+ scale: FBXVector3;
8613
+ preRotation: FBXVector3;
8614
+ postRotation: FBXVector3;
8615
+ rotationPivot: FBXVector3;
8616
+ scalingPivot: FBXVector3;
8617
+ rotationOffset: FBXVector3;
8618
+ scalingOffset: FBXVector3;
8619
+ rotationOrder: number;
8620
+ inheritType?: number;
8621
+ }
8622
+ export function eulerToMatrixXYZ(rx: number, ry: number, rz: number): Matrix;
8623
+ export function eulerToMatrix(rx: number, ry: number, rz: number, order: number): Matrix;
8624
+ export function computeFBXGeometricMatrix(translation: FBXVector3, rotation: FBXVector3, scale: FBXVector3): Matrix;
8625
+ export function computeFBXGeometricDeltaMatrix(rotation: FBXVector3, scale: FBXVector3): Matrix;
8626
+ export function computeFBXGeometricNormalMatrix(rotation: FBXVector3, scale: FBXVector3): Matrix;
8627
+ export function computeFBXLocalMatrix(components: FBXTransformComponents): Matrix;
8628
+
8629
+
8630
+ export type FBXClusterMode = "Normalize" | "Additive" | "TotalOne" | "Unknown";
8631
+ export interface FBXSkinDiagnostic {
8632
+ type: "cluster-mode-runtime-unsupported" | "missing-cluster-transform" | "missing-cluster-transform-link" | "missing-bind-pose-matrix" | "associate-model-present";
8633
+ message: string;
8634
+ boneModelId?: number;
8635
+ boneName?: string;
8636
+ clusterMode?: FBXClusterMode;
8637
+ }
8638
+ /** Represents a single bone (cluster) in the FBX skeleton */
8639
+ export interface FBXBoneData {
8640
+ /** The Model node ID for this bone */
8641
+ modelId: number;
8642
+ /** Bone name (from the Model node) */
8643
+ name: string;
8644
+ /** Index of this bone in the skeleton */
8645
+ index: number;
8646
+ /** Index of the parent bone (-1 for root) */
8647
+ parentIndex: number;
8648
+ /** Whether this bone corresponds to an FBX Cluster with vertex weights */
8649
+ isCluster: boolean;
8650
+ /** Local translation from parent (Lcl Translation) */
8651
+ translation: [number, number, number];
8652
+ /** Local rotation in degrees (Lcl Rotation) */
8653
+ rotation: [number, number, number];
8654
+ /** Pre-rotation in degrees (applied before Lcl Rotation) */
8655
+ preRotation: [number, number, number];
8656
+ /** Post-rotation in degrees (applied after Lcl Rotation, inverted) */
8657
+ postRotation: [number, number, number];
8658
+ /** Rotation pivot point */
8659
+ rotationPivot: [number, number, number];
8660
+ /** Scaling pivot point */
8661
+ scalingPivot: [number, number, number];
8662
+ /** Rotation offset */
8663
+ rotationOffset: [number, number, number];
8664
+ /** Scaling offset */
8665
+ scalingOffset: [number, number, number];
8666
+ /** Local scale (Lcl Scaling) */
8667
+ scale: [number, number, number];
8668
+ /** Rotation order: 0=XYZ, 1=XZY, 2=YZX, 3=YXZ, 4=ZXY, 5=ZYX */
8669
+ rotationOrder: number;
8670
+ /** FBX transform inheritance mode. 0=RrSs, 1=RSrs, 2=Rrs */
8671
+ inheritType: number;
8672
+ /** Cluster skinning mode */
8673
+ clusterMode: FBXClusterMode;
8674
+ /** Bind pose transform matrix (cluster Transform, 4x4) */
8675
+ bindPoseMatrix: Float64Array | null;
8676
+ /** Bone's world transform at bind time (cluster TransformLink, 4x4) */
8677
+ transformLinkMatrix: Float64Array | null;
8678
+ /** Associate model world transform at bind time (cluster TransformAssociateModel, 4x4) */
8679
+ transformAssociateModelMatrix: Float64Array | null;
8680
+ /** Model's absolute matrix from the FBX BindPose, when present */
8681
+ modelBindPoseMatrix: Float64Array | null;
8682
+ /** Recoverable bind/skinning diagnostics for this bone */
8683
+ diagnostics: FBXSkinDiagnostic[];
8684
+ }
8685
+ /** Represents a skin deformer with its clusters */
8686
+ export interface FBXSkinData {
8687
+ /** Skin deformer ID */
8688
+ id: number;
8689
+ /** Geometry ID this skin is attached to */
8690
+ geometryId: number;
8691
+ /** Mesh model world matrix from the FBX BindPose, when present */
8692
+ meshBindPoseMatrix: Float64Array | null;
8693
+ /** Bones in this skeleton */
8694
+ bones: FBXBoneData[];
8695
+ /** Per-vertex bone indices, sorted by descending weight and capped for Babylon skinning */
8696
+ boneIndices: number[][];
8697
+ /** Per-vertex bone weights, matching boneIndices */
8698
+ boneWeights: number[][];
8699
+ /** Recoverable skinning/bind diagnostics */
8700
+ diagnostics: FBXSkinDiagnostic[];
8701
+ }
8702
+ /**
8703
+ * Extract all skin deformers from the FBX scene.
8704
+ * Returns skin data including bone hierarchy and vertex weights.
8705
+ */
8706
+ export function extractSkins(objectMap: FBXObjectMap): FBXSkinData[];
8707
+ export function isSkeletonModel(modelNode: FBXNode): boolean;
8708
+ export function extractBoneTransform(modelNode: FBXNode): {
8709
+ translation: [number, number, number];
8710
+ rotation: [number, number, number];
8711
+ preRotation: [number, number, number];
8712
+ postRotation: [number, number, number];
8713
+ rotationPivot: [number, number, number];
8714
+ scalingPivot: [number, number, number];
8715
+ rotationOffset: [number, number, number];
8716
+ scalingOffset: [number, number, number];
8717
+ scale: [number, number, number];
8718
+ rotationOrder: number;
8719
+ inheritType: number;
8720
+ };
8721
+
8722
+
8723
+ export type FBXSceneDiagnosticType = "unsupported-constraint" | "unsupported-helper" | "unsupported-deformer" | "unsupported-node-attribute" | "unsupported-pose" | "unsupported-layered-texture" | "connection-graph";
8724
+ export interface FBXSceneDiagnostic {
8725
+ type: FBXSceneDiagnosticType;
8726
+ message: string;
8727
+ objectId?: number;
8728
+ objectName?: string;
8729
+ nodeName?: string;
8730
+ subType?: string;
8731
+ /** Number of accepted parent graph edges for objectId, when objectId is known. */
8732
+ parentCount?: number;
8733
+ childCount?: number;
8734
+ }
8735
+ export function extractSceneDiagnostics(objectMap: FBXObjectMap): FBXSceneDiagnostic[];
8736
+
8737
+
8738
+ export type FBXRigBoneData = FBXBoneData;
8739
+ export interface FBXSkinBindingData {
8740
+ skinId: number;
8741
+ geometryId: number;
8742
+ rigId: string;
8743
+ skinBoneIndexToRigBoneIndex: number[];
8744
+ clusterModelIds: Set<number>;
8745
+ }
8746
+ export interface FBXRigData {
8747
+ id: string;
8748
+ rootModelIds: number[];
8749
+ bones: FBXRigBoneData[];
8750
+ modelIdToBoneIndex: Map<number, number>;
8751
+ clusterModelIds: Set<number>;
8752
+ skinBindings: FBXSkinBindingData[];
8753
+ warnings: string[];
8754
+ }
8755
+ export function resolveRigs(objectMap: FBXObjectMap, skins: FBXSkinData[]): FBXRigData[];
8756
+
8757
+
8758
+ export interface FBXTemplateProperty {
8759
+ name: string;
8760
+ propertyType: string;
8761
+ label: string;
8762
+ flags: string;
8763
+ values: FBXPropertyValue[];
8764
+ }
8765
+ export interface FBXPropertyTemplate {
8766
+ objectType: string;
8767
+ templateName: string;
8768
+ properties: Map<string, FBXTemplateProperty>;
8769
+ }
8770
+ export type FBXPropertyTemplateMap = Map<string, Map<string, FBXPropertyTemplate>>;
8771
+ export function extractPropertyTemplates(doc: FBXDocument): FBXPropertyTemplateMap;
8772
+ export function getPropertyTemplate(templates: FBXPropertyTemplateMap, objectType: string, templateName?: string): FBXPropertyTemplate | undefined;
8773
+ export function getTemplatePropertyValue<T extends FBXPropertyValue>(template: FBXPropertyTemplate | undefined, propertyName: string, valueIndex?: number): T | undefined;
8774
+ export function resolvePropertyValue<T extends FBXPropertyValue>(node: FBXNode, template: FBXPropertyTemplate | undefined, propertyName: string, valueIndex?: number): T | undefined;
8775
+ export function resolveNumberProperty(node: FBXNode, template: FBXPropertyTemplate | undefined, propertyName: string, fallback: number): number;
8776
+ export function resolveVector2Property(node: FBXNode, template: FBXPropertyTemplate | undefined, propertyName: string, fallback: [number, number]): [number, number];
8777
+ export function resolveVector3Property(node: FBXNode, template: FBXPropertyTemplate | undefined, propertyName: string, fallback: [number, number, number]): [number, number, number];
8778
+ export function resolvePropertyValues(node: FBXNode, template: FBXPropertyTemplate | undefined, propertyName: string): FBXPropertyValue[] | undefined;
8779
+
8780
+
8781
+ /** Parsed material data */
8782
+ export interface FBXMaterialData {
8783
+ id: number;
8784
+ name: string;
8785
+ type: "Lambert" | "Phong";
8786
+ properties: FBXMaterialProperties;
8787
+ textures: FBXTextureRef[];
8788
+ }
8789
+ export interface FBXMaterialProperties {
8790
+ diffuseColor?: [number, number, number];
8791
+ diffuseFactor?: number;
8792
+ ambientColor?: [number, number, number];
8793
+ ambientFactor?: number;
8794
+ specularColor?: [number, number, number];
8795
+ specularFactor?: number;
8796
+ shininess?: number;
8797
+ emissiveColor?: [number, number, number];
8798
+ emissiveFactor?: number;
8799
+ opacity?: number;
8800
+ transparencyFactor?: number;
8801
+ }
8802
+ export interface FBXTextureRef {
8803
+ /** Which material property this texture is connected to */
8804
+ propertyName: string;
8805
+ /** Absolute file path from the FBX */
8806
+ fileName: string;
8807
+ /** Relative file path from the FBX */
8808
+ relativeFileName: string;
8809
+ /** Texture node ID */
8810
+ id: number;
8811
+ /** Embedded texture data (from Video node Content), if available */
8812
+ embeddedData: Uint8Array | null;
8813
+ /** UV translation [u, v] */
8814
+ uvTranslation?: [number, number];
8815
+ /** UV scaling [u, v] */
8816
+ uvScaling?: [number, number];
8817
+ /** UV rotation in degrees */
8818
+ uvRotation?: number;
8819
+ /** Which UV set index this texture uses */
8820
+ uvSetIndex?: number;
8821
+ /** Which named UV set this texture uses */
8822
+ uvSetName?: string;
8823
+ }
8824
+ /**
8825
+ * Extract material data from an FBX Material node.
8826
+ */
8827
+ export function extractMaterial(materialNode: FBXNode, materialId: number, objectMap: FBXObjectMap, templates?: FBXPropertyTemplateMap): FBXMaterialData;
8828
+
8829
+
8830
+ /** A named UV set */
8831
+ export interface FBXUVSet {
8832
+ /** UV set name (e.g. "UVMap", "lightmap") */
8833
+ name: string;
8834
+ /** Per-vertex UV data [u,v, ...] (expanded to match triangle vertices) */
8835
+ data: Float64Array;
8836
+ }
8837
+ /** Recoverable geometry import issue. */
8838
+ export interface FBXGeometryDiagnostic {
8839
+ /** Diagnostic category. */
8840
+ type: "degenerate-polygon" | "triangulation-fallback" | "layer-index-out-of-bounds" | "layer-data-too-short";
8841
+ /** Human-readable diagnostic message. */
8842
+ message: string;
8843
+ /** Polygon index associated with the diagnostic, if applicable. */
8844
+ polygonIndex?: number;
8845
+ /** Layer element name associated with the diagnostic, if applicable. */
8846
+ layerName?: string;
8847
+ /** Source data index associated with the diagnostic, if applicable. */
8848
+ index?: number;
8849
+ }
8850
+ /** Parsed geometry data ready for Babylon consumption */
8851
+ export interface FBXGeometryData {
8852
+ /** Node ID from the FBX document */
8853
+ id: number;
8854
+ /** Geometry name */
8855
+ name: string;
8856
+ /** Flat array of vertex positions [x,y,z, x,y,z, ...] */
8857
+ positions: Float64Array;
8858
+ /** Triangle indices (already triangulated from n-gons) */
8859
+ indices: Uint32Array;
8860
+ /** Per-vertex normals [x,y,z, ...] (expanded to match triangle vertices) */
8861
+ normals: Float64Array | null;
8862
+ /** Per-vertex UVs [u,v, ...] (expanded to match triangle vertices) — first UV set for convenience */
8863
+ uvs: Float64Array | null;
8864
+ /** All UV sets (including the first) */
8865
+ uvSets: FBXUVSet[];
8866
+ /** Per-vertex colors [r,g,b,a, ...] (expanded to match triangle vertices) */
8867
+ colors: Float32Array | null;
8868
+ /** Per-vertex tangents [x,y,z,w, ...] expanded to match triangle vertices */
8869
+ tangents: Float64Array | null;
8870
+ /** Per-vertex binormals [x,y,z, ...] expanded to match triangle vertices */
8871
+ binormals: Float64Array | null;
8872
+ /** Control point index for each polygon-vertex (for skinning lookup) */
8873
+ controlPointIndices: Uint32Array | null;
8874
+ /** Per-triangle material index (which material each triangle belongs to) */
8875
+ materialIndices: Int32Array | null;
8876
+ /** Recoverable geometry import issues */
8877
+ diagnostics: FBXGeometryDiagnostic[];
8878
+ }
8879
+ /**
8880
+ * Extract geometry data from an FBX Geometry node.
8881
+ * Handles polygon triangulation and layer element expansion.
8882
+ */
8883
+ export function extractGeometry(geometryNode: FBXNode, nodeId: number): FBXGeometryData;
8884
+
8885
+
8886
+ /** Represents a model (transform node) in the FBX scene */
8887
+ export interface FBXModelData {
8888
+ id: number;
8889
+ name: string;
8890
+ subType: string;
8891
+ /** Geometry attached to this model (if it's a Mesh type) */
8892
+ geometry?: FBXGeometryData;
8893
+ /** Materials assigned to this model */
8894
+ materials: FBXMaterialData[];
8895
+ /** Child models */
8896
+ children: FBXModelData[];
8897
+ /** Transform properties */
8898
+ translation: [number, number, number];
8899
+ rotation: [number, number, number];
8900
+ scale: [number, number, number];
8901
+ /** PreRotation (applied before Lcl Rotation, in degrees) */
8902
+ preRotation: [number, number, number];
8903
+ /** PostRotation (applied after Lcl Rotation, inverted, in degrees) */
8904
+ postRotation: [number, number, number];
8905
+ /** RotationPivot — point around which rotation occurs */
8906
+ rotationPivot: [number, number, number];
8907
+ /** ScalingPivot — point around which scaling occurs */
8908
+ scalingPivot: [number, number, number];
8909
+ /** RotationOffset — translation after rotation pivot */
8910
+ rotationOffset: [number, number, number];
8911
+ /** ScalingOffset — translation after scaling pivot */
8912
+ scalingOffset: [number, number, number];
8913
+ /** Geometric transforms — applied to geometry only, do not affect children */
8914
+ geometricTranslation: [number, number, number];
8915
+ geometricRotation: [number, number, number];
8916
+ geometricScaling: [number, number, number];
8917
+ /** Rotation order: 0=XYZ, 1=XZY, 2=YZX, 3=YXZ, 4=ZXY, 5=ZYX */
8918
+ rotationOrder: number;
8919
+ /** FBX transform inheritance mode. 0=RrSs, 1=RSrs, 2=Rrs */
8920
+ inheritType: number;
8921
+ /** Whether backface culling is disabled ("CullingOff") */
8922
+ cullingOff: boolean;
8923
+ /** User-defined custom properties from Properties70 */
8924
+ customProperties?: Record<string, string | number | boolean>;
8925
+ /** Recoverable model import diagnostics */
8926
+ diagnostics: string[];
8927
+ }
8928
+ /** Camera data extracted from FBX */
8929
+ export interface FBXCameraData {
8930
+ /** Model ID this camera is attached to */
8931
+ modelId: number;
8932
+ /** Camera name */
8933
+ name: string;
8934
+ /** Field of view in degrees */
8935
+ fieldOfView: number;
8936
+ /** Near clip plane */
8937
+ nearPlane: number;
8938
+ /** Far clip plane */
8939
+ farPlane: number;
8940
+ /** Aspect ratio (width/height), 0 = use viewport */
8941
+ aspectRatio: number;
8942
+ /** Projection type */
8943
+ projectionType: "perspective" | "orthographic";
8944
+ /** Focal length in millimeters when present */
8945
+ focalLength?: number;
8946
+ /** Filmback width in inches when present */
8947
+ filmWidth?: number;
8948
+ /** Filmback height in inches when present */
8949
+ filmHeight?: number;
8950
+ /** Orthographic zoom/height when present */
8951
+ orthoZoom?: number;
8952
+ /** Camera roll in degrees when present */
8953
+ roll?: number;
8954
+ /** Known unsupported or unrecognized camera properties */
8955
+ unknownProperties: string[];
8956
+ /** Recoverable camera import diagnostics */
8957
+ diagnostics: string[];
8958
+ }
8959
+ /** Light data extracted from FBX */
8960
+ export interface FBXLightData {
8961
+ /** Model ID this light is attached to */
8962
+ modelId: number;
8963
+ /** Light name */
8964
+ name: string;
8965
+ /** Light type: 0=Point, 1=Directional, 2=Spot */
8966
+ lightType: number;
8967
+ /** Color [r,g,b] 0-1 */
8968
+ color: [number, number, number];
8969
+ /** Intensity multiplier */
8970
+ intensity: number;
8971
+ /** Cone angle in degrees (for spot lights) */
8972
+ coneAngle: number;
8973
+ /** Decay type: 0=None, 1=Linear, 2=Quadratic */
8974
+ decayType: number;
8975
+ /** Inner cone angle in degrees for spot lights */
8976
+ innerAngle?: number;
8977
+ /** Outer cone angle in degrees for spot lights */
8978
+ outerAngle?: number;
8979
+ /** Distance at which FBX attenuation starts; preserved as metadata */
8980
+ decayStart?: number;
8981
+ /** Whether FBX near attenuation is enabled */
8982
+ enableNearAttenuation?: boolean;
8983
+ /** Whether FBX far attenuation is enabled */
8984
+ enableFarAttenuation?: boolean;
8985
+ /** Whether the source light requested shadow casting */
8986
+ castShadows?: boolean;
8987
+ /** Known unsupported or unrecognized light properties */
8988
+ unknownProperties: string[];
8989
+ /** Recoverable light import diagnostics */
8990
+ diagnostics: string[];
8991
+ }
8992
+ /** Result of interpreting an FBX document */
8993
+ export interface FBXSceneData {
8994
+ /** All root-level models */
8995
+ rootModels: FBXModelData[];
8996
+ /** All geometries in the scene */
8997
+ geometries: FBXGeometryData[];
8998
+ /** All materials in the scene */
8999
+ materials: FBXMaterialData[];
9000
+ /** Skin deformers (skeletons + vertex weights) */
9001
+ skins: FBXSkinData[];
9002
+ /** Resolved deformation rigs shared by one or more skins */
9003
+ rigs: FBXRigData[];
9004
+ /** Blend shape deformers (morph targets) */
9005
+ blendShapes: FBXBlendShapeData[];
9006
+ /** Animation stacks (clips) */
9007
+ animations: FBXAnimationStackData[];
9008
+ /** Cameras */
9009
+ cameras: FBXCameraData[];
9010
+ /** Lights */
9011
+ lights: FBXLightData[];
9012
+ /** Scene-level unsupported feature diagnostics */
9013
+ diagnostics: FBXSceneDiagnostic[];
9014
+ /** Global settings */
9015
+ upAxis: number;
9016
+ upAxisSign: number;
9017
+ frontAxis: number;
9018
+ frontAxisSign: number;
9019
+ coordAxis: number;
9020
+ coordAxisSign: number;
9021
+ unitScaleFactor: number;
9022
+ }
9023
+ /**
9024
+ * Interpret a parsed FBX document into scene data.
9025
+ */
9026
+ export function interpretFBX(doc: FBXDocument): FBXSceneData;
9027
+
9028
+
9029
+ /** Connection type: OO = object-to-object, OP = object-to-property */
9030
+ export type ConnectionType = "OO" | "OP";
9031
+ /** A resolved FBX object connection. */
9032
+ export interface FBXConnection {
9033
+ /** Connection type. */
9034
+ type: ConnectionType;
9035
+ /** Child object ID. */
9036
+ childId: number;
9037
+ /** Parent object ID. */
9038
+ parentId: number;
9039
+ /** For OP connections, the property name on the parent (e.g. "DiffuseColor") */
9040
+ propertyName?: string;
9041
+ }
9042
+ /** Object table entry used by the FBX connection graph. */
9043
+ export interface FBXObjectEntry {
9044
+ /** Object ID. */
9045
+ id: number;
9046
+ /** Object node. */
9047
+ node: FBXNode;
9048
+ /** Source of the object entry. */
9049
+ source: "Objects" | "legacySyntheticGeometry";
9050
+ /** Legacy string object name, when applicable. */
9051
+ legacyName?: string;
9052
+ /** True if the object was synthesized for legacy compatibility. */
9053
+ synthetic: boolean;
9054
+ }
9055
+ /** Raw connection-table entry and import status. */
9056
+ export interface FBXConnectionEntry {
9057
+ /** Source node name. */
9058
+ source: "C" | "Connect";
9059
+ /** Raw connection type. */
9060
+ rawType?: string;
9061
+ /** Child object ID, when resolved. */
9062
+ childId?: number;
9063
+ /** Parent object ID, when resolved. */
9064
+ parentId?: number;
9065
+ /** OP connection property name, when present. */
9066
+ propertyName?: string;
9067
+ /** True if the connection was accepted into the resolved graph. */
9068
+ accepted: boolean;
9069
+ }
9070
+ /** Reason a connection produced a diagnostic. */
9071
+ export type FBXConnectionDiagnosticReason = "unsupported-connection-type" | "missing-connection-endpoint" | "unresolved-legacy-endpoint" | "unresolved-object-reference" | "duplicate-parent" | "self-loop";
9072
+ /** Recoverable connection graph issue. */
9073
+ export interface FBXConnectionDiagnostic {
9074
+ /** Diagnostic reason. */
9075
+ reason: FBXConnectionDiagnosticReason;
9076
+ /** Human-readable diagnostic message. */
9077
+ message: string;
9078
+ /** Connection-table index associated with the diagnostic, if applicable. */
9079
+ connectionIndex?: number;
9080
+ /** Connection type associated with the diagnostic, if applicable. */
9081
+ type?: string;
9082
+ /** Child object ID associated with the diagnostic, if applicable. */
9083
+ childId?: number;
9084
+ /** Parent object ID associated with the diagnostic, if applicable. */
9085
+ parentId?: number;
9086
+ /** OP connection property name associated with the diagnostic, if applicable. */
9087
+ propertyName?: string;
9088
+ }
9089
+ /** Resolved FBX object and connection graph. */
9090
+ export interface FBXObjectMap {
9091
+ /** All objects by their unique ID */
9092
+ objects: Map<number, FBXNode>;
9093
+ /** Object table entries, including synthetic compatibility objects */
9094
+ objectEntries: FBXObjectEntry[];
9095
+ /** Children of each object ID */
9096
+ childrenOf: Map<number, {
9097
+ id: number;
9098
+ propertyName?: string;
9099
+ }[]>;
9100
+ /** Parent of each object ID */
9101
+ parentOf: Map<number, {
9102
+ id: number;
9103
+ propertyName?: string;
9104
+ }>;
9105
+ /** Raw connection list */
9106
+ connections: FBXConnection[];
9107
+ /** Raw connection-table entries and whether they were accepted into the graph */
9108
+ connectionEntries: FBXConnectionEntry[];
9109
+ /** Unsupported or suspicious connection shapes encountered while preserving graph behavior */
9110
+ diagnostics: FBXConnectionDiagnostic[];
9111
+ }
9112
+ /**
9113
+ * Build a connection graph from a parsed FBX document.
9114
+ * Maps object IDs to their FBXNode and resolves parent-child relationships.
9115
+ */
9116
+ export function resolveConnections(doc: FBXDocument): FBXObjectMap;
9117
+ /** Get all child objects of a given parent ID, optionally filtered by node name */
9118
+ export function getChildren(map: FBXObjectMap, parentId: number, nodeName?: string): {
9119
+ id: number;
9120
+ node: FBXNode;
9121
+ propertyName?: string;
9122
+ }[];
9123
+
9124
+
9125
+ /** A single morph target (shape) within a blend shape channel */
9126
+ export interface FBXShapeData {
9127
+ /** Sparse vertex indices affected by this shape */
9128
+ indices: Uint32Array;
9129
+ /** Absolute vertex positions for affected vertices [x,y,z,...] */
9130
+ vertices: Float64Array;
9131
+ /** Normals for affected vertices [x,y,z,...] (optional) */
9132
+ normals: Float64Array | null;
9133
+ }
9134
+ export interface FBXBlendShapeDiagnostic {
9135
+ type: "full-weights-mismatch" | "missing-full-weights";
9136
+ message: string;
9137
+ channelId: number;
9138
+ channelName: string;
9139
+ }
9140
+ /** A blend shape channel (one animatable morph target) */
9141
+ export interface FBXBlendShapeChannelData {
9142
+ /** Channel name */
9143
+ name: string;
9144
+ /** Channel node ID */
9145
+ id: number;
9146
+ /** Default weight (0-100 in FBX) */
9147
+ deformPercent: number;
9148
+ /** Shape geometry (typically one per channel, but FBX supports in-between shapes) */
9149
+ shapes: FBXShapeData[];
9150
+ /** In-between full weights in FBX DeformPercent units (0-100), one per shape when present */
9151
+ fullWeights: number[] | null;
9152
+ /** Recoverable blend-shape diagnostics */
9153
+ diagnostics: FBXBlendShapeDiagnostic[];
9154
+ }
9155
+ /** A blend shape deformer attached to a geometry */
9156
+ export interface FBXBlendShapeData {
9157
+ /** Deformer ID */
9158
+ id: number;
9159
+ /** Geometry ID this blend shape is attached to */
9160
+ geometryId: number;
9161
+ /** Channels (each is an animatable morph target) */
9162
+ channels: FBXBlendShapeChannelData[];
9163
+ }
9164
+ /**
9165
+ * Extract all blend shape deformers from the FBX scene.
9166
+ */
9167
+ export function extractBlendShapes(objectMap: FBXObjectMap): FBXBlendShapeData[];
9168
+
9169
+
9170
+ export type FBXInterpolationType = "constant" | "linear" | "cubic";
9171
+ /** A single keyframe */
9172
+ export interface FBXKeyframe {
9173
+ /** Time in seconds */
9174
+ time: number;
9175
+ /** Value at this keyframe */
9176
+ value: number;
9177
+ /** Interpolation used from this key to the next key */
9178
+ interpolation: FBXInterpolationType;
9179
+ /** Constant interpolation variant */
9180
+ constantMode?: "standard" | "next";
9181
+ /** Cubic outgoing slope in value units per second */
9182
+ rightSlope?: number;
9183
+ /** Cubic incoming slope for the next key, in value units per second */
9184
+ nextLeftSlope?: number;
9185
+ }
9186
+ /** An animation curve (one axis of one property) */
9187
+ export interface FBXCurveData {
9188
+ /** Channel: "d|X", "d|Y", "d|Z" */
9189
+ channel: string;
9190
+ /** Keyframes */
9191
+ keys: FBXKeyframe[];
9192
+ /** True for baked sample curves that should be connected as linear samples */
9193
+ isSampled?: boolean;
9194
+ }
9195
+ /** An animation curve node (T/R/S for one bone) */
9196
+ export interface FBXCurveNodeData {
9197
+ /** Property type: "T" (translation), "R" (rotation), "S" (scale) */
9198
+ type: string;
9199
+ /** Target model (bone) ID */
9200
+ targetModelId: number;
9201
+ /** Curves for each axis */
9202
+ curves: FBXCurveData[];
9203
+ }
9204
+ /** Unsupported animation curve node preserved for diagnostics and future support. */
9205
+ export interface FBXUnsupportedCurveNodeData {
9206
+ /** Raw AnimationCurveNode property type/name */
9207
+ type: string;
9208
+ /** CurveNode object ID */
9209
+ id: number;
9210
+ /** Target object ID if the curve node is connected to an object/property */
9211
+ targetId: number | null;
9212
+ /** OP connection property name on the target, e.g. Visibility */
9213
+ propertyName?: string;
9214
+ /** Number of connected animation curves that were ignored */
9215
+ curveCount: number;
9216
+ /** Connected curves preserved for diagnostics and future runtime support */
9217
+ curves: FBXCurveData[];
9218
+ /** Local default values stored on the unsupported curve node */
9219
+ defaultValues: Record<string, number>;
9220
+ }
9221
+ /** Recoverable animation import issue. */
9222
+ export interface FBXAnimationDiagnostic {
9223
+ /** Diagnostic category. */
9224
+ type: "multiple-animation-layers" | "unsupported-layer-blend-mode" | "partial-layer-weight" | "unsupported-curve-node";
9225
+ /** Human-readable diagnostic message. */
9226
+ message: string;
9227
+ /** Animation layer name associated with the diagnostic, if applicable. */
9228
+ layerName?: string;
9229
+ /** AnimationCurveNode object ID associated with the diagnostic, if applicable. */
9230
+ curveNodeId?: number;
9231
+ /** AnimationCurveNode type/name associated with the diagnostic, if applicable. */
9232
+ curveNodeType?: string;
9233
+ /** Target object ID associated with the diagnostic, if applicable. */
9234
+ targetId?: number | null;
9235
+ /** Target property name associated with the diagnostic, if applicable. */
9236
+ propertyName?: string;
9237
+ }
9238
+ /** Animation layer with blend mode info */
9239
+ export interface FBXAnimationLayerData {
9240
+ /** Layer name */
9241
+ name: string;
9242
+ /** Layer weight (0-100, default 100) */
9243
+ weight: number;
9244
+ /** Layer weight normalized to 0-1 */
9245
+ normalizedWeight: number;
9246
+ /** Blend mode: 0=Additive, 1=Override, 2=OverridePassthrough */
9247
+ blendMode: number;
9248
+ /** Curve nodes in this layer */
9249
+ curveNodes: FBXCurveNodeData[];
9250
+ /** Unsupported/non-TRS curve nodes preserved for diagnostics */
9251
+ unsupportedCurveNodes: FBXUnsupportedCurveNodeData[];
9252
+ /** Recoverable layer diagnostics */
9253
+ diagnostics: FBXAnimationDiagnostic[];
9254
+ }
9255
+ /** One animation clip (AnimationStack) */
9256
+ export interface FBXAnimationStackData {
9257
+ /** Animation name */
9258
+ name: string;
9259
+ /** Clip start in seconds after any keyframe rebasing */
9260
+ startTime: number;
9261
+ /** Clip stop in seconds after any keyframe rebasing */
9262
+ stopTime: number;
9263
+ /** Duration in seconds */
9264
+ duration: number;
9265
+ /** Per-bone curve nodes (flattened from all layers for backward compat) */
9266
+ curveNodes: FBXCurveNodeData[];
9267
+ /** Animation layers (preserves blend mode info) */
9268
+ layers: FBXAnimationLayerData[];
9269
+ /** Unsupported/non-TRS curve nodes preserved for diagnostics */
9270
+ unsupportedCurveNodes: FBXUnsupportedCurveNodeData[];
9271
+ /** Recoverable animation diagnostics */
9272
+ diagnostics: FBXAnimationDiagnostic[];
9273
+ }
9274
+ /**
9275
+ * Extract all animation stacks from the FBX scene.
9276
+ */
9277
+ export function extractAnimations(objectMap: FBXObjectMap): FBXAnimationStackData[];
9278
+ /**
9279
+ * Determines whether a key sequence appears to be a uniformly frame-baked sampled curve.
9280
+ * @param keys - Keyframes to inspect
9281
+ * @returns true if the keys look like sampled frame data rather than authored interpolation
9282
+ */
9283
+ export function isFrameBakedSampledCurve(keys: readonly FBXKeyframe[]): boolean;
9284
+ /**
9285
+ * Samples an FBX animation curve at a specific time.
9286
+ * @param curveData - Curve data to sample
9287
+ * @param time - Time in seconds
9288
+ * @returns The sampled value, or null when the curve has no keys
9289
+ */
9290
+ export function sampleFBXCurveAtTime(curveData: FBXCurveData | undefined, time: number): number | null;
9291
+
9292
+
9293
+
9294
+
7819
9295
  /**
7820
9296
  * Options for loading BVH files
7821
9297
  */