@babylonjs-toolkit/next 9.11.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.
@@ -9,7 +9,7 @@ import { Light } from '@babylonjs/core/Lights/light';
9
9
  import { Vector3, Quaternion, Vector2, Vector4, Matrix, TmpVectors } from '@babylonjs/core/Maths/math.vector';
10
10
  import { FreeCamera } from '@babylonjs/core/Cameras/freeCamera';
11
11
  import { Engine } from '@babylonjs/core/Engines/engine';
12
- import { SphericalPolynomial, HDRCubeTexture, EXRCubeTexture, SphericalHarmonics, Constants, PostProcess, BlurPostProcess, DefaultRenderingPipeline, SSAORenderingPipeline, SSRRenderingPipeline, MotionBlurPostProcess } from '@babylonjs/core';
12
+ import { SphericalPolynomial, HDRCubeTexture, EXRCubeTexture, SphericalHarmonics, Constants, SerializationHelper, PostProcess, BlurPostProcess, DefaultRenderingPipeline, SSAORenderingPipeline, SSRRenderingPipeline, MotionBlurPostProcess } from '@babylonjs/core';
13
13
  import { Material } from '@babylonjs/core/Materials/material';
14
14
  import { InstancedMesh } from '@babylonjs/core/Meshes/instancedMesh';
15
15
  import { CreateNavigationPluginAsync } from '@babylonjs/addons';
@@ -53,7 +53,7 @@ import { ShaderMaterial } from '@babylonjs/core/Materials/shaderMaterial';
53
53
  import { MultiMaterial } from '@babylonjs/core/Materials/multiMaterial';
54
54
  import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight';
55
55
  import { MaterialPluginBase } from '@babylonjs/core/Materials/materialPluginBase';
56
- import { ShaderLanguage, RawTexture2DArray, RawTexture, ShadowDepthWrapper, ImageProcessingConfiguration, ColorCurves } from '@babylonjs/core/Materials';
56
+ import { ShaderLanguage, RawTexture, ShadowDepthWrapper, ImageProcessingConfiguration, ColorCurves, RawTexture2DArray } from '@babylonjs/core/Materials';
57
57
  import { SpotLight } from '@babylonjs/core/Lights/spotLight';
58
58
  import { UniversalCamera } from '@babylonjs/core/Cameras/universalCamera';
59
59
  import { DirectionalLight } from '@babylonjs/core/Lights/directionalLight';
@@ -98,7 +98,7 @@ import { ShadowOnlyMaterial } from '@babylonjs/materials/shadowOnly/shadowOnlyMa
98
98
  import { SkyMaterial } from '@babylonjs/materials/sky/skyMaterial';
99
99
  import { WaterMaterial } from '@babylonjs/materials/water/waterMaterial';
100
100
  export class SceneManager {
101
- static get Version() { return "9.11.1 - R1"; }
101
+ static get Version() { return "9.12.101 - R1"; }
102
102
  static get Copyright() { return "All rights reserved (c) 2024 Mackey Kinard"; }
103
103
  static GetEngine(scene) {
104
104
  let result = null;
@@ -2824,9 +2824,15 @@ export class ScriptComponent {
2824
2824
  return SceneManager.GetSkinnedMesh(this.transform);
2825
2825
  }
2826
2826
  getTransformMesh() {
2827
+ const atransform = this.transform;
2828
+ if (atransform._skinnedMesh)
2829
+ return atransform._skinnedMesh;
2827
2830
  return (this._transform instanceof Mesh) ? this._transform : null;
2828
2831
  }
2829
2832
  getAbstractMesh() {
2833
+ const atransform = this.transform;
2834
+ if (atransform._skinnedMesh)
2835
+ return atransform._skinnedMesh;
2830
2836
  return (this._transform instanceof AbstractMesh) ? this._transform : null;
2831
2837
  }
2832
2838
  getInstancedMesh() {
@@ -3787,6 +3793,31 @@ export var MaterialAlphaMode;
3787
3793
  export class CubeTextureLoader {
3788
3794
  }
3789
3795
  export class CVTOOLS_unity_metadata {
3796
+ static SuppressReflectionProbeDiffuse(texture) {
3797
+ if (texture == null)
3798
+ return;
3799
+ texture.sphericalPolynomial = null;
3800
+ const internal = texture._texture;
3801
+ if (internal != null) {
3802
+ internal._sphericalPolynomial = null;
3803
+ internal._sphericalPolynomialComputed = true;
3804
+ }
3805
+ }
3806
+ static ApplyGlobalReflectionProbeDiffuse(texture, scene, createPolynomialsFromFaces = false) {
3807
+ if (texture == null || createPolynomialsFromFaces === true)
3808
+ return;
3809
+ const sp = (scene != null) ? SceneManager.GetImageBasedLighting(scene) : null;
3810
+ if (sp == null) {
3811
+ CVTOOLS_unity_metadata.SuppressReflectionProbeDiffuse(texture);
3812
+ return;
3813
+ }
3814
+ texture.sphericalPolynomial = sp;
3815
+ const internal = texture._texture;
3816
+ if (internal != null) {
3817
+ internal._sphericalPolynomial = sp;
3818
+ internal._sphericalPolynomialComputed = true;
3819
+ }
3820
+ }
3790
3821
  constructor(loader) {
3791
3822
  this.name = SceneManager.CVTOOLS_NAME;
3792
3823
  this.enabled = false;
@@ -3972,7 +4003,7 @@ export class CVTOOLS_unity_metadata {
3972
4003
  finishComplete() {
3973
4004
  if (this._parseScene === true) {
3974
4005
  if (this._rootUrl != null && this._rootUrl.toLowerCase().indexOf("repo.babylontoolkit.com") >= 0) {
3975
- this._applyRepoWatermark(this._babylonScene);
4006
+ this.applyRepoWatermark(this._babylonScene);
3976
4007
  }
3977
4008
  this.startParsing();
3978
4009
  SceneManager.RenderLoopReady = true;
@@ -4144,6 +4175,7 @@ export class CVTOOLS_unity_metadata {
4144
4175
  }
4145
4176
  }
4146
4177
  }
4178
+ const createPolynomialsFromFaces = scene.extras?.metadata?.createpolynomials ?? false;
4147
4179
  if (this._reflectionMap != null && this._reflectionCache != null) {
4148
4180
  let tindex = 0;
4149
4181
  for (const key in this._reflectionMap) {
@@ -4163,6 +4195,7 @@ export class CVTOOLS_unity_metadata {
4163
4195
  if (cachedCubeTexture != null) {
4164
4196
  if (ctx.material != null && Utilities.HasOwnProperty(ctx.material, "reflectionTexture")) {
4165
4197
  ctx.material.reflectionTexture = cachedCubeTexture;
4198
+ CVTOOLS_unity_metadata.ApplyGlobalReflectionProbeDiffuse(cachedCubeTexture, this._babylonScene, createPolynomialsFromFaces);
4166
4199
  }
4167
4200
  }
4168
4201
  else {
@@ -4175,8 +4208,8 @@ export class CVTOOLS_unity_metadata {
4175
4208
  buffer: bytes,
4176
4209
  prefiltered: true,
4177
4210
  forcedExtension: newCubeExtension,
4178
- createPolynomials: true,
4179
- onLoad: () => { },
4211
+ createPolynomials: createPolynomialsFromFaces,
4212
+ onLoad: () => { CVTOOLS_unity_metadata.ApplyGlobalReflectionProbeDiffuse(newCubeTexture, this._babylonScene, createPolynomialsFromFaces); },
4180
4213
  onError: (msg, ex) => console.error(msg, ex),
4181
4214
  });
4182
4215
  newCubeTexture.name = ctx.name;
@@ -4189,6 +4222,7 @@ export class CVTOOLS_unity_metadata {
4189
4222
  this._reflectionCache[ctx.mapkey] = newCubeTexture;
4190
4223
  if (ctx.material != null && Utilities.HasOwnProperty(ctx.material, "reflectionTexture")) {
4191
4224
  ctx.material.reflectionTexture = newCubeTexture;
4225
+ CVTOOLS_unity_metadata.ApplyGlobalReflectionProbeDiffuse(newCubeTexture, this._babylonScene, createPolynomialsFromFaces);
4192
4226
  }
4193
4227
  }
4194
4228
  }
@@ -4578,7 +4612,7 @@ export class CVTOOLS_unity_metadata {
4578
4612
  if (metadata.ambientlighting != null && metadata.ambientlighting === 1) {
4579
4613
  const ascene = babylonScene;
4580
4614
  if (ascene._AmbientLight == null) {
4581
- ascene._AmbientLight = new HemisphericLight("Ambient Lighting", new Vector3(0, 1, 0), babylonScene);
4615
+ ascene._AmbientLight = new HemisphericLight("Ambient Light", new Vector3(0, 1, 0), babylonScene);
4582
4616
  ascene._AmbientLight.falloffType = Light.FALLOFF_STANDARD;
4583
4617
  switch (ambientLightmapMode) {
4584
4618
  case 2:
@@ -4593,28 +4627,19 @@ export class CVTOOLS_unity_metadata {
4593
4627
  }
4594
4628
  }
4595
4629
  if (ascene._AmbientLight != null) {
4596
- if (metadata.ambientskymode === "skybox" || metadata.ambientoverride === false) {
4630
+ ascene._AmbientLight.diffuse = (metadata.ambientskycolor != null) ? Utilities.ParseColor3(metadata.ambientskycolor, Color3.Gray()) : Color3.Gray();
4631
+ ascene._AmbientLight.specular = (metadata.ambientspecularcolor != null) ? Color3.Black() : Color3.Black();
4632
+ ascene._AmbientLight.groundColor = (metadata.ambientgroundcolor != null) ? Utilities.ParseColor3(metadata.ambientgroundcolor, Color3.Gray()) : Color3.Gray();
4633
+ ascene._AmbientLight.intensity = (metadata.ambientlightintensity != null) ? metadata.ambientlightintensity : 1.0;
4634
+ if (SceneManager.GetImageBasedLighting(babylonScene) != null) {
4597
4635
  ascene._AmbientLight.name = "Ambient Blacklight";
4598
4636
  ascene._AmbientLight.diffuse = Color3.Black();
4599
4637
  ascene._AmbientLight.specular = Color3.Black();
4600
4638
  ascene._AmbientLight.groundColor = Color3.Black();
4601
- if (metadata.ambientlightintensity != null)
4602
- ascene._AmbientLight.intensity = metadata.ambientlightintensity;
4603
- ascene._AmbientLight.intensity *= SceneManager.AmbientLightIntensity;
4604
- ascene._AmbientLight.intensityMode = Light.INTENSITYMODE_ILLUMINANCE;
4605
- }
4606
- else {
4607
- if (metadata.ambientskycolor != null)
4608
- ascene._AmbientLight.diffuse = Utilities.ParseColor3(metadata.ambientskycolor, Color3.Gray());
4609
- if (metadata.ambientspecularcolor != null)
4610
- ascene._AmbientLight.specular = Utilities.ParseColor3(metadata.ambientspecularcolor, Color3.Black());
4611
- if (metadata.ambientgroundcolor != null)
4612
- ascene._AmbientLight.groundColor = Utilities.ParseColor3(metadata.ambientgroundcolor, Color3.Gray());
4613
- if (metadata.ambientlightintensity != null)
4614
- ascene._AmbientLight.intensity = metadata.ambientlightintensity;
4615
- ascene._AmbientLight.intensity *= SceneManager.AmbientLightIntensity;
4616
- ascene._AmbientLight.intensityMode = Light.INTENSITYMODE_ILLUMINANCE;
4639
+ ascene._AmbientLight.intensity = 1.0;
4617
4640
  }
4641
+ ascene._AmbientLight.intensity *= SceneManager.AmbientLightIntensity;
4642
+ ascene._AmbientLight.intensityMode = Light.INTENSITYMODE_ILLUMINANCE;
4618
4643
  }
4619
4644
  }
4620
4645
  if (metadata.fogmode != null) {
@@ -4835,8 +4860,8 @@ export class CVTOOLS_unity_metadata {
4835
4860
  const diffuseIbl = Scalar.Clamp(ibl, 0.0, IBL_MAX);
4836
4861
  const specIbl = Scalar.Clamp(derivedRefl, 0.0, LEVEL_MAX);
4837
4862
  const hasDiffuseIbl = Array.isArray(environment.sh) && environment.sh.length === 27;
4838
- const applyPolynomials = (environment.sky === "skybox" && hasDiffuseIbl === true);
4839
- if (applyPolynomials)
4863
+ const useUnitySH = (mode === 0 && hasDiffuseIbl === true);
4864
+ if (useUnitySH)
4840
4865
  this.generateSphericalHarmonics(babylonScene, environment.sh, diffuseIbl);
4841
4866
  if (envinfo != null && envinfo !== "") {
4842
4867
  try {
@@ -4859,32 +4884,25 @@ export class CVTOOLS_unity_metadata {
4859
4884
  });
4860
4885
  babylonScene.environmentTexture = newCubeTexture;
4861
4886
  babylonScene.environmentTexture.name = ("assets/" + envinfo.name);
4862
- babylonScene.environmentTexture.level = applyPolynomials ? (specIbl * scale) : specIbl;
4863
- if (applyPolynomials) {
4864
- if (newCubeTexture.isReady()) {
4887
+ babylonScene.environmentTexture.level = useUnitySH ? (specIbl * scale) : specIbl;
4888
+ if (useUnitySH) {
4889
+ const applyUnitySH = () => {
4865
4890
  const sp = SceneManager.GetImageBasedLighting(babylonScene);
4866
4891
  if (sp != null) {
4867
- if (mode === 0) {
4868
- babylonScene.environmentTexture.sphericalPolynomial = sp;
4869
- }
4870
- else {
4892
+ const envTex = babylonScene.environmentTexture;
4893
+ envTex.sphericalPolynomial = sp;
4894
+ const internal = envTex._texture;
4895
+ if (internal != null) {
4896
+ internal._sphericalPolynomial = sp;
4897
+ internal._sphericalPolynomialComputed = true;
4871
4898
  }
4872
4899
  babylonScene.markAllMaterialsAsDirty(Material.AllDirtyFlag);
4873
4900
  }
4874
- }
4875
- else {
4876
- newCubeTexture.onLoadObservable.addOnce(() => {
4877
- const sp = SceneManager.GetImageBasedLighting(babylonScene);
4878
- if (sp != null) {
4879
- if (mode === 0) {
4880
- babylonScene.environmentTexture.sphericalPolynomial = sp;
4881
- }
4882
- else {
4883
- }
4884
- babylonScene.markAllMaterialsAsDirty(Material.AllDirtyFlag);
4885
- }
4886
- });
4887
- }
4901
+ };
4902
+ if (newCubeTexture.isReady())
4903
+ applyUnitySH();
4904
+ else
4905
+ newCubeTexture.onLoadObservable.addOnce(applyUnitySH);
4888
4906
  }
4889
4907
  }
4890
4908
  else {
@@ -5332,7 +5350,43 @@ export class CVTOOLS_unity_metadata {
5332
5350
  }
5333
5351
  }
5334
5352
  }
5335
- _applyRepoWatermark(scene) {
5353
+ _parseUniformAndSamplerProperties(context, material, sourceMaterial) {
5354
+ const commonConstant = (material.extras != null && material.extras.metadata != null) ? material.extras.metadata : null;
5355
+ if (commonConstant != null) {
5356
+ if (Utilities.HasOwnProperty(sourceMaterial, "universalMaterial")) {
5357
+ const universalMaterial = sourceMaterial;
5358
+ if (commonConstant.customTextures) {
5359
+ for (const tkey in commonConstant.customTextures) {
5360
+ const tvalue = commonConstant.customTextures[tkey];
5361
+ if (tvalue != null)
5362
+ universalMaterial.checkSampler(tkey);
5363
+ }
5364
+ }
5365
+ if (commonConstant.customVectors) {
5366
+ for (const vkey in commonConstant.customVectors) {
5367
+ const vvalue = commonConstant.customVectors[vkey];
5368
+ if (vvalue != null)
5369
+ universalMaterial.checkUniform(vkey, "vec4", Vector4.FromArray(vvalue));
5370
+ }
5371
+ }
5372
+ if (commonConstant.customColors) {
5373
+ for (const ckey in commonConstant.customColors) {
5374
+ const cvalue = commonConstant.customColors[ckey];
5375
+ if (cvalue != null)
5376
+ universalMaterial.checkUniform(ckey, "vec4", Vector4.FromArray(cvalue));
5377
+ }
5378
+ }
5379
+ if (commonConstant.customFloats) {
5380
+ for (const fkey in commonConstant.customFloats) {
5381
+ const fvalue = commonConstant.customFloats[fkey];
5382
+ if (fvalue != null)
5383
+ universalMaterial.checkUniform(fkey, "float", fvalue);
5384
+ }
5385
+ }
5386
+ }
5387
+ }
5388
+ }
5389
+ applyRepoWatermark(scene) {
5336
5390
  const canvas = scene.getEngine().getRenderingCanvas();
5337
5391
  if (canvas == null)
5338
5392
  return;
@@ -5362,8 +5416,9 @@ export class CVTOOLS_unity_metadata {
5362
5416
  el.title = WM_TITLE;
5363
5417
  el.style.cssText = [
5364
5418
  "position:absolute",
5365
- "bottom:8px",
5366
- "left:10px",
5419
+ "bottom:6px",
5420
+ "left:50%",
5421
+ "transform:translateX(-50%)",
5367
5422
  "color:rgba(0,204,0,0.5)",
5368
5423
  "font:13px/1 Arial,sans-serif",
5369
5424
  "pointer-events:auto",
@@ -5413,6 +5468,10 @@ export class CVTOOLS_unity_metadata {
5413
5468
  s.visibility = "visible";
5414
5469
  s.opacity = "";
5415
5470
  }
5471
+ if (s.left !== "50%")
5472
+ s.left = "50%";
5473
+ if (s.transform !== "translateX(-50%)")
5474
+ s.transform = "translateX(-50%)";
5416
5475
  }
5417
5476
  });
5418
5477
  scene.onDisposeObservable.addOnce(() => {
@@ -5433,42 +5492,6 @@ export class CVTOOLS_unity_metadata {
5433
5492
  }
5434
5493
  });
5435
5494
  }
5436
- _parseUniformAndSamplerProperties(context, material, sourceMaterial) {
5437
- const commonConstant = (material.extras != null && material.extras.metadata != null) ? material.extras.metadata : null;
5438
- if (commonConstant != null) {
5439
- if (Utilities.HasOwnProperty(sourceMaterial, "universalMaterial")) {
5440
- const universalMaterial = sourceMaterial;
5441
- if (commonConstant.customTextures) {
5442
- for (const tkey in commonConstant.customTextures) {
5443
- const tvalue = commonConstant.customTextures[tkey];
5444
- if (tvalue != null)
5445
- universalMaterial.checkSampler(tkey);
5446
- }
5447
- }
5448
- if (commonConstant.customVectors) {
5449
- for (const vkey in commonConstant.customVectors) {
5450
- const vvalue = commonConstant.customVectors[vkey];
5451
- if (vvalue != null)
5452
- universalMaterial.checkUniform(vkey, "vec4", Vector4.FromArray(vvalue));
5453
- }
5454
- }
5455
- if (commonConstant.customColors) {
5456
- for (const ckey in commonConstant.customColors) {
5457
- const cvalue = commonConstant.customColors[ckey];
5458
- if (cvalue != null)
5459
- universalMaterial.checkUniform(ckey, "vec4", Vector4.FromArray(cvalue));
5460
- }
5461
- }
5462
- if (commonConstant.customFloats) {
5463
- for (const fkey in commonConstant.customFloats) {
5464
- const fvalue = commonConstant.customFloats[fkey];
5465
- if (fvalue != null)
5466
- universalMaterial.checkUniform(fkey, "float", fvalue);
5467
- }
5468
- }
5469
- }
5470
- }
5471
- }
5472
5495
  }
5473
5496
  CVTOOLS_unity_metadata.IBL_TINY_VALUE = 0.001;
5474
5497
  CVTOOLS_unity_metadata.IBL_SPEC_FACTOR = 1.0;
@@ -5520,10 +5543,18 @@ export class CustomShaderMaterial extends PBRMaterial {
5520
5543
  this.shader = null;
5521
5544
  this.plugin = null;
5522
5545
  this._unityLightingPlugin = null;
5546
+ this._skinArray = false;
5547
+ this._skinArrayNormal = false;
5548
+ this._skinArrayMetalRough = false;
5549
+ this._skinArrayEmissive = false;
5550
+ this._skinLayerUniform = false;
5551
+ this._skinArraySwitchingPlugin = null;
5523
5552
  this.shader = "CustomShaderMaterial";
5524
5553
  this.addFloatUniform("tkIblDiffuseScale", 1.0);
5525
5554
  this.addFloatUniform("tkIblSpecularScale", 1.0);
5555
+ this.addFloatUniform("tkSkinLayerUni", 0.0);
5526
5556
  this._unityLightingPlugin = new UnityStyleLightingPlugin(this, this.shader);
5557
+ this._skinArraySwitchingPlugin = new SkinArraySwitchingPlugin(this);
5527
5558
  }
5528
5559
  initMaterial() {
5529
5560
  if (this["awake"])
@@ -5557,6 +5588,108 @@ export class CustomShaderMaterial extends PBRMaterial {
5557
5588
  this.plugin?.markAllDefinesAsDirty();
5558
5589
  this._unityLightingPlugin?.markAllDefinesAsDirty();
5559
5590
  }
5591
+ enableSkinArray(layerUniform = false) {
5592
+ this._skinArray = true;
5593
+ this._skinLayerUniform = layerUniform;
5594
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
5595
+ this.plugin?.markAllDefinesAsDirty();
5596
+ this._unityLightingPlugin?.markAllDefinesAsDirty();
5597
+ this._skinArraySwitchingPlugin?.markAllDefinesAsDirty();
5598
+ return this;
5599
+ }
5600
+ setSkinLayer(layer) {
5601
+ this._skinLayerUniform = true;
5602
+ this.setFloatValue("tkSkinLayerUni", Math.max(0, Math.floor(layer)));
5603
+ return this;
5604
+ }
5605
+ enableSkinArrayNormal(enabled = true) {
5606
+ this._skinArrayNormal = enabled;
5607
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
5608
+ this.plugin?.markAllDefinesAsDirty();
5609
+ this._unityLightingPlugin?.markAllDefinesAsDirty();
5610
+ this._skinArraySwitchingPlugin?.markAllDefinesAsDirty();
5611
+ return this;
5612
+ }
5613
+ enableSkinArrayMetalRough(enabled = true) {
5614
+ this._skinArrayMetalRough = enabled;
5615
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
5616
+ this.plugin?.markAllDefinesAsDirty();
5617
+ this._unityLightingPlugin?.markAllDefinesAsDirty();
5618
+ this._skinArraySwitchingPlugin?.markAllDefinesAsDirty();
5619
+ return this;
5620
+ }
5621
+ enableSkinArrayEmissive(enabled = true) {
5622
+ this._skinArrayEmissive = enabled;
5623
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
5624
+ this.plugin?.markAllDefinesAsDirty();
5625
+ this._unityLightingPlugin?.markAllDefinesAsDirty();
5626
+ this._skinArraySwitchingPlugin?.markAllDefinesAsDirty();
5627
+ return this;
5628
+ }
5629
+ setSkinArrayLayerMode(uniform) {
5630
+ this._skinLayerUniform = uniform;
5631
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
5632
+ this._skinArraySwitchingPlugin?.markAllDefinesAsDirty();
5633
+ return this;
5634
+ }
5635
+ addTextureArrayUniform(name, texture) {
5636
+ if (this._samplers.indexOf(name) === -1)
5637
+ this._samplers.push(name);
5638
+ this._textures[name] = texture;
5639
+ this.markTextureAsRaw(name);
5640
+ return this;
5641
+ }
5642
+ get skinArray() { return this._skinArray; }
5643
+ get skinArrayNormal() { return this._skinArrayNormal; }
5644
+ get skinArrayMetalRough() { return this._skinArrayMetalRough; }
5645
+ get skinArrayEmissive() { return this._skinArrayEmissive; }
5646
+ get skinArrayActive() {
5647
+ return this._skinArray || this._skinArrayNormal || this._skinArrayMetalRough || this._skinArrayEmissive;
5648
+ }
5649
+ get skinLayerUniform() { return this._skinLayerUniform; }
5650
+ clone(name, cloneTexturesOnlyOnce = true, rootUrl = "") {
5651
+ const scene = this.getScene();
5652
+ const ctor = this.constructor;
5653
+ const result = SerializationHelper.Clone(() => new ctor(name, scene), this, { cloneTexturesOnlyOnce });
5654
+ if (result == null)
5655
+ return result;
5656
+ result.id = name;
5657
+ result.name = name;
5658
+ if (this.stencil && result.stencil)
5659
+ this.stencil.copyTo(result.stencil);
5660
+ this.copyCustomStateTo(result);
5661
+ return result;
5662
+ }
5663
+ copyCustomStateTo(result) {
5664
+ result.universalMaterial = this.universalMaterial;
5665
+ result._defines = Object.assign({}, this._defines);
5666
+ result._uniforms = this._uniforms.slice();
5667
+ result._samplers = this._samplers.slice();
5668
+ result._attributes = this._attributes.slice();
5669
+ result._ubos = (this._ubos != null) ? this._ubos.map((u) => ({ name: u.name, size: u.size, type: u.type, arraySize: u.arraySize })) : null;
5670
+ result._textures = Object.assign({}, this._textures);
5671
+ result._noMatrixTextures = (this._noMatrixTextures != null) ? new Set(this._noMatrixTextures) : null;
5672
+ result._vectors4 = {};
5673
+ for (const n in this._vectors4)
5674
+ result._vectors4[n] = this._vectors4[n].clone();
5675
+ result._vectors3 = {};
5676
+ for (const n in this._vectors3)
5677
+ result._vectors3[n] = this._vectors3[n].clone();
5678
+ result._vectors2 = {};
5679
+ for (const n in this._vectors2)
5680
+ result._vectors2[n] = this._vectors2[n].clone();
5681
+ result._floats = Object.assign({}, this._floats);
5682
+ result._bools = Object.assign({}, this._bools);
5683
+ result._skinArray = this._skinArray;
5684
+ result._skinArrayNormal = this._skinArrayNormal;
5685
+ result._skinArrayMetalRough = this._skinArrayMetalRough;
5686
+ result._skinArrayEmissive = this._skinArrayEmissive;
5687
+ result._skinLayerUniform = this._skinLayerUniform;
5688
+ result.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
5689
+ result.plugin?.markAllDefinesAsDirty();
5690
+ result._unityLightingPlugin?.markAllDefinesAsDirty();
5691
+ result._skinArraySwitchingPlugin?.markAllDefinesAsDirty();
5692
+ }
5560
5693
  addTextureUniform(name, texture) {
5561
5694
  this.checkSampler(name, texture);
5562
5695
  this._textures[name] = texture;
@@ -5999,6 +6132,301 @@ export class UnityStyleLightingPlugin extends CustomShaderMaterialPlugin {
5999
6132
  this.getCustomShaderMaterial().updateCustomBindings(uniformBuffer);
6000
6133
  }
6001
6134
  }
6135
+ export class SkinArraySwitchingPlugin extends CustomShaderMaterialPlugin {
6136
+ constructor(material) {
6137
+ super(material, "SkinArraySwitchingPlugin", 21, { TOOLKIT_SKIN_ARRAY: false, TOOLKIT_SKIN_LAYER_UNIFORM: false, TOOLKIT_SKIN_ARRAY_ALBEDO: false, TOOLKIT_SKIN_ARRAY_NORMAL: false, TOOLKIT_SKIN_ARRAY_MR: false, TOOLKIT_SKIN_ARRAY_EMISSIVE: false }, true, true, false);
6138
+ }
6139
+ isCompatible(shaderLanguage) {
6140
+ return (shaderLanguage === ShaderLanguage.WGSL || shaderLanguage === ShaderLanguage.GLSL);
6141
+ }
6142
+ getClassName() {
6143
+ return "SkinArraySwitchingPlugin";
6144
+ }
6145
+ getAttributes(attributes, scene, mesh) {
6146
+ const mat = this.getCustomShaderMaterial();
6147
+ if (!mat.skinArrayActive)
6148
+ return;
6149
+ if (mat.skinLayerUniform !== true && attributes.indexOf("tkSkinLayer") === -1)
6150
+ attributes.push("tkSkinLayer");
6151
+ }
6152
+ getSamplers(samplers) {
6153
+ const mat = this.getCustomShaderMaterial();
6154
+ if (!mat.skinArrayActive)
6155
+ return;
6156
+ const customSamplers = mat.getCustomSamplers();
6157
+ if (customSamplers != null) {
6158
+ for (const s of customSamplers)
6159
+ if (samplers.indexOf(s) === -1)
6160
+ samplers.push(s);
6161
+ }
6162
+ }
6163
+ prepareDefines(defines, scene, mesh) {
6164
+ const mat = this.getCustomShaderMaterial();
6165
+ defines.TOOLKIT_SKIN_ARRAY = !!mat.skinArrayActive;
6166
+ defines.TOOLKIT_SKIN_LAYER_UNIFORM = !!mat.skinLayerUniform;
6167
+ defines.TOOLKIT_SKIN_ARRAY_ALBEDO = !!mat.skinArray;
6168
+ defines.TOOLKIT_SKIN_ARRAY_NORMAL = !!mat.skinArrayNormal;
6169
+ defines.TOOLKIT_SKIN_ARRAY_MR = !!mat.skinArrayMetalRough;
6170
+ defines.TOOLKIT_SKIN_ARRAY_EMISSIVE = !!mat.skinArrayEmissive;
6171
+ }
6172
+ getCustomCode(shaderType, shaderLanguage) {
6173
+ const wgsl = (shaderLanguage === ShaderLanguage.WGSL);
6174
+ if (shaderType === "vertex") {
6175
+ if (wgsl) {
6176
+ return {
6177
+ CUSTOM_VERTEX_DEFINITIONS: this.getWGSLVertexDefinitions(),
6178
+ CUSTOM_VERTEX_MAIN_END: this.getWGSLVertexMainEnd()
6179
+ };
6180
+ }
6181
+ return {
6182
+ CUSTOM_VERTEX_DEFINITIONS: this.getGLSLVertexDefinitions(),
6183
+ CUSTOM_VERTEX_MAIN_END: this.getGLSLVertexMainEnd()
6184
+ };
6185
+ }
6186
+ if (shaderType === "fragment") {
6187
+ if (wgsl) {
6188
+ return {
6189
+ CUSTOM_FRAGMENT_DEFINITIONS: this.getWGSLFragmentDefinitions(),
6190
+ CUSTOM_FRAGMENT_UPDATE_ALBEDO: this.getWGSLAlbedoCode() + this.getWGSLMetalRoughSampleCode(),
6191
+ CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.getWGSLNormalCode(),
6192
+ CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS: this.getWGSLMetalRoughApplyCode(),
6193
+ CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION: this.getWGSLEmissiveCode()
6194
+ };
6195
+ }
6196
+ return {
6197
+ CUSTOM_FRAGMENT_DEFINITIONS: this.getGLSLFragmentDefinitions(),
6198
+ CUSTOM_FRAGMENT_UPDATE_ALBEDO: this.getGLSLAlbedoCode() + this.getGLSLMetalRoughSampleCode(),
6199
+ CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.getGLSLNormalCode(),
6200
+ CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS: this.getGLSLMetalRoughApplyCode(),
6201
+ CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION: this.getGLSLEmissiveCode()
6202
+ };
6203
+ }
6204
+ return null;
6205
+ }
6206
+ getGLSLVertexDefinitions() {
6207
+ return `
6208
+ #ifdef TOOLKIT_SKIN_ARRAY
6209
+ #ifndef TOOLKIT_SKIN_V_DEFINED
6210
+ #define TOOLKIT_SKIN_V_DEFINED
6211
+ #ifdef TOOLKIT_SKIN_LAYER_UNIFORM
6212
+ uniform float tkSkinLayerUni;
6213
+ #else
6214
+ attribute float tkSkinLayer;
6215
+ #endif
6216
+ varying float vSkinLayer;
6217
+ varying vec2 vSkinUV;
6218
+ #endif
6219
+ #endif
6220
+ `;
6221
+ }
6222
+ getGLSLVertexMainEnd() {
6223
+ return `
6224
+ #ifdef TOOLKIT_SKIN_ARRAY
6225
+ #ifdef UV1
6226
+ #ifdef TOOLKIT_SKIN_LAYER_UNIFORM
6227
+ vSkinLayer = tkSkinLayerUni;
6228
+ #else
6229
+ vSkinLayer = tkSkinLayer;
6230
+ #endif
6231
+ vSkinUV = uv;
6232
+ #else
6233
+ vSkinLayer = 0.0;
6234
+ vSkinUV = vec2(0.0);
6235
+ #endif
6236
+ #endif
6237
+ `;
6238
+ }
6239
+ getGLSLFragmentDefinitions() {
6240
+ return `
6241
+ #ifdef TOOLKIT_SKIN_ARRAY
6242
+ #ifndef TOOLKIT_SKIN_F_DEFINED
6243
+ #define TOOLKIT_SKIN_F_DEFINED
6244
+ varying float vSkinLayer;
6245
+ varying vec2 vSkinUV;
6246
+ #ifdef TOOLKIT_SKIN_ARRAY_ALBEDO
6247
+ uniform sampler2DArray tkAlbedoArray;
6248
+ #endif
6249
+ #ifdef TOOLKIT_SKIN_ARRAY_NORMAL
6250
+ uniform sampler2DArray tkNormalArray;
6251
+ #endif
6252
+ #ifdef TOOLKIT_SKIN_ARRAY_MR
6253
+ uniform sampler2DArray tkMetalRoughArray;
6254
+ vec2 _tkMR;
6255
+ #endif
6256
+ #ifdef TOOLKIT_SKIN_ARRAY_EMISSIVE
6257
+ uniform sampler2DArray tkEmissiveArray;
6258
+ #endif
6259
+ #endif
6260
+ #endif
6261
+ `;
6262
+ }
6263
+ getGLSLAlbedoCode() {
6264
+ return `
6265
+ #ifdef TOOLKIT_SKIN_ARRAY_ALBEDO
6266
+ {
6267
+ float _tkLayer = floor(vSkinLayer + 0.5);
6268
+ vec4 _tkAlb = texture(tkAlbedoArray, vec3(vSkinUV, _tkLayer));
6269
+ #ifdef GAMMAALBEDO
6270
+ surfaceAlbedo = toLinearSpace(_tkAlb.rgb) * vAlbedoColor.rgb;
6271
+ #else
6272
+ surfaceAlbedo = _tkAlb.rgb * vAlbedoColor.rgb;
6273
+ #endif
6274
+ #ifdef ALPHATEST
6275
+ if (_tkAlb.a < alphaCutOff) discard;
6276
+ #endif
6277
+ }
6278
+ #endif
6279
+ `;
6280
+ }
6281
+ getGLSLNormalCode() {
6282
+ return `
6283
+ #if defined(TOOLKIT_SKIN_ARRAY) && defined(TOOLKIT_SKIN_ARRAY_NORMAL) && defined(BUMP) && defined(TANGENT) && defined(NORMAL)
6284
+ {
6285
+ vec3 _tkNrmTexel = texture(tkNormalArray, vec3(vSkinUV, floor(vSkinLayer + 0.5))).xyz;
6286
+ normalW = perturbNormal(vTBN, _tkNrmTexel, vBumpInfos.y);
6287
+ }
6288
+ #endif
6289
+ `;
6290
+ }
6291
+ getGLSLMetalRoughSampleCode() {
6292
+ return `
6293
+ #ifdef TOOLKIT_SKIN_ARRAY_MR
6294
+ {
6295
+ vec4 _tkMRtexel = texture(tkMetalRoughArray, vec3(vSkinUV, floor(vSkinLayer + 0.5)));
6296
+ _tkMR = vec2(_tkMRtexel.b, _tkMRtexel.g);
6297
+ }
6298
+ #endif
6299
+ `;
6300
+ }
6301
+ getGLSLMetalRoughApplyCode() {
6302
+ return `
6303
+ #if defined(TOOLKIT_SKIN_ARRAY_MR) && defined(METALLICWORKFLOW)
6304
+ metallicRoughness.r = _tkMR.x;
6305
+ metallicRoughness.g = _tkMR.y;
6306
+ #endif
6307
+ `;
6308
+ }
6309
+ getGLSLEmissiveCode() {
6310
+ return `
6311
+ #if defined(TOOLKIT_SKIN_ARRAY) && defined(TOOLKIT_SKIN_ARRAY_EMISSIVE)
6312
+ {
6313
+ vec3 _tkEmis = texture(tkEmissiveArray, vec3(vSkinUV, floor(vSkinLayer + 0.5))).rgb;
6314
+ finalEmissive = toLinearSpace(_tkEmis);
6315
+ }
6316
+ #endif
6317
+ `;
6318
+ }
6319
+ getWGSLVertexDefinitions() {
6320
+ return `
6321
+ #ifdef TOOLKIT_SKIN_ARRAY
6322
+ #ifndef TOOLKIT_SKIN_LAYER_UNIFORM
6323
+ attribute tkSkinLayer: f32;
6324
+ #endif
6325
+ varying vSkinLayer: f32;
6326
+ varying vSkinUV: vec2<f32>;
6327
+ #endif
6328
+ `;
6329
+ }
6330
+ getWGSLVertexMainEnd() {
6331
+ return `
6332
+ #ifdef TOOLKIT_SKIN_ARRAY
6333
+ #ifdef UV1
6334
+ #ifdef TOOLKIT_SKIN_LAYER_UNIFORM
6335
+ vertexOutputs.vSkinLayer = uniforms.tkSkinLayerUni;
6336
+ #else
6337
+ vertexOutputs.vSkinLayer = vertexInputs.tkSkinLayer;
6338
+ #endif
6339
+ vertexOutputs.vSkinUV = vertexInputs.uv;
6340
+ #else
6341
+ vertexOutputs.vSkinLayer = 0.0;
6342
+ vertexOutputs.vSkinUV = vec2f(0.0);
6343
+ #endif
6344
+ #endif
6345
+ `;
6346
+ }
6347
+ getWGSLFragmentDefinitions() {
6348
+ return `
6349
+ #ifdef TOOLKIT_SKIN_ARRAY
6350
+ varying vSkinLayer: f32;
6351
+ varying vSkinUV: vec2<f32>;
6352
+ #ifdef TOOLKIT_SKIN_ARRAY_ALBEDO
6353
+ var tkAlbedoArray: texture_2d_array<f32>;
6354
+ var tkAlbedoArraySampler: sampler;
6355
+ #endif
6356
+ #ifdef TOOLKIT_SKIN_ARRAY_NORMAL
6357
+ var tkNormalArray: texture_2d_array<f32>;
6358
+ var tkNormalArraySampler: sampler;
6359
+ #endif
6360
+ #ifdef TOOLKIT_SKIN_ARRAY_MR
6361
+ var tkMetalRoughArray: texture_2d_array<f32>;
6362
+ var tkMetalRoughArraySampler: sampler;
6363
+ var<private> _tkMR: vec2<f32>;
6364
+ #endif
6365
+ #ifdef TOOLKIT_SKIN_ARRAY_EMISSIVE
6366
+ var tkEmissiveArray: texture_2d_array<f32>;
6367
+ var tkEmissiveArraySampler: sampler;
6368
+ #endif
6369
+ #endif
6370
+ `;
6371
+ }
6372
+ getWGSLAlbedoCode() {
6373
+ return `
6374
+ #ifdef TOOLKIT_SKIN_ARRAY_ALBEDO
6375
+ {
6376
+ let _tkLayer = i32(fragmentInputs.vSkinLayer + 0.5);
6377
+ let _tkAlb = textureSample(tkAlbedoArray, tkAlbedoArraySampler, fragmentInputs.vSkinUV, _tkLayer);
6378
+ #ifdef GAMMAALBEDO
6379
+ surfaceAlbedo = toLinearSpaceVec3(_tkAlb.rgb) * vAlbedoColor.rgb;
6380
+ #else
6381
+ surfaceAlbedo = _tkAlb.rgb * vAlbedoColor.rgb;
6382
+ #endif
6383
+ #ifdef ALPHATEST
6384
+ if (_tkAlb.a < alphaCutOff) { discard; }
6385
+ #endif
6386
+ }
6387
+ #endif
6388
+ `;
6389
+ }
6390
+ getWGSLNormalCode() {
6391
+ return `
6392
+ #if defined(TOOLKIT_SKIN_ARRAY) && defined(TOOLKIT_SKIN_ARRAY_NORMAL) && defined(BUMP) && defined(TANGENT) && defined(NORMAL)
6393
+ {
6394
+ let _tkNrmTexel = textureSample(tkNormalArray, tkNormalArraySampler, fragmentInputs.vSkinUV, i32(fragmentInputs.vSkinLayer + 0.5)).rgb;
6395
+ let _tkNrmTBN = mat3x3<f32>(fragmentInputs.vTBN0, fragmentInputs.vTBN1, fragmentInputs.vTBN2);
6396
+ normalW = perturbNormal(_tkNrmTBN, _tkNrmTexel, uniforms.vBumpInfos.y);
6397
+ }
6398
+ #endif
6399
+ `;
6400
+ }
6401
+ getWGSLMetalRoughSampleCode() {
6402
+ return `
6403
+ #ifdef TOOLKIT_SKIN_ARRAY_MR
6404
+ {
6405
+ let _tkMRtexel = textureSample(tkMetalRoughArray, tkMetalRoughArraySampler, fragmentInputs.vSkinUV, i32(fragmentInputs.vSkinLayer + 0.5));
6406
+ _tkMR = vec2<f32>(_tkMRtexel.b, _tkMRtexel.g);
6407
+ }
6408
+ #endif
6409
+ `;
6410
+ }
6411
+ getWGSLMetalRoughApplyCode() {
6412
+ return `
6413
+ #if defined(TOOLKIT_SKIN_ARRAY_MR) && defined(METALLICWORKFLOW)
6414
+ metallicRoughness.r = _tkMR.x;
6415
+ metallicRoughness.g = _tkMR.y;
6416
+ #endif
6417
+ `;
6418
+ }
6419
+ getWGSLEmissiveCode() {
6420
+ return `
6421
+ #if defined(TOOLKIT_SKIN_ARRAY) && defined(TOOLKIT_SKIN_ARRAY_EMISSIVE)
6422
+ {
6423
+ let _tkEmis = textureSample(tkEmissiveArray, tkEmissiveArraySampler, fragmentInputs.vSkinUV, i32(fragmentInputs.vSkinLayer + 0.5)).rgb;
6424
+ finalEmissive = toLinearSpaceVec3(_tkEmis);
6425
+ }
6426
+ #endif
6427
+ `;
6428
+ }
6429
+ }
6002
6430
  export class UniversalShaderMaterial extends CustomShaderMaterial {
6003
6431
  constructor(name, scene) {
6004
6432
  super(name, scene);
@@ -6525,13 +6953,12 @@ export class MetadataParser {
6525
6953
  if (babylonLight != null) {
6526
6954
  const lightRange = component.range != null ? component.range : Number.MAX_VALUE;
6527
6955
  const lightColor = component.color != null ? Utilities.ParseColor3(component.color, Color3.White()) : Color3.White();
6528
- const specularColor = component.specular != null ? Utilities.ParseColor3(component.specular, Color3.White()) : Color3.White();
6529
6956
  const lightmapMode = component.lightmapmode != null ? component.lightmapmode : 0;
6530
6957
  const lightmapType = component.lightmaptype != null ? component.lightmaptype : 0;
6531
6958
  babylonLight.parent = entity;
6532
6959
  babylonLight.range = lightRange;
6533
6960
  babylonLight.diffuse = lightColor;
6534
- babylonLight.specular = specularColor;
6961
+ babylonLight.specular = Color3.White();
6535
6962
  babylonLight.intensity = (component.intensity != null ? component.intensity : 1) * intensityFactor;
6536
6963
  switch (lightmapMode) {
6537
6964
  case 2:
@@ -11727,8 +12154,10 @@ export class VertexAnimationMaterial extends CustomShaderMaterial {
11727
12154
  constructor(name, scene) {
11728
12155
  super(name, scene);
11729
12156
  this.controller = null;
11730
- this.runtimeAlbedoSkins = null;
11731
- this.runtimeNormalSkins = null;
12157
+ this._vatSkinArray = false;
12158
+ this._vatSkinArrayNormal = false;
12159
+ this._vatSkinArrayMetalRough = false;
12160
+ this._vatSkinArrayEmissive = false;
11732
12161
  this.shader = this.getShaderName();
11733
12162
  this.addAttribute("g_vatAnim0");
11734
12163
  this.addAttribute("g_vatAnim1");
@@ -11784,97 +12213,90 @@ export class VertexAnimationMaterial extends CustomShaderMaterial {
11784
12213
  cloned.alpha = this.alpha;
11785
12214
  cloned.alphaMode = this.alphaMode;
11786
12215
  cloned.transparencyMode = this.transparencyMode;
11787
- cloned.runtimeAlbedoSkins = this.runtimeAlbedoSkins;
11788
- cloned.runtimeNormalSkins = this.runtimeNormalSkins;
11789
- cloned._notifyPluginOfRuntimeSkinChange();
12216
+ if (this._vatSkinArray) {
12217
+ const arr = this.getTextureValue("tkAlbedoArray");
12218
+ if (arr != null)
12219
+ cloned.addTextureArrayUniform("tkAlbedoArray", arr);
12220
+ cloned.enableVatSkinArray();
12221
+ }
12222
+ if (this._vatSkinArrayNormal) {
12223
+ const narr = this.getTextureValue("tkNormalArray");
12224
+ if (narr != null)
12225
+ cloned.addTextureArrayUniform("tkNormalArray", narr);
12226
+ cloned.enableVatSkinArrayNormal();
12227
+ }
12228
+ if (this._vatSkinArrayMetalRough) {
12229
+ const mrarr = this.getTextureValue("tkMetalRoughArray");
12230
+ if (mrarr != null)
12231
+ cloned.addTextureArrayUniform("tkMetalRoughArray", mrarr);
12232
+ cloned.enableVatSkinArrayMetalRough();
12233
+ }
12234
+ if (this._vatSkinArrayEmissive) {
12235
+ const earr = this.getTextureValue("tkEmissiveArray");
12236
+ if (earr != null)
12237
+ cloned.addTextureArrayUniform("tkEmissiveArray", earr);
12238
+ cloned.enableVatSkinArrayEmissive();
12239
+ }
11790
12240
  return cloned;
11791
12241
  }
11792
- setRuntimeSkinArrays(albedoArray, normalArray, nullOutDefaults = true) {
11793
- this.runtimeAlbedoSkins = albedoArray || null;
11794
- this.runtimeNormalSkins = normalArray || null;
11795
- if (nullOutDefaults) {
11796
- this.albedoTexture = null;
11797
- this.bumpTexture = null;
11798
- }
11799
- this._notifyPluginOfRuntimeSkinChange();
12242
+ enableVatSkinArray() {
12243
+ this._vatSkinArray = true;
12244
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12245
+ if (this.plugin)
12246
+ this.plugin.markAllDefinesAsDirty();
12247
+ return this;
11800
12248
  }
11801
- clearRuntimeSkinArrays() {
11802
- this.runtimeAlbedoSkins = null;
11803
- this.runtimeNormalSkins = null;
11804
- this._notifyPluginOfRuntimeSkinChange();
12249
+ disableVatSkinArray() {
12250
+ this._vatSkinArray = false;
12251
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12252
+ if (this.plugin)
12253
+ this.plugin.markAllDefinesAsDirty();
11805
12254
  }
11806
- async installSkinsFromUrlsAsync(layers, width, height, options) {
11807
- if (!layers || layers.length === 0) {
11808
- throw new Error("VertexAnimationMaterial.installSkinsFromUrlsAsync: layers list is empty.");
11809
- }
11810
- const opts = options || {};
11811
- const nullOutDefaults = opts.nullOutDefaults !== false;
11812
- const rootUrl = opts.rootUrl || "";
11813
- const generateMipMaps = opts.generateMipMaps !== false;
11814
- const samplingMode = (opts.samplingMode != null) ? opts.samplingMode : Texture.TRILINEAR_SAMPLINGMODE;
11815
- const scene = this.getScene();
11816
- const layerCount = layers.length;
11817
- const bytesPerLayer = width * height * 4;
11818
- const albedoData = new Uint8Array(bytesPerLayer * layerCount);
11819
- const normalData = new Uint8Array(bytesPerLayer * layerCount);
11820
- const resolveUrl = (rel) => {
11821
- if (!rel)
11822
- return "";
11823
- if (rel.indexOf("://") >= 0 || rel.indexOf("blob:") === 0 || rel.indexOf("data:") === 0 || rel.indexOf("/") === 0)
11824
- return rel;
11825
- if (rootUrl) {
11826
- const sep = rootUrl.charAt(rootUrl.length - 1) === "/" ? "" : "/";
11827
- return rootUrl + sep + rel;
11828
- }
11829
- return rel;
11830
- };
11831
- const loadAndCopy = (url, dst, layerIdx, label) => {
11832
- return new Promise((resolve, reject) => {
11833
- const fullUrl = resolveUrl(url);
11834
- const tex = new Texture(fullUrl, scene, true, true, samplingMode, () => {
11835
- const size = tex.getSize();
11836
- if (size.width !== width || size.height !== height) {
11837
- tex.dispose();
11838
- reject(new Error(`${label} '${fullUrl}' is ${size.width}x${size.height}, expected ${width}x${height}.`));
11839
- return;
11840
- }
11841
- const pixelsPromise = tex.readPixels();
11842
- const onPixels = (pixels) => {
11843
- if (!pixels) {
11844
- tex.dispose();
11845
- reject(new Error(`${label} '${fullUrl}': readPixels returned null.`));
11846
- return;
11847
- }
11848
- dst.set(new Uint8Array(pixels.buffer, pixels.byteOffset, pixels.byteLength), layerIdx * bytesPerLayer);
11849
- tex.dispose();
11850
- resolve();
11851
- };
11852
- if (pixelsPromise && typeof pixelsPromise.then === "function") {
11853
- pixelsPromise.then(onPixels).catch((e) => { tex.dispose(); reject(e); });
11854
- }
11855
- else {
11856
- onPixels(pixelsPromise);
11857
- }
11858
- }, (msg) => reject(new Error(`${label} '${fullUrl}' load failed: ${msg}`)));
11859
- });
11860
- };
11861
- const tasks = [];
11862
- for (let i = 0; i < layerCount; i++) {
11863
- tasks.push(loadAndCopy(layers[i].albedo, albedoData, i, "Skin albedo"));
11864
- tasks.push(loadAndCopy(layers[i].normal, normalData, i, "Skin normal"));
11865
- }
11866
- await Promise.all(tasks);
11867
- const albedoArray = RawTexture2DArray.CreateRGBATexture(albedoData, width, height, layerCount, scene, generateMipMaps, false, samplingMode);
11868
- const normalArray = RawTexture2DArray.CreateRGBATexture(normalData, width, height, layerCount, scene, generateMipMaps, false, samplingMode);
11869
- normalArray.gammaSpace = false;
11870
- this.setRuntimeSkinArrays(albedoArray, normalArray, nullOutDefaults);
11871
- }
11872
- _notifyPluginOfRuntimeSkinChange() {
11873
- const plug = this.plugin;
11874
- if (plug && typeof plug.onRuntimeSkinArraysChanged === "function") {
11875
- plug.onRuntimeSkinArraysChanged();
11876
- }
12255
+ enableVatSkinArrayNormal() {
12256
+ this._vatSkinArrayNormal = true;
12257
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12258
+ if (this.plugin)
12259
+ this.plugin.markAllDefinesAsDirty();
12260
+ return this;
12261
+ }
12262
+ disableVatSkinArrayNormal() {
12263
+ this._vatSkinArrayNormal = false;
11877
12264
  this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12265
+ if (this.plugin)
12266
+ this.plugin.markAllDefinesAsDirty();
12267
+ }
12268
+ enableVatSkinArrayMetalRough() {
12269
+ this._vatSkinArrayMetalRough = true;
12270
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12271
+ if (this.plugin)
12272
+ this.plugin.markAllDefinesAsDirty();
12273
+ return this;
12274
+ }
12275
+ disableVatSkinArrayMetalRough() {
12276
+ this._vatSkinArrayMetalRough = false;
12277
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12278
+ if (this.plugin)
12279
+ this.plugin.markAllDefinesAsDirty();
12280
+ }
12281
+ enableVatSkinArrayEmissive() {
12282
+ this._vatSkinArrayEmissive = true;
12283
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12284
+ if (this.plugin)
12285
+ this.plugin.markAllDefinesAsDirty();
12286
+ return this;
12287
+ }
12288
+ disableVatSkinArrayEmissive() {
12289
+ this._vatSkinArrayEmissive = false;
12290
+ this.markAsDirty(Constants.MATERIAL_AllDirtyFlag);
12291
+ if (this.plugin)
12292
+ this.plugin.markAllDefinesAsDirty();
12293
+ }
12294
+ get vatSkinArray() { return this._vatSkinArray; }
12295
+ get vatSkinArrayNormal() { return this._vatSkinArrayNormal; }
12296
+ get vatSkinArrayMetalRough() { return this._vatSkinArrayMetalRough; }
12297
+ get vatSkinArrayEmissive() { return this._vatSkinArrayEmissive; }
12298
+ get vatSkinArrayActive() {
12299
+ return this._vatSkinArray || this._vatSkinArrayNormal || this._vatSkinArrayMetalRough || this._vatSkinArrayEmissive;
11878
12300
  }
11879
12301
  }
11880
12302
  export class VertexAnimationController {
@@ -11909,15 +12331,15 @@ export class VertexAnimationController {
11909
12331
  }
11910
12332
  return result;
11911
12333
  }
11912
- static SetSkinIndex(mesh, skinIndex) {
12334
+ static SetAtlasCellIndex(mesh, cellIndex) {
11913
12335
  if (!mesh)
11914
12336
  return;
11915
- mesh.runtimeSkinIndex = Math.max(0, Math.floor(skinIndex || 0));
12337
+ mesh.atlasCellIndex = Math.max(0, Math.floor(cellIndex || 0));
11916
12338
  }
11917
- static GetSkinIndex(mesh) {
12339
+ static GetAtlasCellIndex(mesh) {
11918
12340
  if (!mesh)
11919
12341
  return 0;
11920
- const v = mesh.runtimeSkinIndex;
12342
+ const v = mesh.atlasCellIndex;
11921
12343
  return (typeof v === "number" && isFinite(v) && v >= 0) ? Math.floor(v) : 0;
11922
12344
  }
11923
12345
  static DisposeAll(scene) {
@@ -12001,39 +12423,6 @@ export class VertexAnimationController {
12001
12423
  }
12002
12424
  setSpeed(speed) { this._speed = speed; }
12003
12425
  setLoop(loop) { this._manualLoopOverride = loop; this._loop = loop; }
12004
- setRuntimeSkinArrays(albedoArray, normalArray, nullOutDefaults = true) {
12005
- for (let i = 0; i < this._plugins.length; i++) {
12006
- const plugin = this._plugins[i];
12007
- const mat = plugin.getCustomShaderMaterial();
12008
- if (mat)
12009
- mat.setRuntimeSkinArrays(albedoArray, normalArray, nullOutDefaults);
12010
- }
12011
- }
12012
- clearRuntimeSkinArrays() {
12013
- for (let i = 0; i < this._plugins.length; i++) {
12014
- const plugin = this._plugins[i];
12015
- const mat = plugin.getCustomShaderMaterial();
12016
- if (mat)
12017
- mat.clearRuntimeSkinArrays();
12018
- }
12019
- }
12020
- async installSkinsFromUrlsAsync(layers, width, height, options) {
12021
- if (this._plugins.length === 0) {
12022
- throw new Error("VertexAnimationController.installSkinsFromUrlsAsync: no registered VAT materials.");
12023
- }
12024
- const opts = options || {};
12025
- const effectiveOptions = { ...opts, rootUrl: opts.rootUrl || this.rootUrl };
12026
- const nullOutDefaults = opts.nullOutDefaults !== false;
12027
- const firstMat = this._plugins[0].getCustomShaderMaterial();
12028
- await firstMat.installSkinsFromUrlsAsync(layers, width, height, effectiveOptions);
12029
- const sharedAlbedo = firstMat.runtimeAlbedoSkins;
12030
- const sharedNormal = firstMat.runtimeNormalSkins;
12031
- for (let i = 1; i < this._plugins.length; i++) {
12032
- const mat = this._plugins[i].getCustomShaderMaterial();
12033
- if (mat)
12034
- mat.setRuntimeSkinArrays(sharedAlbedo, sharedNormal, nullOutDefaults);
12035
- }
12036
- }
12037
12426
  setCurrentTime(time) {
12038
12427
  if (!this._currentClip)
12039
12428
  return;
@@ -12243,9 +12632,9 @@ export class VertexAnimationController {
12243
12632
  const s = current.settings;
12244
12633
  anim0.set(this._time * s.vertexfps, s.vertexframes, current.fromRow, this._blendWeight);
12245
12634
  const loopFlag = this._loopBlend ? 1.0 : 0.0;
12246
- const meshSkinRaw = imesh.runtimeSkinIndex;
12247
- const meshSkin = (typeof meshSkinRaw === "number" && meshSkinRaw >= 0) ? meshSkinRaw : 0;
12248
- anim1.w = meshSkin * 2.0 + loopFlag;
12635
+ const cellRaw = imesh.atlasCellIndex;
12636
+ const cell = (typeof cellRaw === "number" && cellRaw >= 0) ? Math.floor(cellRaw) : 0;
12637
+ anim1.w = cell * 2.0 + loopFlag;
12249
12638
  const prev = this.getPreviousRendererClip(rendererGuid);
12250
12639
  if (prev && this._previousClip && this._blendWeight < 0.999 && this._isRendererPositionReady(prev)) {
12251
12640
  const ps = prev.settings;
@@ -12415,17 +12804,17 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12415
12804
  super(customMaterial, shaderName, 100, {
12416
12805
  VERTEXANIMATIONMATERIAL: false,
12417
12806
  VAT_NORMAL_SEPARATE: false,
12418
- RUNTIME_SKINS_ALBEDO: false,
12419
- RUNTIME_SKINS_NORMAL: false,
12420
- RUNTIME_SKINS_NORMAL_DERIVE_TBN: false
12807
+ VAT_SKIN_ARRAY: false,
12808
+ VAT_SKIN_ARRAY_ALBEDO: false,
12809
+ VAT_SKIN_ARRAY_NORMAL: false,
12810
+ VAT_SKIN_ARRAY_MR: false,
12811
+ VAT_SKIN_ARRAY_EMISSIVE: false
12421
12812
  });
12422
12813
  this.vertexAnimations = null;
12423
12814
  this.controller = null;
12424
12815
  this._rendererTargets = [];
12425
12816
  this._targetRendererGuid = "";
12426
12817
  this._placeholderTexture = null;
12427
- this._placeholderAlbedoArray = null;
12428
- this._placeholderNormalArray = null;
12429
12818
  this._cc_posTex = null;
12430
12819
  this._cc_normTex = null;
12431
12820
  this._cc_prevPosTex = null;
@@ -12447,24 +12836,12 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12447
12836
  customMaterial.setTextureValue("vatNormal", this._placeholderTexture);
12448
12837
  customMaterial.setTextureValue("vatPrevPosition", this._placeholderTexture);
12449
12838
  customMaterial.setTextureValue("vatPrevNormal", this._placeholderTexture);
12450
- const greyArr = new Uint8Array([128, 128, 128, 255]);
12451
- const flatNormalArr = new Uint8Array([128, 128, 255, 255]);
12452
- this._placeholderAlbedoArray = RawTexture2DArray.CreateRGBATexture(greyArr, 1, 1, 1, scene, false, false, Texture.NEAREST_SAMPLINGMODE);
12453
- this._placeholderNormalArray = RawTexture2DArray.CreateRGBATexture(flatNormalArr, 1, 1, 1, scene, false, false, Texture.NEAREST_SAMPLINGMODE);
12454
12839
  }
12455
12840
  dispose() {
12456
12841
  if (this._placeholderTexture) {
12457
12842
  this._placeholderTexture.dispose();
12458
12843
  this._placeholderTexture = null;
12459
12844
  }
12460
- if (this._placeholderAlbedoArray) {
12461
- this._placeholderAlbedoArray.dispose();
12462
- this._placeholderAlbedoArray = null;
12463
- }
12464
- if (this._placeholderNormalArray) {
12465
- this._placeholderNormalArray.dispose();
12466
- this._placeholderNormalArray = null;
12467
- }
12468
12845
  super.dispose();
12469
12846
  }
12470
12847
  isCompatible(shaderLanguage) {
@@ -12478,28 +12855,32 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12478
12855
  CUSTOM_VERTEX_DEFINITIONS: this.getWGSLVertexDefinitions(),
12479
12856
  CUSTOM_VERTEX_UPDATE_POSITION: this.getWGSLVertexPositionCode(),
12480
12857
  CUSTOM_VERTEX_UPDATE_NORMAL: this.getWGSLVertexNormalCode(),
12481
- CUSTOM_VERTEX_MAIN_END: this.getWGSLVertexMainEnd()
12858
+ CUSTOM_VERTEX_MAIN_END: this.getWGSLSkinLayerVaryingCode()
12482
12859
  };
12483
12860
  }
12484
12861
  return {
12485
12862
  CUSTOM_VERTEX_DEFINITIONS: this.getGLSLVertexDefinitions(),
12486
12863
  CUSTOM_VERTEX_UPDATE_POSITION: this.getGLSLVertexPositionCode(),
12487
12864
  CUSTOM_VERTEX_UPDATE_NORMAL: this.getGLSLVertexNormalCode(),
12488
- CUSTOM_VERTEX_MAIN_END: this.getGLSLVertexMainEnd()
12865
+ CUSTOM_VERTEX_MAIN_END: this.getGLSLSkinLayerVaryingCode()
12489
12866
  };
12490
12867
  }
12491
12868
  if (shaderType === "fragment") {
12492
12869
  if (wgsl) {
12493
12870
  return {
12494
- CUSTOM_FRAGMENT_DEFINITIONS: this.getWGSLFragmentDefinitions(),
12495
- CUSTOM_FRAGMENT_UPDATE_ALBEDO: this.getWGSLFragmentAlbedoCode(),
12496
- CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.getWGSLFragmentNormalCode()
12871
+ CUSTOM_FRAGMENT_DEFINITIONS: this.getWGSLSkinFragmentDefs(),
12872
+ CUSTOM_FRAGMENT_UPDATE_ALBEDO: this.getWGSLSkinAlbedoCode() + this.getWGSLSkinMetalRoughSampleCode(),
12873
+ CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.getWGSLSkinNormalCode(),
12874
+ CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS: this.getWGSLSkinMetalRoughApplyCode(),
12875
+ CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION: this.getWGSLSkinEmissiveCode()
12497
12876
  };
12498
12877
  }
12499
12878
  return {
12500
- CUSTOM_FRAGMENT_DEFINITIONS: this.getGLSLFragmentDefinitions(),
12501
- CUSTOM_FRAGMENT_UPDATE_ALBEDO: this.getGLSLFragmentAlbedoCode(),
12502
- CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.getGLSLFragmentNormalCode()
12879
+ CUSTOM_FRAGMENT_DEFINITIONS: this.getGLSLSkinFragmentDefs(),
12880
+ CUSTOM_FRAGMENT_UPDATE_ALBEDO: this.getGLSLSkinAlbedoCode() + this.getGLSLSkinMetalRoughSampleCode(),
12881
+ CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.getGLSLSkinNormalCode(),
12882
+ CUSTOM_FRAGMENT_UPDATE_METALLICROUGHNESS: this.getGLSLSkinMetalRoughApplyCode(),
12883
+ CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION: this.getGLSLSkinEmissiveCode()
12503
12884
  };
12504
12885
  }
12505
12886
  return null;
@@ -12514,13 +12895,6 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12514
12895
  const customSamplers = this.getCustomShaderMaterial().getCustomSamplers();
12515
12896
  if (customSamplers != null && customSamplers.length > 0)
12516
12897
  samplers.push(...customSamplers);
12517
- const mat = this.getCustomShaderMaterial();
12518
- if (mat) {
12519
- if (samplers.indexOf("runtimeAlbedoSkins") === -1)
12520
- samplers.push("runtimeAlbedoSkins");
12521
- if (samplers.indexOf("runtimeNormalSkins") === -1)
12522
- samplers.push("runtimeNormalSkins");
12523
- }
12524
12898
  }
12525
12899
  getAttributes(attributes, scene, mesh) {
12526
12900
  const customAttributes = this.getCustomShaderMaterial().getCustomAttributes();
@@ -12536,15 +12910,12 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12536
12910
  defines.VAT_NORMAL_SEPARATE = (mode === "separate");
12537
12911
  const engine = scene ? scene.getEngine() : null;
12538
12912
  defines.UV3 = !(engine && engine.isWebGPU);
12539
- const mat = this.getCustomShaderMaterial();
12540
- const hasAlbedoArr = !!(mat && mat.runtimeAlbedoSkins);
12541
- const hasNormalArr = !!(mat && mat.runtimeNormalSkins);
12542
- defines.RUNTIME_SKINS_ALBEDO = hasAlbedoArr;
12543
- defines.RUNTIME_SKINS_NORMAL = hasNormalArr;
12544
- defines.RUNTIME_SKINS_NORMAL_DERIVE_TBN = hasNormalArr && !(mat && mat.bumpTexture);
12545
- }
12546
- onRuntimeSkinArraysChanged() {
12547
- this.markAllDefinesAsDirty();
12913
+ const matSkin = this.getCustomShaderMaterial();
12914
+ defines.VAT_SKIN_ARRAY = !!(matSkin && matSkin.vatSkinArrayActive);
12915
+ defines.VAT_SKIN_ARRAY_ALBEDO = !!(matSkin && matSkin.vatSkinArray);
12916
+ defines.VAT_SKIN_ARRAY_NORMAL = !!(matSkin && matSkin.vatSkinArrayNormal);
12917
+ defines.VAT_SKIN_ARRAY_MR = !!(matSkin && matSkin.vatSkinArrayMetalRough);
12918
+ defines.VAT_SKIN_ARRAY_EMISSIVE = !!(matSkin && matSkin.vatSkinArrayEmissive);
12548
12919
  }
12549
12920
  bindForSubMesh(uniformBuffer, scene, engine, subMesh) {
12550
12921
  if (!this.getIsEnabled())
@@ -12552,16 +12923,6 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12552
12923
  const renderMesh = subMesh && subMesh.getRenderingMesh ? subMesh.getRenderingMesh() : null;
12553
12924
  this._captureRendererTarget(renderMesh || (subMesh ? subMesh.getMesh() : null));
12554
12925
  this._pushVATUniforms(uniformBuffer, false);
12555
- this._pushRuntimeSkinSamplers(uniformBuffer);
12556
- }
12557
- _pushRuntimeSkinSamplers(buf) {
12558
- const mat = this.getCustomShaderMaterial();
12559
- const albedoArr = (mat && mat.runtimeAlbedoSkins && mat.runtimeAlbedoSkins.isReady && mat.runtimeAlbedoSkins.isReady())
12560
- ? mat.runtimeAlbedoSkins : this._placeholderAlbedoArray;
12561
- const normalArr = (mat && mat.runtimeNormalSkins && mat.runtimeNormalSkins.isReady && mat.runtimeNormalSkins.isReady())
12562
- ? mat.runtimeNormalSkins : this._placeholderNormalArray;
12563
- buf.setTexture("runtimeAlbedoSkins", albedoArr);
12564
- buf.setTexture("runtimeNormalSkins", normalArr);
12565
12926
  }
12566
12927
  setupAnimations(settings, controllerId, rootUrl = "", lazyLoadTextures = false) {
12567
12928
  this.vertexAnimations = settings;
@@ -12814,7 +13175,6 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12814
13175
  }
12815
13176
  bindVATToEffect(effect) {
12816
13177
  this._pushVATUniforms(effect, true);
12817
- this._pushRuntimeSkinSamplers(effect);
12818
13178
  }
12819
13179
  _pushVATUniforms(buf, isEffect) {
12820
13180
  const fb = this._placeholderTexture;
@@ -12887,13 +13247,12 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12887
13247
  uniform sampler2D vatPrevPosition;
12888
13248
  uniform sampler2D vatPrevNormal;
12889
13249
  #endif
12890
- // Runtime-skin varyings — pass packed skin index + UV0 to the fragment stage so
12891
- // fragment can sample runtimeAlbedoSkins[layer]/runtimeNormalSkins[layer]. Always
12892
- // emitted (cheap) so the shader compiles regardless of the runtime-skin defines.
12893
- #ifndef TOOLKIT_VAT_RUNTIMESKIN_VARYINGS
12894
- #define TOOLKIT_VAT_RUNTIMESKIN_VARYINGS
12895
- varying float vRuntimeSkinIndex;
12896
- varying vec2 vRuntimeSkinUV;
13250
+ #ifdef VAT_SKIN_ARRAY
13251
+ #ifndef TOOLKIT_VAT_SKIN_V_DEFINED
13252
+ #define TOOLKIT_VAT_SKIN_V_DEFINED
13253
+ varying float vVatSkinLayer;
13254
+ varying vec2 vVatSkinUV;
13255
+ #endif
12897
13256
  #endif
12898
13257
  `;
12899
13258
  }
@@ -12907,8 +13266,11 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12907
13266
  float vatPrevFrame = g_vatAnim1.x;
12908
13267
  float vatPrevFrameCount = g_vatAnim1.y;
12909
13268
  float vatPrevFromRow = g_vatAnim1.z;
12910
- float vatPackedSkinLoop = g_vatAnim1.w;
12911
- float vatLoopBlend = mod(vatPackedSkinLoop, 2.0);
13269
+ #ifdef VAT_SKIN_ARRAY
13270
+ float vatLoopBlend = mod(g_vatAnim1.w, 2.0); // low bit is loop-blend, high bits carry the skin layer index
13271
+ #else
13272
+ float vatLoopBlend = g_vatAnim1.w;
13273
+ #endif
12912
13274
  if (vatFrameCount > 0.5) {
12913
13275
  // GLSL path uses the third UV channel:
12914
13276
  // Unity uv3 -> glTF TEXCOORD_2 -> Babylon shader uv3
@@ -12976,9 +13338,11 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
12976
13338
  float vatNormFrame = g_vatAnim0.x;
12977
13339
  float vatNormFrameCount = g_vatAnim0.y;
12978
13340
  float vatNormFromRow = g_vatAnim0.z;
12979
-
12980
- float vatNormPackedSkinLoop = g_vatAnim1.w;
12981
- float vatNormLoopBlend = mod(vatNormPackedSkinLoop, 2.0);
13341
+ #ifdef VAT_SKIN_ARRAY
13342
+ float vatNormLoopBlend = mod(g_vatAnim1.w, 2.0);
13343
+ #else
13344
+ float vatNormLoopBlend = g_vatAnim1.w;
13345
+ #endif
12982
13346
 
12983
13347
  if (vatNormFrameCount > 0.5) {
12984
13348
  // GLSL path uses the third UV channel:
@@ -13024,9 +13388,10 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
13024
13388
  var vatPrevPositionSampler: sampler;
13025
13389
  var vatPrevNormal: texture_2d<f32>;
13026
13390
  var vatPrevNormalSampler: sampler;
13027
- // Runtime-skin varyings — packed skin index and UV0, passed to fragment.
13028
- varying vRuntimeSkinIndex: f32;
13029
- varying vRuntimeSkinUV: vec2<f32>;
13391
+ #endif
13392
+ #ifdef VAT_SKIN_ARRAY
13393
+ varying vVatSkinLayer: f32;
13394
+ varying vVatSkinUV: vec2<f32>;
13030
13395
  #endif
13031
13396
  `;
13032
13397
  }
@@ -13040,8 +13405,11 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
13040
13405
  let vatPrevFrame = vertexInputs.g_vatAnim1.x;
13041
13406
  let vatPrevFrameCount = vertexInputs.g_vatAnim1.y;
13042
13407
  let vatPrevFromRow = vertexInputs.g_vatAnim1.z;
13043
- let packedSkinLoop = vertexInputs.g_vatAnim1.w;
13044
- let vatLoopBlend = packedSkinLoop % 2.0;
13408
+ #ifdef VAT_SKIN_ARRAY
13409
+ let vatLoopBlend = vertexInputs.g_vatAnim1.w % 2.0; // low bit is loop-blend, high bits carry the skin layer index
13410
+ #else
13411
+ let vatLoopBlend = vertexInputs.g_vatAnim1.w;
13412
+ #endif
13045
13413
  if (vatFrameCount > 0.5) {
13046
13414
  // WGSL path derives the VAT texel coordinate from the vertex index instead
13047
13415
  // of consuming a dedicated uv3 vertex-buffer slot.
@@ -13110,8 +13478,11 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
13110
13478
  let vatNormFrame = vertexInputs.g_vatAnim0.x;
13111
13479
  let vatNormFrameCount = vertexInputs.g_vatAnim0.y;
13112
13480
  let vatNormFromRow = vertexInputs.g_vatAnim0.z;
13113
- let vatNormPackedSkinLoop = vertexInputs.g_vatAnim1.w;
13114
- let vatNormLoopBlend = vatNormPackedSkinLoop % 2.0;
13481
+ #ifdef VAT_SKIN_ARRAY
13482
+ let vatNormLoopBlend = vertexInputs.g_vatAnim1.w % 2.0;
13483
+ #else
13484
+ let vatNormLoopBlend = vertexInputs.g_vatAnim1.w;
13485
+ #endif
13115
13486
  if (vatNormFrameCount > 0.5) {
13116
13487
  // WGSL path derives the VAT texel coordinate from the vertex index instead
13117
13488
  // of consuming a dedicated uv3 vertex-buffer slot.
@@ -13148,130 +13519,187 @@ export class VertexAnimationMaterialPlugin extends CustomShaderMaterialPlugin {
13148
13519
  #endif
13149
13520
  `;
13150
13521
  }
13151
- getGLSLVertexMainEnd() {
13522
+ getGLSLSkinLayerVaryingCode() {
13152
13523
  return `
13153
- #ifdef VERTEXANIMATIONMATERIAL
13154
- {
13155
- float _rsPackedSkinLoop = g_vatAnim1.w;
13156
- vRuntimeSkinIndex = floor(_rsPackedSkinLoop * 0.5 + 0.0001);
13157
- #ifdef UV1
13158
- vRuntimeSkinUV = uv;
13159
- #else
13160
- vRuntimeSkinUV = vec2(0.0, 0.0);
13161
- #endif
13162
- }
13524
+ #ifdef VAT_SKIN_ARRAY
13525
+ #ifdef UV1
13526
+ vVatSkinLayer = floor(g_vatAnim1.w * 0.5 + 0.0001);
13527
+ vVatSkinUV = uv;
13528
+ #else
13529
+ vVatSkinLayer = 0.0;
13530
+ vVatSkinUV = vec2(0.0);
13531
+ #endif
13163
13532
  #endif
13164
13533
  `;
13165
13534
  }
13166
- getWGSLVertexMainEnd() {
13535
+ getGLSLSkinFragmentDefs() {
13167
13536
  return `
13168
- #ifdef VERTEXANIMATIONMATERIAL
13537
+ #ifdef VAT_SKIN_ARRAY
13538
+ varying float vVatSkinLayer;
13539
+ varying vec2 vVatSkinUV;
13540
+ #ifdef VAT_SKIN_ARRAY_ALBEDO
13541
+ uniform sampler2DArray tkAlbedoArray;
13542
+ #endif
13543
+ #ifdef VAT_SKIN_ARRAY_NORMAL
13544
+ uniform sampler2DArray tkNormalArray;
13545
+ #endif
13546
+ #ifdef VAT_SKIN_ARRAY_MR
13547
+ uniform sampler2DArray tkMetalRoughArray;
13548
+ vec2 _tkMR;
13549
+ #endif
13550
+ #ifdef VAT_SKIN_ARRAY_EMISSIVE
13551
+ uniform sampler2DArray tkEmissiveArray;
13552
+ #endif
13553
+ #endif
13554
+ `;
13555
+ }
13556
+ getGLSLSkinAlbedoCode() {
13557
+ return `
13558
+ #ifdef VAT_SKIN_ARRAY_ALBEDO
13169
13559
  {
13170
- let _rsPackedSkinLoop = vertexInputs.g_vatAnim1.w;
13171
- vertexOutputs.vRuntimeSkinIndex = floor(_rsPackedSkinLoop * 0.5 + 0.0001);
13172
- #ifdef UV1
13173
- vertexOutputs.vRuntimeSkinUV = vertexInputs.uv;
13560
+ vec4 _vatSkinAlbedo = texture(tkAlbedoArray, vec3(vVatSkinUV, floor(vVatSkinLayer + 0.5)));
13561
+ // Match Babylon's native albedo: sRGB -> linear when the albedo is gamma-space.
13562
+ #ifdef GAMMAALBEDO
13563
+ surfaceAlbedo = toLinearSpace(_vatSkinAlbedo.rgb) * vAlbedoColor.rgb;
13174
13564
  #else
13175
- vertexOutputs.vRuntimeSkinUV = vec2f(0.0, 0.0);
13565
+ surfaceAlbedo = _vatSkinAlbedo.rgb * vAlbedoColor.rgb;
13566
+ #endif
13567
+ #ifdef ALPHATEST
13568
+ if (_vatSkinAlbedo.a < alphaCutOff) discard;
13176
13569
  #endif
13177
13570
  }
13178
13571
  #endif
13179
13572
  `;
13180
13573
  }
13181
- getGLSLFragmentDefinitions() {
13574
+ getGLSLSkinNormalCode() {
13182
13575
  return `
13183
- #ifndef TOOLKIT_VAT_RUNTIMESKIN_F_UNIFORMS
13184
- #define TOOLKIT_VAT_RUNTIMESKIN_F_UNIFORMS
13185
- varying float vRuntimeSkinIndex;
13186
- varying vec2 vRuntimeSkinUV;
13187
- uniform highp sampler2DArray runtimeAlbedoSkins;
13188
- uniform highp sampler2DArray runtimeNormalSkins;
13576
+ #if defined(VAT_SKIN_ARRAY) && defined(VAT_SKIN_ARRAY_NORMAL) && defined(BUMP) && defined(TANGENT) && defined(NORMAL)
13577
+ {
13578
+ vec3 _vatNrmTexel = texture(tkNormalArray, vec3(vVatSkinUV, floor(vVatSkinLayer + 0.5))).xyz;
13579
+ normalW = perturbNormal(vTBN, _vatNrmTexel, vBumpInfos.y);
13580
+ }
13189
13581
  #endif
13190
13582
  `;
13191
13583
  }
13192
- getGLSLFragmentAlbedoCode() {
13584
+ getGLSLSkinMetalRoughSampleCode() {
13193
13585
  return `
13194
- #ifdef RUNTIME_SKINS_ALBEDO
13586
+ #ifdef VAT_SKIN_ARRAY_MR
13195
13587
  {
13196
- float _rsLayer = max(0.0, floor(vRuntimeSkinIndex + 0.5));
13197
- vec4 _rsAlbedo = texture(runtimeAlbedoSkins, vec3(vRuntimeSkinUV, _rsLayer));
13198
- surfaceAlbedo = _rsAlbedo.rgb * vAlbedoColor.rgb;
13199
- #ifdef ALPHATEST
13200
- if (_rsAlbedo.a < alphaCutOff) discard;
13201
- #endif
13588
+ vec4 _vatMRtexel = texture(tkMetalRoughArray, vec3(vVatSkinUV, floor(vVatSkinLayer + 0.5)));
13589
+ _tkMR = vec2(_vatMRtexel.b, _vatMRtexel.g);
13202
13590
  }
13203
13591
  #endif
13204
13592
  `;
13205
13593
  }
13206
- getGLSLFragmentNormalCode() {
13594
+ getGLSLSkinMetalRoughApplyCode() {
13595
+ return `
13596
+ #if defined(VAT_SKIN_ARRAY_MR) && defined(METALLICWORKFLOW)
13597
+ metallicRoughness.r = _tkMR.x;
13598
+ metallicRoughness.g = _tkMR.y;
13599
+ #endif
13600
+ `;
13601
+ }
13602
+ getGLSLSkinEmissiveCode() {
13207
13603
  return `
13208
- #ifdef RUNTIME_SKINS_NORMAL
13604
+ #if defined(VAT_SKIN_ARRAY) && defined(VAT_SKIN_ARRAY_EMISSIVE)
13209
13605
  {
13210
- float _rsLayer = max(0.0, floor(vRuntimeSkinIndex + 0.5));
13211
- vec3 _rsNormalSample = texture(runtimeNormalSkins, vec3(vRuntimeSkinUV, _rsLayer)).xyz;
13212
- vec3 _rsNormalTS = _rsNormalSample * 2.0 - 1.0;
13213
- vec3 _rsP = vPositionW;
13214
- vec3 _rsN = normalize(vNormalW);
13215
- vec3 _rsDp1 = dFdx(_rsP);
13216
- vec3 _rsDp2 = dFdy(_rsP);
13217
- vec2 _rsDuv1 = dFdx(vRuntimeSkinUV);
13218
- vec2 _rsDuv2 = dFdy(vRuntimeSkinUV);
13219
- vec3 _rsDp2perp = cross(_rsDp2, _rsN);
13220
- vec3 _rsDp1perp = cross(_rsN, _rsDp1);
13221
- vec3 _rsT = _rsDp2perp * _rsDuv1.x + _rsDp1perp * _rsDuv2.x;
13222
- vec3 _rsB = _rsDp2perp * _rsDuv1.y + _rsDp1perp * _rsDuv2.y;
13223
- float _rsInvMax = inversesqrt(max(dot(_rsT, _rsT), dot(_rsB, _rsB)));
13224
- mat3 _rsTBN = mat3(_rsT * _rsInvMax, _rsB * _rsInvMax, _rsN);
13225
- normalW = normalize(_rsTBN * _rsNormalTS);
13606
+ vec3 _vatEmis = texture(tkEmissiveArray, vec3(vVatSkinUV, floor(vVatSkinLayer + 0.5))).rgb;
13607
+ finalEmissive = toLinearSpace(_vatEmis);
13226
13608
  }
13227
13609
  #endif
13228
13610
  `;
13229
13611
  }
13230
- getWGSLFragmentDefinitions() {
13612
+ getWGSLSkinLayerVaryingCode() {
13231
13613
  return `
13232
- varying vRuntimeSkinIndex: f32;
13233
- varying vRuntimeSkinUV: vec2<f32>;
13234
- var runtimeAlbedoSkins: texture_2d_array<f32>;
13235
- var runtimeAlbedoSkinsSampler: sampler;
13236
- var runtimeNormalSkins: texture_2d_array<f32>;
13237
- var runtimeNormalSkinsSampler: sampler;
13614
+ #ifdef VAT_SKIN_ARRAY
13615
+ #ifdef UV1
13616
+ vertexOutputs.vVatSkinLayer = floor(vertexInputs.g_vatAnim1.w * 0.5 + 0.0001);
13617
+ vertexOutputs.vVatSkinUV = vertexInputs.uv;
13618
+ #else
13619
+ vertexOutputs.vVatSkinLayer = 0.0;
13620
+ vertexOutputs.vVatSkinUV = vec2f(0.0);
13621
+ #endif
13622
+ #endif
13238
13623
  `;
13239
13624
  }
13240
- getWGSLFragmentAlbedoCode() {
13625
+ getWGSLSkinFragmentDefs() {
13241
13626
  return `
13242
- #ifdef RUNTIME_SKINS_ALBEDO
13627
+ #ifdef VAT_SKIN_ARRAY
13628
+ varying vVatSkinLayer: f32;
13629
+ varying vVatSkinUV: vec2<f32>;
13630
+ #ifdef VAT_SKIN_ARRAY_ALBEDO
13631
+ var tkAlbedoArray: texture_2d_array<f32>;
13632
+ var tkAlbedoArraySampler: sampler;
13633
+ #endif
13634
+ #ifdef VAT_SKIN_ARRAY_NORMAL
13635
+ var tkNormalArray: texture_2d_array<f32>;
13636
+ var tkNormalArraySampler: sampler;
13637
+ #endif
13638
+ #ifdef VAT_SKIN_ARRAY_MR
13639
+ var tkMetalRoughArray: texture_2d_array<f32>;
13640
+ var tkMetalRoughArraySampler: sampler;
13641
+ var<private> _tkMR: vec2<f32>;
13642
+ #endif
13643
+ #ifdef VAT_SKIN_ARRAY_EMISSIVE
13644
+ var tkEmissiveArray: texture_2d_array<f32>;
13645
+ var tkEmissiveArraySampler: sampler;
13646
+ #endif
13647
+ #endif
13648
+ `;
13649
+ }
13650
+ getWGSLSkinAlbedoCode() {
13651
+ return `
13652
+ #ifdef VAT_SKIN_ARRAY_ALBEDO
13243
13653
  {
13244
- let _rsLayer = i32(max(0.0, floor(fragmentInputs.vRuntimeSkinIndex + 0.5)));
13245
- let _rsAlbedo = textureSample(runtimeAlbedoSkins, runtimeAlbedoSkinsSampler, fragmentInputs.vRuntimeSkinUV, _rsLayer);
13246
- surfaceAlbedo = _rsAlbedo.rgb * vAlbedoColor.rgb;
13654
+ let _vatSkinAlbedo = textureSample(tkAlbedoArray, tkAlbedoArraySampler, fragmentInputs.vVatSkinUV, i32(fragmentInputs.vVatSkinLayer + 0.5));
13655
+ // Match Babylon's native albedo: sRGB -> linear when the albedo is gamma-space.
13656
+ #ifdef GAMMAALBEDO
13657
+ surfaceAlbedo = toLinearSpaceVec3(_vatSkinAlbedo.rgb) * vAlbedoColor.rgb;
13658
+ #else
13659
+ surfaceAlbedo = _vatSkinAlbedo.rgb * vAlbedoColor.rgb;
13660
+ #endif
13247
13661
  #ifdef ALPHATEST
13248
- if (_rsAlbedo.a < alphaCutOff) { discard; }
13662
+ if (_vatSkinAlbedo.a < alphaCutOff) { discard; }
13249
13663
  #endif
13250
13664
  }
13251
13665
  #endif
13252
13666
  `;
13253
13667
  }
13254
- getWGSLFragmentNormalCode() {
13668
+ getWGSLSkinNormalCode() {
13669
+ return `
13670
+ #if defined(VAT_SKIN_ARRAY) && defined(VAT_SKIN_ARRAY_NORMAL) && defined(BUMP) && defined(TANGENT) && defined(NORMAL)
13671
+ {
13672
+ let _vatNrmTexel = textureSample(tkNormalArray, tkNormalArraySampler, fragmentInputs.vVatSkinUV, i32(fragmentInputs.vVatSkinLayer + 0.5)).rgb;
13673
+ let _vatNrmTBN = mat3x3<f32>(fragmentInputs.vTBN0, fragmentInputs.vTBN1, fragmentInputs.vTBN2);
13674
+ normalW = perturbNormal(_vatNrmTBN, _vatNrmTexel, uniforms.vBumpInfos.y);
13675
+ }
13676
+ #endif
13677
+ `;
13678
+ }
13679
+ getWGSLSkinMetalRoughSampleCode() {
13255
13680
  return `
13256
- #ifdef RUNTIME_SKINS_NORMAL
13681
+ #ifdef VAT_SKIN_ARRAY_MR
13257
13682
  {
13258
- let _rsLayer = i32(max(0.0, floor(fragmentInputs.vRuntimeSkinIndex + 0.5)));
13259
- let _rsNormalSample = textureSample(runtimeNormalSkins, runtimeNormalSkinsSampler, fragmentInputs.vRuntimeSkinUV, _rsLayer).xyz;
13260
- let _rsNormalTS = _rsNormalSample * 2.0 - vec3f(1.0);
13261
- let _rsP = fragmentInputs.vPositionW;
13262
- let _rsN = normalize(fragmentInputs.vNormalW);
13263
- let _rsDp1 = dpdx(_rsP);
13264
- let _rsDp2 = dpdy(_rsP);
13265
- let _rsDuv1 = dpdx(fragmentInputs.vRuntimeSkinUV);
13266
- let _rsDuv2 = dpdy(fragmentInputs.vRuntimeSkinUV);
13267
- let _rsDp2perp = cross(_rsDp2, _rsN);
13268
- let _rsDp1perp = cross(_rsN, _rsDp1);
13269
- let _rsT = _rsDp2perp * _rsDuv1.x + _rsDp1perp * _rsDuv2.x;
13270
- let _rsB = _rsDp2perp * _rsDuv1.y + _rsDp1perp * _rsDuv2.y;
13271
- let _rsInvMax = inverseSqrt(max(dot(_rsT, _rsT), dot(_rsB, _rsB)));
13272
- let _rsTcol = _rsT * _rsInvMax;
13273
- let _rsBcol = _rsB * _rsInvMax;
13274
- normalW = normalize(_rsTcol * _rsNormalTS.x + _rsBcol * _rsNormalTS.y + _rsN * _rsNormalTS.z);
13683
+ let _vatMRtexel = textureSample(tkMetalRoughArray, tkMetalRoughArraySampler, fragmentInputs.vVatSkinUV, i32(fragmentInputs.vVatSkinLayer + 0.5));
13684
+ _tkMR = vec2<f32>(_vatMRtexel.b, _vatMRtexel.g);
13685
+ }
13686
+ #endif
13687
+ `;
13688
+ }
13689
+ getWGSLSkinMetalRoughApplyCode() {
13690
+ return `
13691
+ #if defined(VAT_SKIN_ARRAY_MR) && defined(METALLICWORKFLOW)
13692
+ metallicRoughness.r = _tkMR.x;
13693
+ metallicRoughness.g = _tkMR.y;
13694
+ #endif
13695
+ `;
13696
+ }
13697
+ getWGSLSkinEmissiveCode() {
13698
+ return `
13699
+ #if defined(VAT_SKIN_ARRAY) && defined(VAT_SKIN_ARRAY_EMISSIVE)
13700
+ {
13701
+ let _vatEmis = textureSample(tkEmissiveArray, tkEmissiveArraySampler, fragmentInputs.vVatSkinUV, i32(fragmentInputs.vVatSkinLayer + 0.5)).rgb;
13702
+ finalEmissive = toLinearSpaceVec3(_vatEmis);
13275
13703
  }
13276
13704
  #endif
13277
13705
  `;
@@ -27671,6 +28099,138 @@ ShurikenParticles.UNITY_TO_BABYLON_LIFETIME_RATIO = 0.65;
27671
28099
  ShurikenParticles.UNITY_TO_BABYLON_DEATH_FADE_ALPHA = 0.2;
27672
28100
  ShurikenParticles.EMITTER_POSITION_OFFSET = new Vector3(0, 0, 0);
27673
28101
  ShurikenParticles.EMITTER_ROTATION_OFFSET = new Vector3(90, 0, 0);
28102
+ export class SkinTextureArray {
28103
+ static Build(scene, urls, options = {}) {
28104
+ const requested = (urls || []).filter((u) => u != null && String(u).length > 0);
28105
+ if (requested.length === 0)
28106
+ return Promise.resolve(null);
28107
+ const maxLayers = SkinTextureArray._maxArrayLayers(scene.getEngine());
28108
+ let list = requested;
28109
+ if (requested.length > maxLayers) {
28110
+ console.warn("SkinTextureArray: " + requested.length + " slices exceeds GPU maxTextureArrayLayers ("
28111
+ + maxLayers + ") — clamping to " + maxLayers + ". Skin indices >= " + maxLayers + " will be unavailable.");
28112
+ list = requested.slice(0, maxLayers);
28113
+ }
28114
+ const optKey = "m" + (options.mipmaps === false ? 0 : 1)
28115
+ + "|y" + (options.invertY === true ? 1 : 0)
28116
+ + "|s" + (options.samplingMode != null ? options.samplingMode : "d");
28117
+ const key = list.join("|") + "||" + optKey;
28118
+ if (SkinTextureArray._cache[key] != null)
28119
+ return SkinTextureArray._cache[key];
28120
+ const built = SkinTextureArray._loadImages(list).then((images) => {
28121
+ if (images == null || images.length === 0)
28122
+ return null;
28123
+ const w = images[0].width, h = images[0].height, depth = images.length;
28124
+ const canvas = document.createElement("canvas");
28125
+ canvas.width = w;
28126
+ canvas.height = h;
28127
+ const ctx = canvas.getContext("2d");
28128
+ const data = new Uint8Array(w * h * 4 * depth);
28129
+ const flipY = (options.invertY != null) ? options.invertY : false;
28130
+ for (let i = 0; i < depth; i++) {
28131
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
28132
+ ctx.clearRect(0, 0, w, h);
28133
+ if (flipY)
28134
+ ctx.setTransform(1, 0, 0, -1, 0, h);
28135
+ ctx.drawImage(images[i], 0, 0, w, h);
28136
+ const id = ctx.getImageData(0, 0, w, h).data;
28137
+ data.set(id, i * w * h * 4);
28138
+ }
28139
+ const engine = scene.getEngine();
28140
+ const isWebGPU = (engine != null && engine.isWebGPU === true);
28141
+ const genMips = (options.mipmaps !== false);
28142
+ const mipCount = genMips ? (Math.floor(Math.log2(Math.max(w, h))) + 1) : 1;
28143
+ const samplingMode = (options.samplingMode != null) ? options.samplingMode
28144
+ : (genMips ? Texture.TRILINEAR_SAMPLINGMODE : Texture.BILINEAR_SAMPLINGMODE);
28145
+ const arr = new RawTexture2DArray(data, w, h, depth, Constants.TEXTUREFORMAT_RGBA, scene, genMips, false, samplingMode, Constants.TEXTURETYPE_UNSIGNED_BYTE, 0, mipCount);
28146
+ if (genMips && isWebGPU && mipCount > 1)
28147
+ SkinTextureArray._uploadMipChain(scene, arr, data, w, h, depth);
28148
+ arr.wrapU = Texture.CLAMP_ADDRESSMODE;
28149
+ arr.wrapV = Texture.CLAMP_ADDRESSMODE;
28150
+ arr.anisotropicFilteringLevel = (options.aniso != null) ? options.aniso : 8;
28151
+ return arr;
28152
+ }).catch((e) => { console.warn("SkinTextureArray: build failed", e); delete SkinTextureArray._cache[key]; return null; });
28153
+ SkinTextureArray._cache[key] = built;
28154
+ return built;
28155
+ }
28156
+ static _uploadMipChain(scene, arr, base, w, h, depth) {
28157
+ const engine = scene.getEngine();
28158
+ const itex = arr.getInternalTexture();
28159
+ if (itex == null || typeof engine.updateRawTexture2DArray !== "function")
28160
+ return;
28161
+ const count = Math.floor(Math.log2(Math.max(w, h))) + 1;
28162
+ try {
28163
+ let prev = base, pw = w, ph = h;
28164
+ for (let m = 1; m < count; m++) {
28165
+ const nw = Math.max(1, pw >> 1), nh = Math.max(1, ph >> 1);
28166
+ const mip = SkinTextureArray._downsampleLayers(prev, pw, ph, depth, nw, nh);
28167
+ engine.updateRawTexture2DArray(itex, mip, Constants.TEXTUREFORMAT_RGBA, false, null, Constants.TEXTURETYPE_UNSIGNED_BYTE, m);
28168
+ prev = mip;
28169
+ pw = nw;
28170
+ ph = nh;
28171
+ }
28172
+ }
28173
+ catch (e) {
28174
+ console.warn("SkinTextureArray: mip-chain upload failed, using base level only", e);
28175
+ }
28176
+ }
28177
+ static _downsampleLayers(src, sw, sh, depth, dw, dh) {
28178
+ const out = new Uint8Array(dw * dh * 4 * depth);
28179
+ for (let L = 0; L < depth; L++) {
28180
+ const so = L * sw * sh * 4, doo = L * dw * dh * 4;
28181
+ for (let y = 0; y < dh; y++) {
28182
+ for (let x = 0; x < dw; x++) {
28183
+ const sx = x * 2, sy = y * 2;
28184
+ const sx1 = Math.min(sx + 1, sw - 1), sy1 = Math.min(sy + 1, sh - 1);
28185
+ for (let c = 0; c < 4; c++) {
28186
+ const a = src[so + (sy * sw + sx) * 4 + c], b = src[so + (sy * sw + sx1) * 4 + c], e = src[so + (sy1 * sw + sx) * 4 + c], d = src[so + (sy1 * sw + sx1) * 4 + c];
28187
+ out[doo + (y * dw + x) * 4 + c] = (a + b + e + d) >> 2;
28188
+ }
28189
+ }
28190
+ }
28191
+ }
28192
+ return out;
28193
+ }
28194
+ static _maxArrayLayers(engine) {
28195
+ try {
28196
+ const dev = (engine != null) ? (engine._device || (typeof engine.getDevice === "function" ? engine.getDevice() : null)) : null;
28197
+ if (dev != null && dev.limits != null && typeof dev.limits.maxTextureArrayLayers === "number" && dev.limits.maxTextureArrayLayers > 0) {
28198
+ return dev.limits.maxTextureArrayLayers;
28199
+ }
28200
+ const gl = (engine != null) ? engine._gl : null;
28201
+ if (gl != null && typeof gl.getParameter === "function") {
28202
+ const MAX_ARRAY_TEXTURE_LAYERS = 0x88FF;
28203
+ const v = gl.getParameter(MAX_ARRAY_TEXTURE_LAYERS);
28204
+ if (typeof v === "number" && v > 0)
28205
+ return v;
28206
+ }
28207
+ }
28208
+ catch (e) { }
28209
+ return 256;
28210
+ }
28211
+ static _loadImages(urls) {
28212
+ return Promise.all(urls.map((u) => new Promise((resolve, reject) => {
28213
+ const img = document.createElement("img");
28214
+ img.crossOrigin = "anonymous";
28215
+ img.onload = () => resolve(img);
28216
+ img.onerror = () => reject(new Error("SkinTextureArray: failed to load " + u));
28217
+ img.src = u;
28218
+ })));
28219
+ }
28220
+ static Clear() {
28221
+ const entries = [];
28222
+ for (const k in SkinTextureArray._cache) {
28223
+ if (SkinTextureArray._cache[k] != null)
28224
+ entries.push(SkinTextureArray._cache[k]);
28225
+ }
28226
+ SkinTextureArray._cache = {};
28227
+ for (const p of entries) {
28228
+ p.then((arr) => { if (arr != null)
28229
+ arr.dispose(); }).catch(() => { });
28230
+ }
28231
+ }
28232
+ }
28233
+ SkinTextureArray._cache = {};
27674
28234
  export class MeshNormalProxy {
27675
28235
  constructor() {
27676
28236
  this.blendWithPhysicsNormal = 0.0;
@@ -29245,6 +29805,410 @@ TerrainBuilder.detailChunkMaxTotalChunks = 256;
29245
29805
  TerrainBuilder.meshDetailChunkMode = 1;
29246
29806
  TerrainBuilder.meshDetailChunkTargetInstances = 20000;
29247
29807
  TerrainBuilder.meshDetailChunkWorldSize = 64;
29808
+ export class TextureAtlasSkin extends ScriptComponent {
29809
+ constructor(transform, scene, properties = {}, alias = "TextureAtlasSkin") {
29810
+ super(transform, scene, properties, alias);
29811
+ this.abtractMesh = null;
29812
+ this.textureData = null;
29813
+ this.defaultIndex = 0;
29814
+ this.skinnedParts = [];
29815
+ this.albedoSlices = null;
29816
+ this.normalSlices = null;
29817
+ this.metallicSlices = null;
29818
+ this.occlusionSlices = null;
29819
+ this.emissiveSlices = null;
29820
+ this._applied = false;
29821
+ this._manifest = null;
29822
+ this._currentIndex = 0;
29823
+ this._targets = [];
29824
+ this._clonedMultiMaterial = null;
29825
+ }
29826
+ awake() {
29827
+ this.abtractMesh = this.getAbstractMesh();
29828
+ this.albedoSlices = this.getProperty("albedoSlices", null);
29829
+ this.normalSlices = this.getProperty("normalSlices", null);
29830
+ this.metallicSlices = this.getProperty("metallicSlices", null);
29831
+ this.occlusionSlices = this.getProperty("occlusionSlices", null);
29832
+ this.emissiveSlices = this.getProperty("emissiveSlices", null);
29833
+ window["__" + this.transform.name] = this;
29834
+ }
29835
+ start() {
29836
+ this.applySkins();
29837
+ }
29838
+ applySkins() {
29839
+ if (this._applied)
29840
+ return;
29841
+ const mesh = this.abtractMesh || this.getAbstractMesh();
29842
+ if (mesh == null) {
29843
+ console.warn("TextureAtlasSkin: no abstract mesh on " + this.transform.name);
29844
+ return;
29845
+ }
29846
+ let manifest = this.parseManifest();
29847
+ if (manifest == null) {
29848
+ const sliceCount = this.maxChannelSliceCount();
29849
+ if (sliceCount > 0) {
29850
+ const skins = [];
29851
+ for (let i = 0; i < sliceCount; i++)
29852
+ skins.push({ index: i, name: "skin_" + i });
29853
+ manifest = { skinArray: true, count: sliceCount, skins: skins };
29854
+ }
29855
+ else {
29856
+ console.warn("TextureAtlasSkin: no textureData manifest and no channel slices on " + this.transform.name);
29857
+ return;
29858
+ }
29859
+ }
29860
+ this._manifest = manifest;
29861
+ this._currentIndex = this.clampIndex(this.toInt(this.defaultIndex, 0), "defaultIndex");
29862
+ const isInstanced = this.isInstancedSetup(mesh);
29863
+ const slots = this.getMeshSlots(mesh);
29864
+ const parts = this.skinnedParts || [];
29865
+ if (parts.length === 0) {
29866
+ console.warn("TextureAtlasSkin: no skinnedParts on " + this.transform.name);
29867
+ return;
29868
+ }
29869
+ for (const part of parts) {
29870
+ const targetMat = this.resolvePartMaterial(part);
29871
+ const slot = this.matchSlot(slots, targetMat, part);
29872
+ if (slot == null || !(slot.material instanceof CustomShaderMaterial)) {
29873
+ console.warn("TextureAtlasSkin: skinned part not found / not a CustomShaderMaterial on " + this.transform.name, part);
29874
+ continue;
29875
+ }
29876
+ let material = slot.material;
29877
+ const isVat = (material instanceof VertexAnimationMaterial);
29878
+ const kind = isVat ? "vat" : (isInstanced ? "instanced" : "regular");
29879
+ if (kind === "regular") {
29880
+ material = this.cloneForRegular(mesh, slot, material);
29881
+ }
29882
+ this.configureMaterial(material, manifest, kind);
29883
+ this._targets.push({ material: material, kind: kind });
29884
+ console.warn(">>> TextureAtlasSkin: " + this.transform.name + " — part '" + material.name + "' → " + this.describeKind(kind) + " setup");
29885
+ }
29886
+ this.applyIndexToTargets(mesh, this._currentIndex);
29887
+ this._applied = true;
29888
+ }
29889
+ setSkinIndex(index) {
29890
+ const requested = this.toInt(index, 0);
29891
+ this._currentIndex = this.clampIndex(requested, "skin index");
29892
+ const mesh = this.abtractMesh || this.getAbstractMesh();
29893
+ this.applyIndexToTargets(mesh, this._currentIndex);
29894
+ return this._currentIndex;
29895
+ }
29896
+ clampIndex(index, label) {
29897
+ const count = this.skinCount();
29898
+ const max = Math.max(0, count - 1);
29899
+ const clamped = (index < 0) ? 0 : (index > max ? max : index);
29900
+ if (clamped !== index) {
29901
+ console.warn("TextureAtlasSkin: " + label + " " + index + " out of range [0, " + max + "] on " + this.transform.name + " — clamped to " + clamped);
29902
+ }
29903
+ return clamped;
29904
+ }
29905
+ skinCount() {
29906
+ const m = this._manifest;
29907
+ if (m == null)
29908
+ return this.maxChannelSliceCount();
29909
+ const skins = m.skins || [];
29910
+ if (skins.length > 0)
29911
+ return skins.length;
29912
+ const c = this.toInt(m.count, 0);
29913
+ if (c > 0)
29914
+ return c;
29915
+ return this.maxChannelSliceCount();
29916
+ }
29917
+ maxChannelSliceCount() {
29918
+ const lens = [this.albedoSlices, this.normalSlices, this.metallicSlices, this.occlusionSlices, this.emissiveSlices]
29919
+ .map((s) => (s != null) ? s.length : 0);
29920
+ return Math.max(0, lens[0], lens[1], lens[2], lens[3], lens[4]);
29921
+ }
29922
+ getSkinIndex() {
29923
+ return this._currentIndex;
29924
+ }
29925
+ configureMaterial(material, manifest, kind) {
29926
+ const shared = (kind !== "regular");
29927
+ if (shared && this.isConfigured(material))
29928
+ return;
29929
+ if (shared)
29930
+ this.markConfigured(material);
29931
+ if (kind === "instanced")
29932
+ this.ensureSkinLayerBuffer(this.abtractMesh || this.getAbstractMesh());
29933
+ const albedoUrls = this.albedoChannelUrls(manifest);
29934
+ const normalUrls = this.normalChannelUrls(manifest);
29935
+ const mrUrls = this.metalRoughChannelUrls(manifest);
29936
+ const emissiveUrls = this.emissiveChannelUrls(manifest);
29937
+ const has = (u) => (u != null && u.length > 0);
29938
+ if (!has(albedoUrls) && !has(normalUrls) && !has(mrUrls) && !has(emissiveUrls)) {
29939
+ console.warn("TextureAtlasSkin: no channel slices to build a skin array on " + this.transform.name);
29940
+ return;
29941
+ }
29942
+ if (kind !== "vat")
29943
+ material.setSkinArrayLayerMode(kind === "regular");
29944
+ const build = (urls, mipped) => has(urls) ? SkinTextureArray.Build(this.scene, urls, mipped ? {} : { mipmaps: false }) : Promise.resolve(null);
29945
+ Promise.all([build(albedoUrls, true), build(normalUrls, false), build(mrUrls, true), build(emissiveUrls, true)])
29946
+ .then(([albTex, nrmTex, mrTex, emisTex]) => {
29947
+ const vmat = (kind === "vat") ? material : null;
29948
+ if (albTex != null) {
29949
+ material.addTextureArrayUniform("tkAlbedoArray", albTex);
29950
+ if (vmat != null)
29951
+ vmat.enableVatSkinArray();
29952
+ else
29953
+ material.enableSkinArray(kind === "regular");
29954
+ }
29955
+ if (nrmTex != null) {
29956
+ material.addTextureArrayUniform("tkNormalArray", nrmTex);
29957
+ if (vmat != null)
29958
+ vmat.enableVatSkinArrayNormal();
29959
+ else
29960
+ material.enableSkinArrayNormal();
29961
+ }
29962
+ if (mrTex != null) {
29963
+ material.addTextureArrayUniform("tkMetalRoughArray", mrTex);
29964
+ if (vmat != null)
29965
+ vmat.enableVatSkinArrayMetalRough();
29966
+ else
29967
+ material.enableSkinArrayMetalRough();
29968
+ }
29969
+ if (emisTex != null) {
29970
+ material.addTextureArrayUniform("tkEmissiveArray", emisTex);
29971
+ if (vmat != null)
29972
+ vmat.enableVatSkinArrayEmissive();
29973
+ else
29974
+ material.enableSkinArrayEmissive();
29975
+ }
29976
+ if (albTex == null && nrmTex == null && mrTex == null && emisTex == null) {
29977
+ console.warn("TextureAtlasSkin: all channel arrays failed to build on " + this.transform.name);
29978
+ return;
29979
+ }
29980
+ this.applyLayerToMaterial(material, kind, this._currentIndex);
29981
+ });
29982
+ }
29983
+ albedoChannelUrls(manifest) {
29984
+ const fromSlices = this.sliceUrls(this.albedoSlices);
29985
+ if (fromSlices != null)
29986
+ return fromSlices;
29987
+ const layers = (manifest != null) ? manifest.albedoLayers : null;
29988
+ if (Array.isArray(layers)) {
29989
+ const out = layers.filter((x) => x != null && String(x).length > 0).map((x) => this.resolveLayerUrl(String(x)));
29990
+ return (out.length > 0) ? out : null;
29991
+ }
29992
+ return null;
29993
+ }
29994
+ normalChannelUrls(manifest) {
29995
+ const fromSlices = this.sliceUrls(this.normalSlices);
29996
+ if (fromSlices != null)
29997
+ return fromSlices;
29998
+ const layers = (manifest != null) ? manifest.normalLayers : null;
29999
+ if (Array.isArray(layers)) {
30000
+ const out = layers.filter((x) => x != null && String(x).length > 0).map((x) => this.resolveLayerUrl(String(x)));
30001
+ return (out.length > 0) ? out : null;
30002
+ }
30003
+ return null;
30004
+ }
30005
+ metalRoughChannelUrls(manifest) {
30006
+ const fromSlices = this.sliceUrls(this.metallicSlices);
30007
+ if (fromSlices != null)
30008
+ return fromSlices;
30009
+ const layers = (manifest != null) ? manifest.metalRoughLayers : null;
30010
+ if (Array.isArray(layers)) {
30011
+ const out = layers.filter((x) => x != null && String(x).length > 0).map((x) => this.resolveLayerUrl(String(x)));
30012
+ return (out.length > 0) ? out : null;
30013
+ }
30014
+ return null;
30015
+ }
30016
+ emissiveChannelUrls(manifest) {
30017
+ const fromSlices = this.sliceUrls(this.emissiveSlices);
30018
+ if (fromSlices != null)
30019
+ return fromSlices;
30020
+ const layers = (manifest != null) ? manifest.emissiveLayers : null;
30021
+ if (Array.isArray(layers)) {
30022
+ const out = layers.filter((x) => x != null && String(x).length > 0).map((x) => this.resolveLayerUrl(String(x)));
30023
+ return (out.length > 0) ? out : null;
30024
+ }
30025
+ return null;
30026
+ }
30027
+ sliceUrls(slices) {
30028
+ if (slices == null || slices.length === 0)
30029
+ return null;
30030
+ const root = SceneManager.GetRootUrl(this.scene) || "";
30031
+ const urls = [];
30032
+ for (const s of slices) {
30033
+ if (s == null)
30034
+ continue;
30035
+ const anyS = s;
30036
+ let u = null;
30037
+ if (anyS.url != null && String(anyS.url).length > 0)
30038
+ u = String(anyS.url);
30039
+ else if (anyS.filename != null && String(anyS.filename).length > 0)
30040
+ u = root + String(anyS.filename);
30041
+ if (u != null && u.length > 0)
30042
+ urls.push(u);
30043
+ }
30044
+ return (urls.length > 0) ? urls : null;
30045
+ }
30046
+ resolveLayerUrl(filename) {
30047
+ if (filename == null)
30048
+ return "";
30049
+ const f = String(filename);
30050
+ if (/^(https?:|blob:|data:|\/)/i.test(f))
30051
+ return f;
30052
+ const root = SceneManager.GetRootUrl(this.scene) || "";
30053
+ return root + f;
30054
+ }
30055
+ applyIndexToTargets(mesh, index) {
30056
+ if (mesh == null)
30057
+ return;
30058
+ for (const t of this._targets) {
30059
+ this.applyLayerToMaterial(t.material, t.kind, index);
30060
+ }
30061
+ }
30062
+ applyLayerToMaterial(material, kind, index) {
30063
+ if (kind === "vat") {
30064
+ VertexAnimationController.SetAtlasCellIndex(this.abtractMesh || this.getAbstractMesh(), index);
30065
+ }
30066
+ else if (kind === "instanced") {
30067
+ const mesh = this.abtractMesh || this.getAbstractMesh();
30068
+ this.ensureSkinLayerBuffer(mesh);
30069
+ mesh.instancedBuffers.tkSkinLayer = index;
30070
+ }
30071
+ else {
30072
+ material.setSkinLayer(index);
30073
+ }
30074
+ }
30075
+ isInstancedSetup(mesh) {
30076
+ if (mesh instanceof InstancedMesh)
30077
+ return true;
30078
+ const src = (mesh instanceof Mesh) ? mesh : null;
30079
+ return (src != null && src.instances != null && src.instances.length > 0);
30080
+ }
30081
+ ensureSkinLayerBuffer(mesh) {
30082
+ const inst = mesh;
30083
+ const src = (inst != null && inst.sourceMesh != null) ? inst.sourceMesh : mesh;
30084
+ const target = (src != null) ? src : mesh;
30085
+ if (target != null && typeof target.registerInstancedBuffer === "function") {
30086
+ const ib = target.instancedBuffers;
30087
+ if (ib == null || ib.tkSkinLayer === undefined) {
30088
+ target.registerInstancedBuffer("tkSkinLayer", 1);
30089
+ target.instancedBuffers = target.instancedBuffers || {};
30090
+ target.instancedBuffers.tkSkinLayer = 0;
30091
+ }
30092
+ }
30093
+ mesh.instancedBuffers = mesh.instancedBuffers || {};
30094
+ }
30095
+ getMeshSlots(mesh) {
30096
+ const slots = [];
30097
+ const mat = mesh.material;
30098
+ if (mat instanceof MultiMaterial) {
30099
+ const subs = mat.subMaterials;
30100
+ for (let i = 0; i < subs.length; i++) {
30101
+ if (subs[i] != null)
30102
+ slots.push({ material: subs[i], mm: mat, index: i });
30103
+ }
30104
+ }
30105
+ else if (mat != null) {
30106
+ slots.push({ material: mat, mm: null, index: -1 });
30107
+ }
30108
+ return slots;
30109
+ }
30110
+ matchSlot(slots, targetMat, part) {
30111
+ if (targetMat != null) {
30112
+ for (const s of slots)
30113
+ if (s.material === targetMat)
30114
+ return s;
30115
+ }
30116
+ const name = this.partName(part) || (targetMat != null ? targetMat.name : null);
30117
+ if (name != null && name.length > 0) {
30118
+ for (const s of slots)
30119
+ if (s.material.name === name)
30120
+ return s;
30121
+ for (const s of slots)
30122
+ if (s.material.name.indexOf(name) !== -1)
30123
+ return s;
30124
+ }
30125
+ if (slots.length === 1)
30126
+ return slots[0];
30127
+ return null;
30128
+ }
30129
+ resolvePartMaterial(part) {
30130
+ if (part == null)
30131
+ return null;
30132
+ if (part instanceof Material)
30133
+ return part;
30134
+ if (part.skinPart != null)
30135
+ return this.resolvePartMaterial(part.skinPart);
30136
+ const name = this.partName(part);
30137
+ if (name == null)
30138
+ return null;
30139
+ let mat = this.scene.getMaterialByName(name);
30140
+ if (mat == null && SceneManager.GetMaterialWithName != null)
30141
+ mat = SceneManager.GetMaterialWithName(this.scene, name);
30142
+ return mat;
30143
+ }
30144
+ partName(part) {
30145
+ if (part == null)
30146
+ return null;
30147
+ if (typeof part === "string")
30148
+ return part;
30149
+ if (part instanceof Material)
30150
+ return part.name;
30151
+ if (part.skinPart != null)
30152
+ return this.partName(part.skinPart);
30153
+ if (part.name != null)
30154
+ return part.name;
30155
+ if (part.material != null)
30156
+ return this.partName(part.material);
30157
+ return null;
30158
+ }
30159
+ cloneForRegular(mesh, slot, material) {
30160
+ const tag = "_skin_" + (this.transform != null ? this.transform.name : "skin");
30161
+ const cloned = material.clone(material.name + tag);
30162
+ if (!(cloned instanceof CustomShaderMaterial)) {
30163
+ console.warn("TextureAtlasSkin: clone did not preserve CustomShaderMaterial — using original (skin may be shared)", cloned);
30164
+ return material;
30165
+ }
30166
+ if (slot.mm != null) {
30167
+ if (this._clonedMultiMaterial == null) {
30168
+ this._clonedMultiMaterial = slot.mm.clone(slot.mm.name + tag);
30169
+ mesh.material = this._clonedMultiMaterial;
30170
+ }
30171
+ this._clonedMultiMaterial.subMaterials[slot.index] = cloned;
30172
+ }
30173
+ else {
30174
+ mesh.material = cloned;
30175
+ }
30176
+ return cloned;
30177
+ }
30178
+ parseManifest() {
30179
+ let data = this.textureData;
30180
+ if (data == null)
30181
+ return null;
30182
+ if (typeof data === "string") {
30183
+ try {
30184
+ data = JSON.parse(data);
30185
+ }
30186
+ catch (e) {
30187
+ console.warn("TextureAtlasSkin: bad textureData JSON", e);
30188
+ return null;
30189
+ }
30190
+ }
30191
+ return (data != null && data.skins != null) ? data : null;
30192
+ }
30193
+ toInt(v, def) {
30194
+ const n = (typeof v === "number") ? v : parseInt(v, 10);
30195
+ return isNaN(n) ? def : Math.floor(n);
30196
+ }
30197
+ isConfigured(material) {
30198
+ return !!(material && material.metadata && material.metadata.atlasSkinConfigured === true);
30199
+ }
30200
+ markConfigured(material) {
30201
+ material.metadata = material.metadata || {};
30202
+ material.metadata.atlasSkinConfigured = true;
30203
+ }
30204
+ describeKind(kind) {
30205
+ if (kind === "vat")
30206
+ return "VAT Mesh (packed cell index)";
30207
+ if (kind === "instanced")
30208
+ return "Instanced Mesh (per-instance UV buffer)";
30209
+ return "Babylon Mesh (cloned material uniform)";
30210
+ }
30211
+ }
29248
30212
  export class UserInterface extends ScriptComponent {
29249
30213
  constructor(transform, scene, properties = {}, alias = "UserInterface") {
29250
30214
  super(transform, scene, properties, alias);
@@ -30766,6 +31730,7 @@ SceneManager.RegisterClass("CustomShaderMaterial", CustomShaderMaterial);
30766
31730
  SceneManager.RegisterClass("UniversalShaderMaterial", UniversalShaderMaterial);
30767
31731
  SceneManager.RegisterClass("UnityStyleLightingPlugin", UnityStyleLightingPlugin);
30768
31732
  SceneManager.RegisterClass("CustomShaderMaterialPlugin", CustomShaderMaterialPlugin);
31733
+ SceneManager.RegisterClass("SkinArraySwitchingPlugin", SkinArraySwitchingPlugin);
30769
31734
  SceneManager.RegisterClass("StandardShaderMaterial", StandardShaderMaterial);
30770
31735
  SceneManager.RegisterClass("StandardShaderMaterialPlugin", StandardShaderMaterialPlugin);
30771
31736
  SceneManager.RegisterClass("UniversalTerrainMaterial", UniversalTerrainMaterial);
@@ -41398,7 +42363,9 @@ SceneManager.RegisterClass("NavigationAgent", NavigationAgent);
41398
42363
  SceneManager.RegisterClass("PostProcessor", PostProcessor);
41399
42364
  SceneManager.RegisterClass("RigidbodyPhysics", RigidbodyPhysics);
41400
42365
  SceneManager.RegisterClass("ShurikenParticles", ShurikenParticles);
42366
+ SceneManager.RegisterClass("SkinTextureArray", SkinTextureArray);
41401
42367
  SceneManager.RegisterClass("TerrainBuilder", TerrainBuilder);
42368
+ SceneManager.RegisterClass("TextureAtlasSkin", TextureAtlasSkin);
41402
42369
  SceneManager.RegisterClass("UnitySlider", UnitySlider);
41403
42370
  SceneManager.RegisterClass("UnityScrollBar", UnityScrollBar);
41404
42371
  SceneManager.RegisterClass("UnityDropdownMenu", UnityDropdownMenu);