babylonjs-toolkit 9.12.1 → 9.12.101

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.
@@ -7,7 +7,7 @@ declare namespace TOOLKIT {
7
7
  * @class SceneManager - All rights reserved (c) 2024 Mackey Kinard
8
8
  */
9
9
  class SceneManager {
10
- /** Gets the toolkit framework version string (9.12.1 - R1) */
10
+ /** Gets the toolkit framework version string (9.12.101 - R1) */
11
11
  static get Version(): string;
12
12
  /** Gets the toolkit framework copyright notice */
13
13
  static get Copyright(): string;
@@ -1066,9 +1066,9 @@ declare namespace TOOLKIT {
1066
1066
  hasSkinnedMesh(): boolean;
1067
1067
  /** Gets the safe transform skinned mesh entity */
1068
1068
  getSkinnedMesh(): BABYLON.AbstractMesh;
1069
- /** Gets the safe transform mesh entity */
1069
+ /** Gets the safe transform mesh entity (Fast Skinned Mesh Check) */
1070
1070
  getTransformMesh(): BABYLON.Mesh;
1071
- /** Gets the safe transform abstract mesh entity */
1071
+ /** Gets the safe transform abstract mesh entity (Fast Skinned Mesh Check) */
1072
1072
  getAbstractMesh(): BABYLON.AbstractMesh;
1073
1073
  /** Gets the safe transform instanced mesh entity */
1074
1074
  getInstancedMesh(): BABYLON.InstancedMesh;
@@ -1974,6 +1974,15 @@ declare namespace TOOLKIT {
1974
1974
  * nulling the value alone is not enough - we must also flag it as computed to stop regeneration.
1975
1975
  */
1976
1976
  static SuppressReflectionProbeDiffuse(texture: BABYLON.BaseTexture): void;
1977
+ /**
1978
+ * Applies the scene's global Unity ambient spherical-harmonics irradiance to a local reflection probe so the
1979
+ * probe contributes Unity-style specular reflections while its diffuse ambient comes from the SAME global SH
1980
+ * the rest of the scene uses - instead of being derived from (or suppressed on) the probe's own cube faces.
1981
+ * Babylon reads diffuse irradiance from the material's own reflectionTexture, so without this a probe-bearing
1982
+ * material would lose all baked IBL ambient. When no global SH is available we fall back to specular-only
1983
+ * (suppressed diffuse), preserving the previous Unity behavior.
1984
+ */
1985
+ static ApplyGlobalReflectionProbeDiffuse(texture: BABYLON.BaseTexture, scene: BABYLON.Scene, createPolynomialsFromFaces?: boolean): void;
1977
1986
  /** Defines whether this extension is enabled. */
1978
1987
  enabled: boolean;
1979
1988
  private _webgpu;
@@ -2154,6 +2163,33 @@ declare namespace TOOLKIT {
2154
2163
  protected shader: string;
2155
2164
  protected plugin: BABYLON.MaterialPluginBase;
2156
2165
  private _unityLightingPlugin;
2166
+ /** Per-skin Texture2DArray switching — opt-in via enableSkinArray(). One shared mesh + an N-layer
2167
+ * albedo array renders a different "skin" per layer, selected by tkSkinLayer. Unlike the atlas path
2168
+ * there is NO UV remap: the surface UVs are used as-is and the layer index picks the slice, so every
2169
+ * skin keeps full resolution with its OWN clean mip chain (no atlas cross-cell bleed). */
2170
+ private _skinArray;
2171
+ /** Per-skin NORMAL Texture2DArray switching — opt-in via enableSkinArrayNormal() once a tkNormalArray
2172
+ * is assigned. The fragment overrides normalW at CUSTOM_FRAGMENT_BEFORE_LIGHTS by running Babylon's
2173
+ * OWN perturbNormal() on its OWN interpolated vTBN with our slice texel substituted for the bump-
2174
+ * sampler fetch — i.e. byte-for-byte the native tangent-space bump, no reinvented/derivative TBN
2175
+ * (that derivative frame is what specular-speckled glossy metal). The normal array is built WITHOUT
2176
+ * mips (box-downsampled normals de-normalize and grain on WebGPU) and kept linear. */
2177
+ private _skinArrayNormal;
2178
+ /** Per-skin METALLIC-ROUGHNESS Texture2DArray switching — opt-in via enableSkinArrayMetalRough() once a
2179
+ * tkMetalRoughArray is assigned. Its hook (CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS) is INSIDE Babylon's
2180
+ * reflectivityBlock where UVs/samplers aren't in scope, so the slice is sampled in main() (UPDATE_ALBEDO)
2181
+ * into a module-global and READ at the hook: metallicRoughness.r=metallic (slice .B), .g=roughness
2182
+ * (slice .G), matching the glTF MR layout. Built MIPPED + linear (roughness wants mip filtering). */
2183
+ private _skinArrayMetalRough;
2184
+ /** Per-skin EMISSIVE Texture2DArray switching — opt-in via enableSkinArrayEmissive() once a tkEmissiveArray
2185
+ * is assigned. Overrides finalEmissive at CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION with
2186
+ * toLinearSpace(slice) (slices are sRGB-authored, mipped). finalEmissive exists even with NO base emissive
2187
+ * on the material, so this drives emissive-only swaps (e.g. brake lights on/off) with no albedo needed. */
2188
+ private _skinArrayEmissive;
2189
+ /** When true tkSkinLayer is a shared material uniform (one skin per mesh); otherwise a per-instance
2190
+ * vertex attribute (per-instance skins on a hardware-instanced mesh). Independent of which channels are on. */
2191
+ private _skinLayerUniform;
2192
+ private _skinArraySwitchingPlugin;
2157
2193
  getPlugin(): BABYLON.MaterialPluginBase;
2158
2194
  getClassName(): string;
2159
2195
  constructor(name: string, scene: BABYLON.Scene);
@@ -2166,6 +2202,102 @@ declare namespace TOOLKIT {
2166
2202
  checkSampler(samplerName: string, texture?: any): void;
2167
2203
  /** Splits the IBL lighting contributions */
2168
2204
  splitLighting(diffuseIbl: number, specularIbl: number): void;
2205
+ /**
2206
+ * Enable (or disable) per-skin Texture2DArray switching for this material (albedo channel).
2207
+ *
2208
+ * One shared mesh + an N-layer albedo array renders a different "skin" per layer. The surface UVs are
2209
+ * used as-is (NO atlas remap); a single layer index selects the slice:
2210
+ *
2211
+ * surfaceAlbedo = texture(tkAlbedoArray, vec3(uv, tkSkinLayer))
2212
+ *
2213
+ * Each layer is a full-resolution slice with its OWN clean mip chain — no atlas cross-cell bleed.
2214
+ * Assign the array first with addTextureArrayUniform("tkAlbedoArray", array), then call this to gate
2215
+ * the shader. Disabled by default — a material that never opts in compiles to identical code (every
2216
+ * injected line is gated behind TOOLKIT_SKIN_ARRAY).
2217
+ *
2218
+ * Layer source:
2219
+ * - Shared uniform (layerUniform = true): tkSkinLayer is one material uniform (setSkinLayer) — the
2220
+ * only mode that works on a NON-instanced mesh (one skin per mesh).
2221
+ * - Per-instance (layerUniform = false): tkSkinLayer is a per-instance vertex attribute — register
2222
+ * mesh.registerInstancedBuffer("tkSkinLayer", 1) and set inst.instancedBuffers.tkSkinLayer = i.
2223
+ */
2224
+ enableSkinArray(layerUniform?: boolean): TOOLKIT.CustomShaderMaterial;
2225
+ /** Set the shared per-skin array layer (uniform mode — one skin for the whole mesh). No shader
2226
+ * rebuild; just updates the uniform. For per-instance skins set inst.instancedBuffers.tkSkinLayer. */
2227
+ setSkinLayer(layer: number): TOOLKIT.CustomShaderMaterial;
2228
+ /**
2229
+ * Enable (or disable) per-skin NORMAL Texture2DArray switching for this material.
2230
+ *
2231
+ * Assign the array first with addTextureArrayUniform("tkNormalArray", array), then call this. The
2232
+ * fragment overrides normalW at CUSTOM_FRAGMENT_BEFORE_LIGHTS using Babylon's native tangent-space
2233
+ * bump EXACTLY — its own perturbNormal() on its own vTBN, only the sampled texel swapped for our
2234
+ * slice. It is therefore only active where the native tangent frame exists: BUMP && TANGENT && NORMAL
2235
+ * (i.e. the base material has a normal/bump map and the geometry has tangents — true for skinned
2236
+ * characters). Where that frame is absent the override compiles out and the native normal stands.
2237
+ * Fully INDEPENDENT of the other channels: the shared vSkinLayer/vSkinUV varyings are forwarded whenever
2238
+ * ANY skin channel is enabled, so normal can be used with NO albedo array (and vice-versa).
2239
+ */
2240
+ enableSkinArrayNormal(enabled?: boolean): TOOLKIT.CustomShaderMaterial;
2241
+ /**
2242
+ * Enable (or disable) per-skin METALLIC-ROUGHNESS Texture2DArray switching. Assign the array first with
2243
+ * addTextureArrayUniform("tkMetalRoughArray", array), then call this. The slice is sampled in main() and
2244
+ * applied at CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS as metallicRoughness.r=metallic (slice .B) and
2245
+ * .g=roughness (slice .G) — the glTF MR channel layout. Only active under METALLICWORKFLOW (PBR metallic).
2246
+ * Independent of every other channel (shared varyings forward whenever any channel is on).
2247
+ */
2248
+ enableSkinArrayMetalRough(enabled?: boolean): TOOLKIT.CustomShaderMaterial;
2249
+ /**
2250
+ * Enable (or disable) per-skin EMISSIVE Texture2DArray switching. Assign the array first with
2251
+ * addTextureArrayUniform("tkEmissiveArray", array), then call this. Overrides finalEmissive at
2252
+ * CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION with toLinearSpace(slice). Works with NO base emissive on
2253
+ * the material, so it can drive emissive-only swaps (e.g. brake lights on/off) standalone. Independent of
2254
+ * every other channel.
2255
+ */
2256
+ enableSkinArrayEmissive(enabled?: boolean): TOOLKIT.CustomShaderMaterial;
2257
+ /**
2258
+ * Set the per-skin layer DELIVERY mode independently of which channels are enabled: uniform = true drives
2259
+ * a single shared layer for the whole mesh (tkSkinLayerUni, set via setSkinLayer) — the only mode that
2260
+ * works on a non-instanced mesh; uniform = false reads a per-instance tkSkinLayer vertex attribute. Call
2261
+ * this once for the mesh (regardless of channel mix) so e.g. an emissive-only mesh still selects layers.
2262
+ */
2263
+ setSkinArrayLayerMode(uniform: boolean): TOOLKIT.CustomShaderMaterial;
2264
+ /** Register a Texture2DArray sampler (sampler2DArray / texture_2d_array) for skin switching. The
2265
+ * declaration is emitted by SkinArraySwitchingPlugin (co-gated with its usage); here we only register
2266
+ * the name for binding and store the texture for upload. Marked raw — arrays carry no UV transform. */
2267
+ addTextureArrayUniform(name: string, texture: BABYLON.BaseTexture): TOOLKIT.CustomShaderMaterial;
2268
+ /** True when per-skin ALBEDO Texture2DArray switching is enabled (see enableSkinArray). */
2269
+ get skinArray(): boolean;
2270
+ /** True when per-skin NORMAL Texture2DArray switching is enabled (see enableSkinArrayNormal). */
2271
+ get skinArrayNormal(): boolean;
2272
+ /** True when per-skin METALLIC-ROUGHNESS switching is enabled (see enableSkinArrayMetalRough). */
2273
+ get skinArrayMetalRough(): boolean;
2274
+ /** True when per-skin EMISSIVE switching is enabled (see enableSkinArrayEmissive). */
2275
+ get skinArrayEmissive(): boolean;
2276
+ /** True when ANY per-skin channel is enabled — the master gate for the shared layer/UV varyings + the
2277
+ * per-instance tkSkinLayer attribute, so any single channel works without the others. */
2278
+ get skinArrayActive(): boolean;
2279
+ /** True when tkSkinLayer is a shared material uniform rather than a per-instance vertex attribute. */
2280
+ get skinLayerUniform(): boolean;
2281
+ /**
2282
+ * Clone this material, preserving the CustomShaderMaterial SUBCLASS and ALL toolkit state — the
2283
+ * custom uniforms / samplers / attributes, the IBL split, and the per-skin Texture2DArray switching
2284
+ * configuration. Each clone is independent: it can hold its own skin (layer index) without affecting
2285
+ * the source material, which makes per-mesh skin selection on NON-instanced meshes safe.
2286
+ *
2287
+ * BabylonJS PBRMaterial.clone() builds a plain PBRMaterial through its own factory, which would
2288
+ * drop the toolkit shader hooks and the skin-array API. Here we instead:
2289
+ * 1. construct the correct subclass via `this.constructor` so the two material plugins
2290
+ * (UnityStyleLightingPlugin, SkinArraySwitchingPlugin) and the base UBO uniforms are recreated
2291
+ * FRESH and bound to the clone — we deliberately do NOT plugin-clone the source's plugins;
2292
+ * 2. copy the standard PBR surface properties with SerializationHelper (shares texture refs);
2293
+ * 3. copy the toolkit-private collections + skin-array config (see copyCustomStateTo).
2294
+ *
2295
+ * NOTE: VertexAnimationMaterial carries extra VAT state and provides its own cloneForInstance();
2296
+ * prefer that for VAT materials.
2297
+ */
2298
+ clone(name: string, cloneTexturesOnlyOnce?: boolean, rootUrl?: string): TOOLKIT.CustomShaderMaterial;
2299
+ /** Copies all toolkit-private state (custom shader collections + skin-array config) onto a clone. */
2300
+ protected copyCustomStateTo(result: TOOLKIT.CustomShaderMaterial): void;
2169
2301
  /** Adds a texture uniform property */
2170
2302
  addTextureUniform(name: string, texture: BABYLON.Texture): TOOLKIT.CustomShaderMaterial;
2171
2303
  /** Sets the texture uniform value */
@@ -2280,6 +2412,55 @@ declare namespace TOOLKIT {
2280
2412
  prepareDefines(defines: BABYLON.MaterialDefines, scene: BABYLON.Scene, mesh: BABYLON.AbstractMesh): void;
2281
2413
  bindForSubMesh(uniformBuffer: BABYLON.UniformBuffer, scene: BABYLON.Scene, engine: BABYLON.AbstractEngine, subMesh: BABYLON.SubMesh): void;
2282
2414
  }
2415
+ /**
2416
+ * Per-Skin Texture2DArray Switching Plugin (BABYLON.MaterialPluginBase)
2417
+ *
2418
+ * Opt-in base-material feature that selects a per-skin SLICE from a Texture2DArray albedo channel by a
2419
+ * single layer index — one shared mesh + an N-layer array presents a different "skin" per mesh or per
2420
+ * instance (e.g. ten jockeys, each a layer). Unlike the texture atlas there is NO UV remap: the surface
2421
+ * UVs are used as-is and the layer index picks the slice, so every skin gets a full-resolution slice with
2422
+ * its OWN clean mip chain (no cross-cell bleed).
2423
+ *
2424
+ * surfaceAlbedo = texture(tkAlbedoArray, vec3(uv, layer)) // albedo only (this phase)
2425
+ *
2426
+ * The layer comes from a per-instance vertex attribute (tkSkinLayer) OR a shared material uniform
2427
+ * (tkSkinLayerUni, the only mode that works on a NON-instanced mesh — one skin per mesh). The layer
2428
+ * value is constant across every vertex of a draw (uniform, or a per-instance attribute), so the
2429
+ * interpolated varying is constant and floor(v + 0.5) recovers the exact integer with no flat qualifier.
2430
+ *
2431
+ * All injected lines are gated behind TOOLKIT_SKIN_ARRAY, so a material that never calls
2432
+ * enableSkinArray() compiles to identical code.
2433
+ * @class SkinArraySwitchingPlugin - All rights reserved (c) 2024 Mackey Kinard
2434
+ */
2435
+ class SkinArraySwitchingPlugin extends TOOLKIT.CustomShaderMaterialPlugin {
2436
+ constructor(material: TOOLKIT.CustomShaderMaterial);
2437
+ isCompatible(shaderLanguage: BABYLON.ShaderLanguage): boolean;
2438
+ getClassName(): string;
2439
+ /** Advertise the per-instance layer attribute to the vertex shader (skipped in shared-uniform mode).
2440
+ * Gated on ANY channel being active so an emissive-only / normal-only mesh still gets the layer. */
2441
+ getAttributes(attributes: string[], scene: BABYLON.Scene, mesh: BABYLON.AbstractMesh): void;
2442
+ /** Register the array sampler(s) with the effect so setTexture() can bind them. The actual sampler
2443
+ * DECLARATIONS are emitted by this plugin's CUSTOM_FRAGMENT_DEFINITIONS (co-gated with their usage). */
2444
+ getSamplers(samplers: string[]): void;
2445
+ prepareDefines(defines: BABYLON.MaterialDefines, scene: BABYLON.Scene, mesh: BABYLON.AbstractMesh): void;
2446
+ getCustomCode(shaderType: string, shaderLanguage: BABYLON.ShaderLanguage): any;
2447
+ private getGLSLVertexDefinitions;
2448
+ private getGLSLVertexMainEnd;
2449
+ private getGLSLFragmentDefinitions;
2450
+ private getGLSLAlbedoCode;
2451
+ private getGLSLNormalCode;
2452
+ private getGLSLMetalRoughSampleCode;
2453
+ private getGLSLMetalRoughApplyCode;
2454
+ private getGLSLEmissiveCode;
2455
+ private getWGSLVertexDefinitions;
2456
+ private getWGSLVertexMainEnd;
2457
+ private getWGSLFragmentDefinitions;
2458
+ private getWGSLAlbedoCode;
2459
+ private getWGSLNormalCode;
2460
+ private getWGSLMetalRoughSampleCode;
2461
+ private getWGSLMetalRoughApplyCode;
2462
+ private getWGSLEmissiveCode;
2463
+ }
2283
2464
  /**
2284
2465
  * Babylon custom uniform items (GLTF)
2285
2466
  */
@@ -3245,13 +3426,19 @@ declare namespace TOOLKIT {
3245
3426
  */
3246
3427
  class VertexAnimationMaterial extends TOOLKIT.CustomShaderMaterial {
3247
3428
  controller: VertexAnimationController;
3248
- /** Optional per-material runtime skin texture arrays. When present, the fragment
3249
- * shader samples runtimeAlbedoSkins[skinLayer] / runtimeNormalSkins[skinLayer]
3250
- * instead of (or on top of) the standard albedo/bump samplers. Layer 0 is the
3251
- * default skin — convention is that callers seed it with the original albedo/normal.
3252
- * Bound by the plugin in bindForSubMesh(). */
3253
- runtimeAlbedoSkins: BABYLON.BaseTexture;
3254
- runtimeNormalSkins: BABYLON.BaseTexture;
3429
+ /** VAT per-skin Texture2DArray switching state — opt-in via enableVatSkinArray(). The shared albedo
3430
+ * array (tkAlbedoArray) is sampled at the layer decoded from the cell index packed in g_vatAnim1.w. */
3431
+ private _vatSkinArray;
3432
+ /** VAT per-skin NORMAL Texture2DArray switching — opt-in via enableVatSkinArrayNormal() once a
3433
+ * tkNormalArray is assigned. Overrides normalW with Babylon's native perturbNormal()/vTBN (only the
3434
+ * texel swapped for our slice), at the layer decoded from g_vatAnim1.w. Built unmipped + linear. */
3435
+ private _vatSkinArrayNormal;
3436
+ /** VAT per-skin METALLIC-ROUGHNESS switching — opt-in via enableVatSkinArrayMetalRough() once a
3437
+ * tkMetalRoughArray is assigned. metallic=slice.B, roughness=slice.G; mipped + linear. */
3438
+ private _vatSkinArrayMetalRough;
3439
+ /** VAT per-skin EMISSIVE switching — opt-in via enableVatSkinArrayEmissive() once a tkEmissiveArray is
3440
+ * assigned. Overrides finalEmissive with toLinearSpace(slice); works with no base emissive. */
3441
+ private _vatSkinArrayEmissive;
3255
3442
  constructor(name: string, scene: BABYLON.Scene);
3256
3443
  awake(): void;
3257
3444
  update(): void;
@@ -3267,44 +3454,55 @@ declare namespace TOOLKIT {
3267
3454
  */
3268
3455
  cloneForInstance(instanceName: string): TOOLKIT.VertexAnimationMaterial;
3269
3456
  /**
3270
- * Install runtime skin texture arrays. Layer 0 is treated as the default skin and is
3271
- * what every instance sees when its `runtimeSkinIndex` is 0 (or unset). Pass either
3272
- * `BABYLON.RawTexture2DArray` or any BaseTexture exposing a 2D-array view.
3457
+ * Enable per-skin Texture2DArray "skin" switching on this VAT material WITHOUT adding any vertex
3458
+ * buffer. VAT instances are already at the vertex-buffer ceiling (instanced world matrix +
3459
+ * g_vatAnim0 + g_vatAnim1), so the skin LAYER index is PACKED into g_vatAnim1.w alongside the
3460
+ * loop-blend flag, decoded in-shader and used to sample the shared albedo Texture2DArray:
3273
3461
  *
3274
- * When `nullOutDefaults` is true (default), the standalone `albedoTexture` and
3275
- * `bumpTexture` are nulled so PBR drops their sampler bindings — the plugin writes
3276
- * its own perturbed normal in the fragment when bumpTexture is absent. Pass false
3277
- * to keep the originals around (then runtime arrays act as overrides on top).
3278
- */
3279
- setRuntimeSkinArrays(albedoArray: BABYLON.BaseTexture, normalArray: BABYLON.BaseTexture, nullOutDefaults?: boolean): void;
3280
- /** Remove runtime skin arrays. Does NOT restore previously-nulled albedo/bump textures. */
3281
- clearRuntimeSkinArrays(): void;
3282
- /**
3283
- * Convenience: load a list of {albedo, normal} URL pairs, assemble two RawTexture2DArrays,
3284
- * and install them via setRuntimeSkinArrays(). Layer 0 is whatever you put at index 0 of
3285
- * the input list (the convention is "default skin first").
3462
+ * layer = floor(g_vatAnim1.w * 0.5) // 0,1,2,... selects the array slice
3463
+ * surfaceAlbedo = texture(tkAlbedoArray, vec3(uv, layer))
3286
3464
  *
3287
- * All input textures must share the same `width × height` — texture arrays are not ragged.
3288
- * Missing/malformed images reject the promise.
3465
+ * Surface UVs are used as-is (NO atlas remap) — each layer is a full-resolution slice with its own
3466
+ * clean mip chain. Assign the array first with addTextureArrayUniform("tkAlbedoArray", array), then
3467
+ * call this. Set the per-instance layer with VertexAnimationController.SetAtlasCellIndex(mesh, index).
3289
3468
  *
3290
- * @param layers ordered list of skin layers; index in array == GPU layer index == skinIndex value
3291
- * @param width pixel width every input texture must be (e.g. 1024)
3292
- * @param height pixel height every input texture must be (e.g. 1024)
3293
- * @param options.nullOutDefaults null mat.albedoTexture/bumpTexture after install (default true)
3294
- * @param options.rootUrl base URL for resolving relative paths (default "")
3295
- * @param options.generateMipMaps generate mips on the GPU arrays (default true)
3296
- * @param options.samplingMode BABYLON.Texture.* sampling mode (default TRILINEAR_SAMPLINGMODE)
3297
- */
3298
- installSkinsFromUrlsAsync(layers: {
3299
- albedo: string;
3300
- normal: string;
3301
- }[], width: number, height: number, options?: {
3302
- nullOutDefaults?: boolean;
3303
- rootUrl?: string;
3304
- generateMipMaps?: boolean;
3305
- samplingMode?: number;
3306
- }): Promise<void>;
3307
- private _notifyPluginOfRuntimeSkinChange;
3469
+ * Albedo only for now (normal/MR/AO/emissive fold in later).
3470
+ */
3471
+ enableVatSkinArray(): TOOLKIT.VertexAnimationMaterial;
3472
+ /** Disable VAT skin-array switching (g_vatAnim1.w reverts to the loop-blend flag only). */
3473
+ disableVatSkinArray(): void;
3474
+ /**
3475
+ * Enable per-skin NORMAL Texture2DArray switching on this VAT material. Assign the array first with
3476
+ * addTextureArrayUniform("tkNormalArray", array), then call this. The fragment overrides normalW with
3477
+ * Babylon's native tangent-space bump EXACTLY (its own perturbNormal()/vTBN, our slice texel swapped
3478
+ * in), at the layer decoded from g_vatAnim1.w. Only active where the native tangent frame exists
3479
+ * (BUMP && TANGENT && NORMAL — i.e. the VAT material carries a bumpTexture and the geometry has
3480
+ * tangents); elsewhere it compiles out and the native VAT normal stands. Normal array is unmipped.
3481
+ */
3482
+ enableVatSkinArrayNormal(): TOOLKIT.VertexAnimationMaterial;
3483
+ /** Disable VAT per-skin NORMAL array switching (normalW reverts to the native VAT normal). */
3484
+ disableVatSkinArrayNormal(): void;
3485
+ /** Enable VAT per-skin METALLIC-ROUGHNESS switching (metallic=slice.B, roughness=slice.G), decoded at
3486
+ * the layer packed in g_vatAnim1.w. Assign tkMetalRoughArray first. Only active under METALLICWORKFLOW. */
3487
+ enableVatSkinArrayMetalRough(): TOOLKIT.VertexAnimationMaterial;
3488
+ /** Disable VAT per-skin METALLIC-ROUGHNESS switching. */
3489
+ disableVatSkinArrayMetalRough(): void;
3490
+ /** Enable VAT per-skin EMISSIVE switching (finalEmissive = toLinearSpace(slice)) at the packed layer.
3491
+ * Assign tkEmissiveArray first. Works with no base emissive on the material. */
3492
+ enableVatSkinArrayEmissive(): TOOLKIT.VertexAnimationMaterial;
3493
+ /** Disable VAT per-skin EMISSIVE switching. */
3494
+ disableVatSkinArrayEmissive(): void;
3495
+ /** True when VAT per-skin Texture2DArray switching is enabled (see enableVatSkinArray). */
3496
+ get vatSkinArray(): boolean;
3497
+ /** True when VAT per-skin NORMAL Texture2DArray switching is enabled (see enableVatSkinArrayNormal). */
3498
+ get vatSkinArrayNormal(): boolean;
3499
+ /** True when VAT per-skin METALLIC-ROUGHNESS switching is enabled. */
3500
+ get vatSkinArrayMetalRough(): boolean;
3501
+ /** True when VAT per-skin EMISSIVE switching is enabled. */
3502
+ get vatSkinArrayEmissive(): boolean;
3503
+ /** True when ANY VAT per-skin channel is enabled — master gate for the shared vVatSkin varyings + the
3504
+ * packed cell-index decode, so any single channel works without the others. */
3505
+ get vatSkinArrayActive(): boolean;
3308
3506
  }
3309
3507
  /**
3310
3508
  * Static renderer reference emitted on Animator metadata for each VAT target.
@@ -3409,13 +3607,14 @@ declare namespace TOOLKIT {
3409
3607
  static GetOrCreate(guid: string, scene: BABYLON.Scene, rootUrl?: string, lazyLoadTextures?: boolean): TOOLKIT.VertexAnimationController;
3410
3608
  /** Collect unique renderer targets from the flat VAT settings array emitted by C#. */
3411
3609
  static CollectRendererTargets(settings: TOOLKIT.IVertexAnimationSettings[]): TOOLKIT.IVertexAnimationRendererReference[];
3412
- /** Set a per-mesh runtime skin index. Stored on the mesh as `runtimeSkinIndex`.
3413
- * Each Babylon InstancedMesh under one VAT controller can carry an independent value;
3414
- * the value is packed into g_vatAnim1.w during _tick() and decoded by the shader.
3415
- * Unset / out-of-range values are treated as 0 (the default skin = layer 0 of the runtime arrays). */
3416
- static SetSkinIndex(mesh: BABYLON.AbstractMesh, skinIndex: number): void;
3417
- /** Read a per-mesh runtime skin index. Returns 0 when the mesh has no value set. */
3418
- static GetSkinIndex(mesh: BABYLON.AbstractMesh): number;
3610
+ /** Set a per-mesh skin layer index for VAT per-skin Texture2DArray switching. Stored on the mesh as
3611
+ * `atlasCellIndex` and packed into g_vatAnim1.w during _tick (alongside the loop-blend flag), so
3612
+ * each InstancedMesh under one VAT controller can display a different array layer with no extra
3613
+ * vertex buffer. Requires enableVatSkinArray() on the material. Unset / out-of-range values are
3614
+ * treated as 0 (the first array layer). */
3615
+ static SetAtlasCellIndex(mesh: BABYLON.AbstractMesh, cellIndex: number): void;
3616
+ /** Read a per-mesh atlas cell index. Returns 0 when the mesh has no value set. */
3617
+ static GetAtlasCellIndex(mesh: BABYLON.AbstractMesh): number;
3419
3618
  /** Dispose every controller (optionally only those tied to a given scene). */
3420
3619
  static DisposeAll(scene?: BABYLON.Scene): void;
3421
3620
  readonly guid: string;
@@ -3467,28 +3666,6 @@ declare namespace TOOLKIT {
3467
3666
  getClip(name: string): IVertexAnimationClip | null;
3468
3667
  setSpeed(speed: number): void;
3469
3668
  setLoop(loop: boolean): void;
3470
- /** Install runtime skin texture arrays on every registered VAT material plugin.
3471
- * Convenience fan-out — equivalent to calling material.setRuntimeSkinArrays() on each material. */
3472
- setRuntimeSkinArrays(albedoArray: BABYLON.BaseTexture, normalArray: BABYLON.BaseTexture, nullOutDefaults?: boolean): void;
3473
- /** Remove runtime skin arrays from every registered VAT material plugin. */
3474
- clearRuntimeSkinArrays(): void;
3475
- /**
3476
- * Convenience: load skin texture pairs from URLs into RawTexture2DArrays and install them
3477
- * on every registered VAT material. Builds the arrays exactly once and shares the GPU
3478
- * resources across all materials — no per-material duplication. Uses this controller's
3479
- * `rootUrl` for relative path resolution unless an explicit one is passed in options.
3480
- *
3481
- * See VertexAnimationMaterial.installSkinsFromUrlsAsync for parameter semantics.
3482
- */
3483
- installSkinsFromUrlsAsync(layers: {
3484
- albedo: string;
3485
- normal: string;
3486
- }[], width: number, height: number, options?: {
3487
- nullOutDefaults?: boolean;
3488
- rootUrl?: string;
3489
- generateMipMaps?: boolean;
3490
- samplingMode?: number;
3491
- }): Promise<void>;
3492
3669
  setCurrentTime(time: number): void;
3493
3670
  getCurrentTime(): number;
3494
3671
  addPlugin(plugin: VertexAnimationMaterialPlugin): void;
@@ -3568,11 +3745,6 @@ declare namespace TOOLKIT {
3568
3745
  private _targetRendererGuid;
3569
3746
  /** 1×1 fallback texture — keeps WebGPU bind groups valid before a clip is assigned */
3570
3747
  private _placeholderTexture;
3571
- /** 1×1×1 fallback texture-array — keeps WebGPU bind groups valid before runtime skin
3572
- * arrays are installed. Two variants: a grey (0.5, 0.5, 0.5, 1) for albedo and a
3573
- * flat-up (0.5, 0.5, 1.0, 1) for tangent-space normal. */
3574
- private _placeholderAlbedoArray;
3575
- private _placeholderNormalArray;
3576
3748
  private _cc_posTex;
3577
3749
  private _cc_normTex;
3578
3750
  private _cc_prevPosTex;
@@ -3598,12 +3770,7 @@ declare namespace TOOLKIT {
3598
3770
  getSamplers(samplers: string[]): void;
3599
3771
  getAttributes(attributes: string[], scene: BABYLON.Scene, mesh: BABYLON.AbstractMesh): void;
3600
3772
  prepareDefines(defines: BABYLON.MaterialDefines, scene: BABYLON.Scene, mesh: BABYLON.AbstractMesh): void;
3601
- /** Called by VertexAnimationMaterial when runtime skin arrays change so we can mark
3602
- * defines dirty and force a shader rebuild. */
3603
- onRuntimeSkinArraysChanged(): void;
3604
3773
  bindForSubMesh(uniformBuffer: BABYLON.UniformBuffer, scene: BABYLON.Scene, engine: BABYLON.AbstractEngine, subMesh: BABYLON.SubMesh): void;
3605
- /** Bind runtime skin texture arrays (or their 1-layer placeholder fallbacks). */
3606
- private _pushRuntimeSkinSamplers;
3607
3774
  /**
3608
3775
  * Wire this material to a VertexAnimationController for the given settings.
3609
3776
  * Called from the AnimationState machine at scene-load time.
@@ -3656,14 +3823,20 @@ declare namespace TOOLKIT {
3656
3823
  private getWGSLVertexDefinitions;
3657
3824
  private getWGSLVertexPositionCode;
3658
3825
  private getWGSLVertexNormalCode;
3659
- private getGLSLVertexMainEnd;
3660
- private getWGSLVertexMainEnd;
3661
- private getGLSLFragmentDefinitions;
3662
- private getGLSLFragmentAlbedoCode;
3663
- private getGLSLFragmentNormalCode;
3664
- private getWGSLFragmentDefinitions;
3665
- private getWGSLFragmentAlbedoCode;
3666
- private getWGSLFragmentNormalCode;
3826
+ private getGLSLSkinLayerVaryingCode;
3827
+ private getGLSLSkinFragmentDefs;
3828
+ private getGLSLSkinAlbedoCode;
3829
+ private getGLSLSkinNormalCode;
3830
+ private getGLSLSkinMetalRoughSampleCode;
3831
+ private getGLSLSkinMetalRoughApplyCode;
3832
+ private getGLSLSkinEmissiveCode;
3833
+ private getWGSLSkinLayerVaryingCode;
3834
+ private getWGSLSkinFragmentDefs;
3835
+ private getWGSLSkinAlbedoCode;
3836
+ private getWGSLSkinNormalCode;
3837
+ private getWGSLSkinMetalRoughSampleCode;
3838
+ private getWGSLSkinMetalRoughApplyCode;
3839
+ private getWGSLSkinEmissiveCode;
3667
3840
  }
3668
3841
  }
3669
3842
  declare namespace PROJECT {
@@ -9808,6 +9981,55 @@ declare namespace TOOLKIT {
9808
9981
  customData?: any;
9809
9982
  }
9810
9983
  }
9984
+ declare namespace TOOLKIT {
9985
+ /**
9986
+ * Builds and caches BABYLON.RawTexture2DArray channels for the per-skin Texture2DArray switching
9987
+ * system (see CustomShaderMaterial.enableSkinArray / SkinArraySwitchingPlugin).
9988
+ *
9989
+ * Each PBR channel (albedo, normal, metallic-roughness, occlusion, emissive) becomes one array of N
9990
+ * same-size layers — one layer per skin. The layers are loaded from N separate image URLs and stacked
9991
+ * into a single GPU array texture with mipmaps + trilinear + anisotropic filtering, so every skin gets
9992
+ * a full-resolution slice with its own clean mip chain (no atlas cross-cell bleed). The array is shared
9993
+ * by every mesh/instance that uses the same skin set — the pixel data exists once, and each draw merely
9994
+ * selects a layer index.
9995
+ *
9996
+ * Color space: bytes are stored raw (RGBA8). sRGB channels (albedo, emissive) are linearized in the
9997
+ * shader (toLinearSpace), so this builder is channel-agnostic — pass the URLs and it stacks them.
9998
+ *
9999
+ * @class SkinTextureArray
10000
+ */
10001
+ class SkinTextureArray {
10002
+ /** Cache keyed by the joined URL list. The PROMISE is cached (not just the result) so concurrent
10003
+ * Build() calls for the same skin set (e.g. one per mesh) all await ONE build and share ONE GPU
10004
+ * array — no duplicate VRAM, no duplicate CPU mip work. Failed builds evict so a retry can happen. */
10005
+ private static _cache;
10006
+ /**
10007
+ * Build (or fetch from cache) a Texture2DArray from N image URLs. All layers are resampled to the
10008
+ * FIRST image's dimensions (arrays require uniform layer size). Returns a Promise that resolves to
10009
+ * the array texture (or null when no URLs are supplied / loading fails). Shared across all callers
10010
+ * with the same URL list.
10011
+ */
10012
+ static Build(scene: BABYLON.Scene, urls: string[], options?: {
10013
+ samplingMode?: number;
10014
+ aniso?: number;
10015
+ invertY?: boolean;
10016
+ mipmaps?: boolean;
10017
+ }): Promise<BABYLON.RawTexture2DArray>;
10018
+ /** Upload a CPU-generated mip chain to every layer (works around the broken WebGPU array auto-mip-gen).
10019
+ * Each level is a box-downsample of the previous, uploaded via engine.updateRawTexture2DArray(.., mipLevel). */
10020
+ private static _uploadMipChain;
10021
+ /** Box-average (2x2) downsample every layer of a stacked RGBA8 buffer to (dw x dh). Edge-clamped. */
10022
+ private static _downsampleLayers;
10023
+ /** GPU cap on Texture2DArray layers: WebGPU device limit `maxTextureArrayLayers`, else WebGL2
10024
+ * `MAX_ARRAY_TEXTURE_LAYERS`. Falls back to the spec-guaranteed floor (256) when it can't be read. */
10025
+ private static _maxArrayLayers;
10026
+ /** Load every URL as an HTMLImageElement (cross-origin enabled for canvas readback). */
10027
+ private static _loadImages;
10028
+ /** Dispose and drop every cached array (e.g. on scene teardown). Cached entries are promises, so we
10029
+ * resolve each before disposing (handles arrays still finishing their async build). */
10030
+ static Clear(): void;
10031
+ }
10032
+ }
9811
10033
  /** Babylon Toolkit Namespace */
9812
10034
  declare namespace TOOLKIT {
9813
10035
  /**
@@ -10243,6 +10465,149 @@ declare namespace TOOLKIT {
10243
10465
  private static LinearToGamma;
10244
10466
  }
10245
10467
  }
10468
+ declare namespace TOOLKIT {
10469
+ /**
10470
+ * Babylon Script Component — Texture Atlas Skin (Texture2DArray "skin slices")
10471
+ *
10472
+ * Mesh-first skin controller: ensures THIS component's owner mesh renders at a chosen skin index.
10473
+ * The mesh's type decides the delivery mechanism; every skinned submesh of the mesh uses the SAME
10474
+ * index (one index per mesh, changeable at runtime via setSkinIndex). Backed by a per-skin
10475
+ * Texture2DArray ("skin slices"): the surface UVs are sampled as-is and the layer index picks the
10476
+ * slice (full-res per-skin slices, each with its own clean mip chain — no atlas cross-cell bleed and
10477
+ * no UV remap).
10478
+ *
10479
+ * Authored properties (auto-filled from the Unity editor):
10480
+ * Every channel below is OPTIONAL and INDEPENDENT — supply only the ones you want to swap. The shared
10481
+ * layer index works for whichever channel(s) are present, so e.g. emissive-only (brake lights on/off)
10482
+ * needs no albedo. Absent channels fall through to the material's native map.
10483
+ * - albedoSlices — per-skin albedo (sRGB, mipped) → tkAlbedoArray, overrides surfaceAlbedo.
10484
+ * - normalSlices — per-skin tangent-space normals (linear, UNMIPPED) → tkNormalArray, fed through
10485
+ * Babylon's native perturbNormal()/vTBN so it matches the engine's bump EXACTLY. Only applied where
10486
+ * the mesh has a tangent frame (BUMP+TANGENT). Unmipped because box-downsampled normals grain.
10487
+ * - metallicSlices — per-skin metallic-roughness (glTF B=metallic, G=roughness; linear, mipped) →
10488
+ * tkMetalRoughArray, overrides metallicRoughness under METALLICWORKFLOW.
10489
+ * - emissiveSlices — per-skin emissive (sRGB, mipped) → tkEmissiveArray, overrides finalEmissive (works
10490
+ * with no base emissive — e.g. brake lights).
10491
+ * - occlusionSlices — reserved for a later phase (parsed but NOT yet stacked; AO has no exact PBR hook).
10492
+ * - textureData — optional skin manifest (JSON): { count, skins:[ { index, name } ], albedoLayers:[] }.
10493
+ * Synthesized from the slice count when absent.
10494
+ * - defaultIndex — the skin index this mesh starts at (applied to all skinned parts).
10495
+ * - skinnedParts — the submesh materials (Unity Material[]) to skin-switch on this mesh.
10496
+ *
10497
+ * Mesh-type routing:
10498
+ * - Regular BABYLON.Mesh → CLONE each part material (private to this mesh, preserves the
10499
+ * CustomShaderMaterial subclass + skin-array API) and drive a shared
10500
+ * layer uniform. Clones the MultiMaterial too so the swap stays local.
10501
+ * - BABYLON.InstancedMesh → shared material configured once; per-instance tkSkinLayer buffer.
10502
+ * - VAT (VertexAnimationMaterial) → shared material configured once (enableVatSkinArray); the layer is
10503
+ * decoded in-shader from the cell index packed into g_vatAnim1.w, so each
10504
+ * VAT instance under one controller can show a different slice.
10505
+ *
10506
+ * @class TextureAtlasSkin
10507
+ */
10508
+ class TextureAtlasSkin extends TOOLKIT.ScriptComponent {
10509
+ private abtractMesh;
10510
+ private textureData;
10511
+ private defaultIndex;
10512
+ private skinnedParts;
10513
+ private albedoSlices;
10514
+ private normalSlices;
10515
+ private metallicSlices;
10516
+ private occlusionSlices;
10517
+ private emissiveSlices;
10518
+ /** Guard so wiring runs once. */
10519
+ private _applied;
10520
+ /** Parsed (or synthesized) manifest. */
10521
+ private _manifest;
10522
+ /** The mesh's current skin index (all skinned parts share it). */
10523
+ private _currentIndex;
10524
+ /** Configured skin targets, with the delivery mechanism resolved per material. */
10525
+ private _targets;
10526
+ /** Per-mesh cloned MultiMaterial (regular-mesh path) so submaterial swaps stay local to this mesh. */
10527
+ private _clonedMultiMaterial;
10528
+ constructor(transform: BABYLON.TransformNode, scene: BABYLON.Scene, properties?: any, alias?: string);
10529
+ protected awake(): void;
10530
+ protected start(): void;
10531
+ /** Configure every skinned part for the skin array and apply defaultIndex. Idempotent (runs once). */
10532
+ applySkins(): void;
10533
+ /**
10534
+ * Switch every skinned part of THIS mesh to skin `index` (scoped to this mesh only).
10535
+ * Out-of-range indices are CLAMPED to [0, skinCount-1] with a warning. Returns the index applied.
10536
+ */
10537
+ setSkinIndex(index: number): number;
10538
+ /** Clamp an index to [0, skinCount-1], warning (with the label) when it was out of range. */
10539
+ private clampIndex;
10540
+ /** Number of skins: skins[] length, else manifest count, else the largest channel's slice count. */
10541
+ skinCount(): number;
10542
+ /** Largest per-skin slice count across every supplied channel (albedo/normal/MR/occlusion/emissive) —
10543
+ * so the skin count is correct even when albedo is absent (e.g. an emissive-only material). */
10544
+ private maxChannelSliceCount;
10545
+ /** The mesh's current skin index. */
10546
+ getSkinIndex(): number;
10547
+ /** Build the albedo Texture2DArray (and, when normalSlices are present, an unmipped/linear normal
10548
+ * Texture2DArray) from the slice images, assign them to the material, then enable per-skin array
10549
+ * switching for the mesh type. Async (images load after start()): the layer index is applied again
10550
+ * once the arrays are ready.
10551
+ * - regular → shared layer uniform (setSkinLayer)
10552
+ * - instanced → per-instance tkSkinLayer attribute buffer
10553
+ * - vat → layer decoded in-shader from the cell index packed in g_vatAnim1.w (enableVatSkinArray) */
10554
+ private configureMaterial;
10555
+ /** Resolve the albedo channel slice URLs. PREFERS the exported slice TEXTURE references (albedoSlices)
10556
+ * — auto-parsed and resolved against the scene root — falling back to manifest.albedoLayers filenames. */
10557
+ private albedoChannelUrls;
10558
+ /** Resolve the normal channel slice URLs (one per skin, same order as albedo). PREFERS the exported
10559
+ * slice TEXTURE references (normalSlices), falling back to manifest.normalLayers filenames. Returns
10560
+ * null when no normal slices were authored (normal switching then stays off and the native normal
10561
+ * stands). */
10562
+ private normalChannelUrls;
10563
+ /** Resolve the metallic-roughness channel slice URLs (glTF layout: B=metallic, G=roughness; one per
10564
+ * skin, same order as albedo). PREFERS exported slice references (metallicSlices), else manifest
10565
+ * metalRoughLayers. Null when no MR slices were authored (MR switching stays off). */
10566
+ private metalRoughChannelUrls;
10567
+ /** Resolve the emissive channel slice URLs (one per skin, same order as albedo). PREFERS exported slice
10568
+ * references (emissiveSlices), else manifest emissiveLayers. Null when no emissive slices were authored
10569
+ * (emissive switching stays off). Drives emissive-only swaps such as brake lights on/off. */
10570
+ private emissiveChannelUrls;
10571
+ /** Resolve each exported slice to a URL. The slices are IUnityTexture references (a filename relative
10572
+ * to the scene root — NOT auto-loaded into a BABYLON.Texture, so no duplicate GPU upload):
10573
+ * url = GetRootUrl(scene) + filename. Also tolerates an already-loaded BABYLON.Texture (.url). */
10574
+ private sliceUrls;
10575
+ /** Resolve a manifest layer filename to a loadable URL (absolute passes through; else scene root). */
10576
+ private resolveLayerUrl;
10577
+ /** Apply the current index to every target via its mesh-type-specific path (this mesh only). */
10578
+ private applyIndexToTargets;
10579
+ /** Select the per-skin array layer for one target:
10580
+ * - vat → the per-mesh cell index packed into g_vatAnim1.w by the VAT controller
10581
+ * - instanced → a per-instance tkSkinLayer attribute buffer
10582
+ * - regular → a shared material uniform on the (cloned) regular-mesh material
10583
+ * Safe to call before the array finishes loading — the value persists and takes effect once the
10584
+ * shader is gated on. */
10585
+ private applyLayerToMaterial;
10586
+ /** True when this mesh must use the per-instance buffer: it is an InstancedMesh, OR a source Mesh
10587
+ * that has hardware instances (instances share sourceMesh.material, so a private clone + uniform
10588
+ * layer is impossible — every instance would read the same shared value). */
10589
+ private isInstancedSetup;
10590
+ /** Register the per-instance tkSkinLayer buffer (one float) on the source mesh if not already present. */
10591
+ private ensureSkinLayerBuffer;
10592
+ /** Collect the host mesh's material slots: each MultiMaterial sub-material, or the single material. */
10593
+ private getMeshSlots;
10594
+ /** Match a resolved part material (by identity, then by name) to one of the mesh's slots. */
10595
+ private matchSlot;
10596
+ /** Resolve a skinnedParts entry to a live material (already-a-material, wrapped, by name). */
10597
+ private resolvePartMaterial;
10598
+ /** Extract a material name from a skinnedParts entry (string, live material, or serialized ref). */
10599
+ private partName;
10600
+ /** Clone a regular mesh's part material (and its MultiMaterial) so this mesh's skin is private. */
10601
+ private cloneForRegular;
10602
+ /** textureData may arrive as a JSON string or an object. Returns null when there is no skins[] list. */
10603
+ private parseManifest;
10604
+ private toInt;
10605
+ private isConfigured;
10606
+ private markConfigured;
10607
+ /** Human-readable label for a delivery mechanism (used in setup logging). */
10608
+ private describeKind;
10609
+ }
10610
+ }
10246
10611
  declare namespace TOOLKIT {
10247
10612
  /**
10248
10613
  * Babylon Toolkit User Interface Controls