@babylonjs-toolkit/next 9.0.1 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,7 +40,7 @@ import { Skeleton } from '@babylonjs/core/Bones/skeleton';
40
40
  import { Control } from '@babylonjs/gui/2D/controls/control';
41
41
  import { TextBlock } from '@babylonjs/gui/2D/controls/textBlock';
42
42
  import { Image } from '@babylonjs/gui/2D/controls/image';
43
- import { PhysicsBody, PhysicsShape, PhysicsShapeCapsule, PhysicsRaycastResult, PhysicsMotionType, PhysicsShapeContainer, ShapeCastResult, PhysicsShapeSphere, PhysicsShapeMesh, PhysicsShapeConvexHull, PhysicsShapeBox, PhysicsShapeCylinder, PhysicsMaterialCombineMode, PhysicsShapeType } from '@babylonjs/core/Physics';
43
+ import { PhysicsBody, PhysicsShape, PhysicsShapeCapsule, PhysicsRaycastResult, PhysicsMotionType, PhysicsShapeContainer, ShapeCastResult, PhysicsShapeMesh, PhysicsShapeConvexHull, PhysicsShapeBox, PhysicsShapeSphere, PhysicsShapeCylinder, PhysicsMaterialCombineMode, PhysicsShapeType } from '@babylonjs/core/Physics';
44
44
  import { HavokPlugin } from '@babylonjs/core/Physics/v2/Plugins/havokPlugin';
45
45
  import { GLTFLoader, ArrayItem } from '@babylonjs/loaders/glTF/2.0/glTFLoader';
46
46
  import { Texture } from '@babylonjs/core/Materials/Textures/texture';
@@ -75,13 +75,11 @@ import { GPUParticleSystem } from '@babylonjs/core/Particles/gpuParticleSystem';
75
75
  import { AdvancedDynamicTexture } from '@babylonjs/gui/2D/advancedDynamicTexture';
76
76
  import { InputText } from '@babylonjs/gui/2D/controls/inputText';
77
77
  import { VideoTexture } from '@babylonjs/core/Materials/Textures/videoTexture';
78
- import { UniversalTerrainMaterial, UniversalTerrainMaterialPlugin } from './universalterrainmaterial';
79
- import { TreeBranchMaterial, TreeBranchMaterialPlugin } from './treebranchmaterial';
80
78
  import { UnitySlider } from './unityslider';
81
79
  import { UnityScrollBar } from './unityscrollbar';
82
80
  import { UnityDropdownMenu } from './unitydropdownmenu';
83
81
  export class SceneManager {
84
- static get Version() { return "9.0.1 - R1"; }
82
+ static get Version() { return "9.5.0 - R1"; }
85
83
  static get Copyright() { return "All rights reserved (c) 2024 Mackey Kinard"; }
86
84
  static GetEngine(scene) {
87
85
  let result = null;
@@ -117,16 +115,17 @@ export class SceneManager {
117
115
  }
118
116
  static get PlaygroundCdn() { return "https://cdn.jsdelivr.net/gh/BabylonJS/BabylonToolkit@master/Runtime/"; }
119
117
  static get PlaygroundRepo() { return "https://www.babylontoolkit.com/playground/"; }
120
- static async InitializePlayground(engine, options = null) {
121
- return SceneManager.InitializeRuntime(engine, options);
118
+ static async InitializePlayground(engine, options = null, scene = null, inputOptions = null) {
119
+ return SceneManager.InitializeRuntime(engine, options, scene, inputOptions);
122
120
  }
123
- static async InitializeRuntime(engine, options = null) {
121
+ static async InitializeRuntime(engine, options = null, scene = null, inputOptions = null) {
124
122
  Tools.Log("Babylon.js Toolkit v" + SceneManager.Version);
125
123
  if (AudioSource.IsLegacyEngine())
126
124
  Tools.Log("Legacy audio engine detected");
127
125
  const hardwareScalingLevel = (options != null && options.hardwareScalingLevel != null) ? options.hardwareScalingLevel : WindowManager.GetHardwareScalingLevel();
128
126
  const initSceneFileLoaders = (options != null && options.initSceneFileLoaders != null) ? options.initSceneFileLoaders : true;
129
127
  const loadAsyncRuntimeLibs = (options != null && options.loadAsyncRuntimeLibs != null) ? options.loadAsyncRuntimeLibs : true;
128
+ const enableUserInput = (options != null && options.enableUserInput != null) ? options.enableUserInput : false;
130
129
  const defaultProjectScriptBundle = (SceneManager.PlaygroundCdn + "default.playground.js");
131
130
  const loadProjectScriptBundle = (options != null && options.loadProjectScriptBundle != null) ? options.loadProjectScriptBundle : false;
132
131
  const projectScriptBundleUrl = (options != null && options.projectScriptBundleUrl != null) ? options.projectScriptBundleUrl : defaultProjectScriptBundle;
@@ -137,6 +136,8 @@ export class SceneManager {
137
136
  SceneManager.ShowLoadingScreen(engine, hideLoadingUIWithEngine, defaultLoadingUIMarginTop);
138
137
  if (hardwareScalingLevel != null && hardwareScalingLevel > 0)
139
138
  engine.setHardwareScalingLevel(hardwareScalingLevel);
139
+ if (enableUserInput === true)
140
+ InputController.EnableUserInput(engine, scene, inputOptions);
140
141
  SceneManager.UniversalModuleDefinition = false;
141
142
  try {
142
143
  SceneManager.InitializeSceneLoaderPlugin();
@@ -369,38 +370,6 @@ export class SceneManager {
369
370
  }
370
371
  });
371
372
  }
372
- static NavigateTo(scene, route, options = null, useWindowLocation = false) {
373
- if (useWindowLocation === true) {
374
- if (options?.replace === true) {
375
- window.location.replace(route);
376
- }
377
- else {
378
- window.location.assign(route);
379
- }
380
- return;
381
- }
382
- if (scene.reactNavigationFunction != null) {
383
- const navOptions = { ...options, state: { ...(options?.state || {}), fromApp: true } };
384
- scene.reactNavigationFunction(route, navOptions);
385
- }
386
- else {
387
- console.warn("React navigation hook is not set on the scene.");
388
- }
389
- }
390
- static SetReactNavigationHook(scene, navigateToFunction) {
391
- scene.reactNavigationFunction = navigateToFunction;
392
- }
393
- static DeleteReactNavigationHook(scene) {
394
- if (scene.reactNavigationFunction != null) {
395
- scene.reactNavigationFunction = null;
396
- try {
397
- delete scene.reactNavigationFunction;
398
- }
399
- catch (e) {
400
- console.warn(e);
401
- }
402
- }
403
- }
404
373
  static EnableSceneParsing(enabled) {
405
374
  SceneManager.SceneParsingEnabled = enabled;
406
375
  }
@@ -3213,6 +3182,11 @@ export class ScriptComponent {
3213
3182
  return result;
3214
3183
  }
3215
3184
  }
3185
+ export class GameModeController extends ScriptComponent {
3186
+ constructor(transform, scene, properties = {}, alias = null) {
3187
+ super(transform, scene, properties, alias);
3188
+ }
3189
+ }
3216
3190
  export var System;
3217
3191
  (function (System) {
3218
3192
  System[System["Deg2Rad"] = (Math.PI * 2 / 360)] = "Deg2Rad";
@@ -9263,6 +9237,643 @@ Utilities.LoadingState = -1;
9263
9237
  Utilities.OnPreloaderProgress = null;
9264
9238
  Utilities.OnPreloaderComplete = null;
9265
9239
  Utilities.LoaderItemsMarkedForDisposal = [];
9240
+ export class UniversalTerrainMaterial extends CustomShaderMaterial {
9241
+ constructor(name, scene) {
9242
+ super(name, scene);
9243
+ this.terrainInfo = null;
9244
+ this.shader = this.getShaderName();
9245
+ this.plugin = new UniversalTerrainMaterialPlugin(this, this.shader);
9246
+ }
9247
+ awake() {
9248
+ }
9249
+ update() {
9250
+ }
9251
+ getShaderName() {
9252
+ return "UniversalTerrainMaterial";
9253
+ }
9254
+ getTerrainInfo() {
9255
+ return this.terrainInfo;
9256
+ }
9257
+ }
9258
+ export class UniversalTerrainMaterialPlugin extends CustomShaderMaterialPlugin {
9259
+ constructor(customMaterial, shaderName) {
9260
+ super(customMaterial, shaderName, 100, { UNIVERSALTERRAINMATERIAL: false }, true, true, false);
9261
+ this.colorName = "surfaceAlbedo";
9262
+ this.splatmapSampler = "splatmapSampler";
9263
+ this.detailsSampler = "detailsSampler";
9264
+ this.normalsSampler = "normalsSampler";
9265
+ this.GLSL_CustomFragment = null;
9266
+ this.GLSL_CustomVertex = null;
9267
+ this.GLSL_VertexMainEnd = null;
9268
+ this.GLSL_FragmentUpdateColor = null;
9269
+ this.WGSL_CustomFragment = null;
9270
+ this.WGSL_CustomVertex = null;
9271
+ this.WGSL_VertexMainEnd = null;
9272
+ this.WGSL_FragmentUpdateColor = null;
9273
+ }
9274
+ isCompatible(shaderLanguage) {
9275
+ return (shaderLanguage === ShaderLanguage.WGSL || shaderLanguage === ShaderLanguage.GLSL);
9276
+ }
9277
+ getClassName() {
9278
+ return "UniversalTerrainMaterialPlugin";
9279
+ }
9280
+ getCustomCode(shaderType, shaderLanguage) {
9281
+ const terrainInfo = this.getCustomShaderMaterial().getTerrainInfo();
9282
+ if (shaderType === "vertex") {
9283
+ if (shaderLanguage === ShaderLanguage.WGSL) {
9284
+ if (this.WGSL_CustomVertex == null)
9285
+ this.WGSL_CustomVertex = this.WGSL_FormatTerrainVertexDefintions(terrainInfo);
9286
+ if (this.WGSL_VertexMainEnd == null)
9287
+ this.WGSL_VertexMainEnd = this.WGSL_FormatTerrainVertexMainEnd(terrainInfo);
9288
+ return {
9289
+ CUSTOM_VERTEX_DEFINITIONS: this.WGSL_CustomVertex,
9290
+ CUSTOM_VERTEX_MAIN_END: this.WGSL_VertexMainEnd,
9291
+ };
9292
+ }
9293
+ else if (shaderLanguage === ShaderLanguage.GLSL) {
9294
+ if (this.GLSL_CustomVertex == null)
9295
+ this.GLSL_CustomVertex = this.GLSL_FormatTerrainVertexDefintions(terrainInfo);
9296
+ if (this.GLSL_VertexMainEnd == null)
9297
+ this.GLSL_VertexMainEnd = this.GLSL_FormatTerrainVertexMainEnd(terrainInfo);
9298
+ return {
9299
+ CUSTOM_VERTEX_DEFINITIONS: this.GLSL_CustomVertex,
9300
+ CUSTOM_VERTEX_MAIN_END: this.GLSL_VertexMainEnd,
9301
+ };
9302
+ }
9303
+ }
9304
+ else if (shaderType === "fragment") {
9305
+ if (shaderLanguage === ShaderLanguage.WGSL) {
9306
+ if (this.WGSL_CustomFragment == null)
9307
+ this.WGSL_CustomFragment = this.WGSL_FormatTerrainFragmentDefintions(terrainInfo, this.splatmapSampler, this.detailsSampler, this.normalsSampler);
9308
+ if (this.WGSL_FragmentUpdateColor == null)
9309
+ this.WGSL_FragmentUpdateColor = this.WGSL_FormatTerrainFragmentUpdateColor(terrainInfo, this.colorName, this.splatmapSampler, this.detailsSampler, this.normalsSampler, SceneManager.TerrainColorCorrection);
9310
+ return {
9311
+ CUSTOM_FRAGMENT_DEFINITIONS: this.WGSL_CustomFragment,
9312
+ CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.WGSL_FragmentUpdateColor,
9313
+ };
9314
+ }
9315
+ else if (shaderLanguage === ShaderLanguage.GLSL) {
9316
+ if (this.GLSL_CustomFragment == null)
9317
+ this.GLSL_CustomFragment = this.GLSL_FormatTerrainFragmentDefintions(terrainInfo, this.splatmapSampler, this.detailsSampler, this.normalsSampler);
9318
+ if (this.GLSL_FragmentUpdateColor == null)
9319
+ this.GLSL_FragmentUpdateColor = this.GLSL_FormatTerrainFragmentUpdateColor(terrainInfo, this.colorName, this.splatmapSampler, this.detailsSampler, this.normalsSampler, SceneManager.TerrainColorCorrection);
9320
+ return {
9321
+ CUSTOM_FRAGMENT_DEFINITIONS: this.GLSL_CustomFragment,
9322
+ CUSTOM_FRAGMENT_BEFORE_LIGHTS: this.GLSL_FragmentUpdateColor,
9323
+ };
9324
+ }
9325
+ }
9326
+ return null;
9327
+ }
9328
+ getUniforms(shaderLanguage) {
9329
+ const wgsl = (shaderLanguage === ShaderLanguage.WGSL);
9330
+ this.vertexDefinitions = this.getCustomShaderMaterial().getCustomVertexCode(wgsl);
9331
+ this.fragmentDefinitions = (wgsl === true) ? this.getCustomShaderMaterial().getCustomFragmentCode(wgsl) : null;
9332
+ return this.getCustomShaderMaterial().getCustomUniforms(wgsl);
9333
+ }
9334
+ getSamplers(samplers) {
9335
+ const customSamplers = this.getCustomShaderMaterial().getCustomSamplers();
9336
+ if (customSamplers != null && customSamplers.length > 0)
9337
+ samplers.push(...customSamplers);
9338
+ }
9339
+ getAttributes(attributes, scene, mesh) {
9340
+ const customAttributes = this.getCustomShaderMaterial().getCustomAttributes();
9341
+ if (customAttributes != null && customAttributes.length > 0)
9342
+ attributes.push(...customAttributes);
9343
+ }
9344
+ prepareDefines(defines, scene, mesh) {
9345
+ if (!this.getIsEnabled())
9346
+ return;
9347
+ this.getCustomShaderMaterial().prepareCustomDefines(defines);
9348
+ }
9349
+ bindForSubMesh(uniformBuffer, scene, engine, subMesh) {
9350
+ if (!this.getIsEnabled())
9351
+ return;
9352
+ this.getCustomShaderMaterial().updateCustomBindings(uniformBuffer);
9353
+ }
9354
+ WGSL_FormatTerrainVertexDefintions(terrainInfo) {
9355
+ let result = "";
9356
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9357
+ result = ("\r\n#ifndef TERRAIN_VERTEX_DEFINITIONS\r\n\r\n"
9358
+ + "#define TERRAIN_VERTEX_DEFINITIONS\r\n\r\n"
9359
+ + "varying vSplatmapUV: vec2<f32>;\r\n"
9360
+ + "\r\n"
9361
+ + "#endif\r\n\r\n");
9362
+ }
9363
+ return result;
9364
+ }
9365
+ WGSL_FormatTerrainVertexMainEnd(terrainInfo) {
9366
+ let result = "";
9367
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9368
+ result = ("\r\n#ifndef TERRAIN_VERTEX_MAIN_END\r\n\r\n"
9369
+ + "#define TERRAIN_VERTEX_MAIN_END\r\n\r\n"
9370
+ + "#ifdef UV1\r\n"
9371
+ + "vertexOutputs.vSplatmapUV = uvUpdated;\r\n"
9372
+ + "#endif\r\n"
9373
+ + "\r\n"
9374
+ + "#endif\r\n\r\n");
9375
+ }
9376
+ return result;
9377
+ }
9378
+ WGSL_FormatTerrainFragmentDefintions(terrainInfo, splatmapSampler, detailsSampler, normalsSampler) {
9379
+ let result = "";
9380
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9381
+ result = ("\r\n#ifndef TERRAIN_FRAGMENT_DEFNITIONS\r\n\r\n"
9382
+ + "#define TERRAIN_FRAGMENT_DEFNITIONS\r\n\r\n"
9383
+ + "varying vSplatmapUV: vec2<f32>;\r\n"
9384
+ + "\r\n"
9385
+ + "fn srgb_to_linear(c: vec3<f32>) -> vec3<f32>\r\n"
9386
+ + "{\r\n"
9387
+ + " return pow(c, vec3<f32>(2.2));\r\n"
9388
+ + "}\r\n"
9389
+ + "\r\n"
9390
+ + "fn linear_to_srgb(c: vec3<f32>) -> vec3<f32>\r\n"
9391
+ + "{\r\n"
9392
+ + " return pow(c, vec3<f32>(1.0 / 2.2));\r\n"
9393
+ + "}\r\n"
9394
+ + "\r\n"
9395
+ + "fn calculateMipmapLevel(uvs: vec2<f32>, size: vec2<f32>) -> f32\r\n"
9396
+ + "{\r\n"
9397
+ + " let dx: vec2<f32> = dpdx(uvs * size.x);\r\n"
9398
+ + " let dy: vec2<f32> = dpdy(uvs * size.y);\r\n"
9399
+ + " let d: f32 = max(dot(dx, dx), dot(dy, dy));\r\n"
9400
+ + " return (0.4 * log2(d));\r\n"
9401
+ + "}\r\n"
9402
+ + "\r\n"
9403
+ + "fn sampleTextureAtlas2D(atlas: texture_2d<f32>, sampler: sampler, gamma: f32, tile: vec2<f32>, rect: vec4<f32>, uvs: vec2<f32>, lod: f32) -> vec4<f32>\r\n"
9404
+ + "{\r\n"
9405
+ + " var level: f32 = lod;\r\n"
9406
+ + " if (level < 0.0) {\r\n"
9407
+ + " level = clamp(calculateMipmapLevel(uvs, vec2(tile.x, tile.x)), 0.0, tile.y);\r\n"
9408
+ + " }\r\n"
9409
+ + " let size: f32 = pow(2.0, tile.y - level);\r\n"
9410
+ + " let sizex: f32 = size / rect.z;\r\n"
9411
+ + " let sizey: f32 = size / rect.w;\r\n"
9412
+ + " var uv: vec2<f32> = fract(uvs);\r\n"
9413
+ + " uv.x = uv.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x;\r\n"
9414
+ + " uv.y = uv.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y;\r\n"
9415
+ + " var color: vec4<f32> = textureSampleLevel(atlas, sampler, uv, level);\r\n"
9416
+ + " if (gamma != 1.0) {\r\n"
9417
+ + " color.r = pow(color.r, gamma);\r\n"
9418
+ + " color.g = pow(color.g, gamma);\r\n"
9419
+ + " color.b = pow(color.b, gamma);\r\n"
9420
+ + " }\r\n"
9421
+ + " return color;\r\n"
9422
+ + "}\r\n"
9423
+ + "\r\n"
9424
+ + "fn sampleSplatmapAtlas2D(atlas: texture_2d<f32>, sampler: sampler, tile: vec2<f32>, rect: vec4<f32>, uvs: vec2<f32>) -> vec4<f32>\r\n"
9425
+ + "{\r\n"
9426
+ + " let size: f32 = pow(2.0, tile.y);\r\n"
9427
+ + " let sizex: f32 = size / rect.z;\r\n"
9428
+ + " let sizey: f32 = size / rect.w;\r\n"
9429
+ + " var uv: vec2<f32> = uvs;\r\n"
9430
+ + " uv.x = uv.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x;\r\n"
9431
+ + " uv.y = uv.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y;\r\n"
9432
+ + " return textureSample(atlas, sampler, uv);\r\n"
9433
+ + "}\r\n"
9434
+ + "\r\n"
9435
+ + "fn blendSplatmapAtlasColors(splatmap: vec4<f32>, color1: vec4<f32>, color2: vec4<f32>, color3: vec4<f32>, color4: vec4<f32>, mixbuffer: vec3<f32>) -> vec3<f32>\r\n"
9436
+ + "{\r\n"
9437
+ + " let buffer1: vec3<f32> = mix(mixbuffer, color1.rgb, splatmap.r);\r\n"
9438
+ + " let buffer2: vec3<f32> = mix(buffer1, color2.rgb, splatmap.g);\r\n"
9439
+ + " let buffer3: vec3<f32> = mix(buffer2, color3.rgb, splatmap.b);\r\n"
9440
+ + " return mix(buffer3, color4.rgb, splatmap.a);\r\n"
9441
+ + "}\r\n"
9442
+ + "\r\n"
9443
+ + "fn perturbNormalSamplerColor(cotangentFrame: mat3x3<f32>, samplerColor: vec3<f32>, scale: f32) -> vec3<f32>\r\n"
9444
+ + "{\r\n"
9445
+ + " var map: vec3<f32> = samplerColor.xyz;\r\n"
9446
+ + " map = map * 2.00787402 - 1.00787402;\r\n"
9447
+ + " #ifdef NORMALXYSCALE\r\n"
9448
+ + " map = normalize(map * vec3<f32>(scale, scale, 1.0));\r\n"
9449
+ + " #endif\r\n"
9450
+ + " return normalize(cotangentFrame * map);\r\n"
9451
+ + "}\r\n"
9452
+ + "#endif\r\n\r\n");
9453
+ }
9454
+ return result;
9455
+ }
9456
+ WGSL_FormatTerrainFragmentUpdateColor(terrainInfo, colorName, splatmapSampler, detailsSampler, normalsSampler, colorCorrection = 1.0) {
9457
+ let result = "";
9458
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9459
+ result = ("\r\n#ifndef TERRAIN_FRAGMENT_UPDATE_COLOR\r\n\r\n"
9460
+ + "#define TERRAIN_FRAGMENT_UPDATE_COLOR\r\n\r\n"
9461
+ + "var normalsColor: vec3<f32> = vec3<f32>(0.5, 0.5, 1.0);\r\n"
9462
+ + "var normalsBuffer: vec3<f32> = normalW.rgb;\r\n"
9463
+ + "var splatmapBuffer: vec3<f32> = " + colorName + ".rgb;\r\n"
9464
+ + "var autoMipMapLevel: f32 = -1.0;\r\n"
9465
+ + "var normalCorrection: f32 = 1.0;\r\n"
9466
+ + "var detailCorrection: f32 = " + colorCorrection.toFixed(4) + ";\r\n"
9467
+ + "\r\n"
9468
+ + "#if defined(ALBEDO) && defined(" + splatmapSampler.toUpperCase() + ") && defined(" + detailsSampler.toUpperCase() + ")\r\n"
9469
+ + "\r\n"
9470
+ + "// Reset Normal Values\r\n"
9471
+ + "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n"
9472
+ + " uvOffset = vec2<f32>(0.0, 0.0);\r\n"
9473
+ + " #ifdef NORMAL\r\n"
9474
+ + " normalW = normalize(input.vNormalW);\r\n"
9475
+ + " #else\r\n"
9476
+ + " normalW = normalize(cross(dpdx(input.vPositionW), dpdy(input.vPositionW))) * scene.vEyePosition.w;\r\n"
9477
+ + " #endif\r\n"
9478
+ + " #ifdef CLEARCOAT\r\n"
9479
+ + " clearCoatNormalW = normalW;\r\n"
9480
+ + " #endif\r\n"
9481
+ + " #if defined(BUMP) || defined(PARALLAX)\r\n"
9482
+ + " #if defined(CLEARCOAT_BUMP) && defined(TANGENT) && defined(NORMAL)\r\n"
9483
+ + " TBN = vTBN;\r\n"
9484
+ + " #else\r\n"
9485
+ + " TBN = cotangent_frame(normalW, input.vPositionW, fragmentInputs.vSplatmapUV, vec2<f32>(1.0, 1.0));\r\n"
9486
+ + " #endif\r\n"
9487
+ + " #elif defined(ANISOTROPIC)\r\n"
9488
+ + " #if defined(CLEARCOAT_BUMP) && defined(TANGENT) && defined(NORMAL)\r\n"
9489
+ + " TBN = vTBN;\r\n"
9490
+ + " #else\r\n"
9491
+ + " TBN = cotangent_frame(normalW, input.vPositionW, fragmentInputs.vSplatmapUV, vec2<f32>(1.0, 1.0));\r\n"
9492
+ + " #endif\r\n"
9493
+ + " #endif\r\n"
9494
+ + " #ifdef PARALLAX\r\n"
9495
+ + " invTBN = transposeMat3(TBN);\r\n"
9496
+ + " #endif\r\n"
9497
+ + " normalW = perturbNormalSamplerColor(TBN, normalsColor, 1.0);\r\n"
9498
+ + "#endif\r\n"
9499
+ + "\r\n"
9500
+ + "// Global Atlas Values\r\n"
9501
+ + "let splatTileSize: f32 = " + terrainInfo.splatmapAtlas[2].toFixed(4) + ";\r\n"
9502
+ + "let splatTileBits: f32 = " + terrainInfo.splatmapAtlas[3].toFixed(4) + ";\r\n"
9503
+ + "let detailTileSize: f32 = " + terrainInfo.textureAtlas[2].toFixed(4) + ";\r\n"
9504
+ + "let detailTileBits: f32 = " + terrainInfo.textureAtlas[3].toFixed(4) + ";\r\n"
9505
+ + "\r\n"
9506
+ + "// Sample splatmap textures\r\n");
9507
+ if (terrainInfo.splatmapCount > 0) {
9508
+ let counter = 0;
9509
+ result += "normalsBuffer = vec3<f32>(0.0,0.0,0.0);\r\n";
9510
+ for (let index = 0; index < terrainInfo.splatmapCount; index++) {
9511
+ counter = (index * 4);
9512
+ const splatmapRect = terrainInfo["splatmapRect" + index];
9513
+ result += "var splatmapRect" + index + ": vec4<f32> = vec4<f32>(" + splatmapRect[0].toFixed(4) + ", " + splatmapRect[1].toFixed(4) + ", " + splatmapRect[2].toFixed(4) + ", " + splatmapRect[3].toFixed(4) + ");\r\n";
9514
+ result += "var splatmapAlbedo" + index + ": vec4<f32> = sampleSplatmapAtlas2D(" + splatmapSampler + ", " + splatmapSampler + "Sampler, vec2<f32>(splatTileSize, splatTileBits), splatmapRect" + index + ", (fragmentInputs.vSplatmapUV + uvOffset));\r\n";
9515
+ result += "var textureAlbedo" + (counter + 0) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9516
+ result += "var textureAlbedo" + (counter + 1) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9517
+ result += "var textureAlbedo" + (counter + 2) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9518
+ result += "var textureAlbedo" + (counter + 3) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9519
+ if (terrainInfo["textureRect" + (counter + 0)]) {
9520
+ const textureRect = terrainInfo["textureRect" + (counter + 0)];
9521
+ const textureInfo = terrainInfo["textureInfo" + (counter + 0)];
9522
+ result += "var textureRect" + (counter + 0) + ": vec4<f32> = vec4<f32>(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9523
+ result += "var textureScale" + (counter + 0) + ": vec2<f32> = vec2<f32>(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9524
+ result += "var textureOffset" + (counter + 0) + ": vec2<f32> = vec2<f32>(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9525
+ result += "var textureTileUV" + (counter + 0) + ": vec2<f32> = ((fragmentInputs.vSplatmapUV + textureOffset" + (counter + 0) + ") * textureScale" + (counter + 0) + ");\r\n";
9526
+ result += "textureAlbedo" + (counter + 0) + " = sampleTextureAtlas2D(" + detailsSampler + ", " + detailsSampler + "Sampler, detailCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 0) + ", textureTileUV" + (counter + 0) + ", autoMipMapLevel);\r\n";
9527
+ }
9528
+ if (terrainInfo["textureRect" + (counter + 1)]) {
9529
+ const textureRect = terrainInfo["textureRect" + (counter + 1)];
9530
+ const textureInfo = terrainInfo["textureInfo" + (counter + 1)];
9531
+ result += "var textureRect" + (counter + 1) + ": vec4<f32> = vec4<f32>(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9532
+ result += "var textureScale" + (counter + 1) + ": vec2<f32> = vec2<f32>(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9533
+ result += "var textureOffset" + (counter + 1) + ": vec2<f32> = vec2<f32>(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9534
+ result += "var textureTileUV" + (counter + 1) + ": vec2<f32> = ((fragmentInputs.vSplatmapUV + textureOffset" + (counter + 1) + ") * textureScale" + (counter + 1) + ");\r\n";
9535
+ result += "textureAlbedo" + (counter + 1) + " = sampleTextureAtlas2D(" + detailsSampler + ", " + detailsSampler + "Sampler, detailCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 1) + ", textureTileUV" + (counter + 1) + ", autoMipMapLevel);\r\n";
9536
+ }
9537
+ if (terrainInfo["textureRect" + (counter + 2)]) {
9538
+ const textureRect = terrainInfo["textureRect" + (counter + 2)];
9539
+ const textureInfo = terrainInfo["textureInfo" + (counter + 2)];
9540
+ result += "var textureRect" + (counter + 2) + ": vec4<f32> = vec4<f32>(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9541
+ result += "var textureScale" + (counter + 2) + ": vec2<f32> = vec2<f32>(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9542
+ result += "var textureOffset" + (counter + 2) + ": vec2<f32> = vec2<f32>(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9543
+ result += "var textureTileUV" + (counter + 2) + ": vec2<f32> = ((fragmentInputs.vSplatmapUV + textureOffset" + (counter + 2) + ") * textureScale" + (counter + 2) + ");\r\n";
9544
+ result += "textureAlbedo" + (counter + 2) + " = sampleTextureAtlas2D(" + detailsSampler + ", " + detailsSampler + "Sampler, detailCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 2) + ", textureTileUV" + (counter + 2) + ", autoMipMapLevel);\r\n";
9545
+ }
9546
+ if (terrainInfo["textureRect" + (counter + 3)]) {
9547
+ const textureRect = terrainInfo["textureRect" + (counter + 3)];
9548
+ const textureInfo = terrainInfo["textureInfo" + (counter + 3)];
9549
+ result += "var textureRect" + (counter + 3) + ": vec4<f32> = vec4<f32>(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9550
+ result += "var textureScale" + (counter + 3) + ": vec2<f32> = vec2<f32>(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9551
+ result += "var textureOffset" + (counter + 3) + ": vec2<f32> = vec2<f32>(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9552
+ result += "var textureTileUV" + (counter + 3) + ": vec2<f32> = ((fragmentInputs.vSplatmapUV + textureOffset" + (counter + 3) + ") * textureScale" + (counter + 3) + ");\r\n";
9553
+ result += "textureAlbedo" + (counter + 3) + " = sampleTextureAtlas2D(" + detailsSampler + ", " + detailsSampler + "Sampler, detailCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 3) + ", textureTileUV" + (counter + 3) + ", autoMipMapLevel);\r\n";
9554
+ }
9555
+ result += "splatmapBuffer = blendSplatmapAtlasColors(splatmapAlbedo" + index + ", textureAlbedo" + (counter + 0) + ", textureAlbedo" + (counter + 1) + ", textureAlbedo" + (counter + 2) + ", textureAlbedo" + (counter + 3) + ", splatmapBuffer);\r\n";
9556
+ result += "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n";
9557
+ result += " #if defined(" + normalsSampler.toUpperCase() + ")\r\n";
9558
+ result += " var normalColor" + (counter + 0) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9559
+ result += " var normalColor" + (counter + 1) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9560
+ result += " var normalColor" + (counter + 2) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9561
+ result += " var normalColor" + (counter + 3) + ": vec4<f32> = vec4<f32>(0.0, 0.0, 0.0, 1.0);\r\n";
9562
+ if (terrainInfo["textureRect" + (counter + 0)]) {
9563
+ const normalScale = terrainInfo["normalsScale" + (counter + 0)];
9564
+ result += " var normalScale" + (counter + 0) + ": f32 = " + normalScale.toFixed(4) + ";\r\n";
9565
+ result += " normalColor" + (counter + 0) + " = sampleTextureAtlas2D(" + normalsSampler + ", " + normalsSampler + "Sampler, normalCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 0) + ", textureTileUV" + (counter + 0) + ", autoMipMapLevel);\r\n";
9566
+ result += " normalColor" + (counter + 0) + " = vec4<f32>(perturbNormalSamplerColor(TBN, normalColor" + (counter + 0) + ".rgb, normalScale" + (counter + 0) + "), 1.0);\r\n";
9567
+ }
9568
+ if (terrainInfo["textureRect" + (counter + 1)]) {
9569
+ const normalScale = terrainInfo["normalsScale" + (counter + 1)];
9570
+ result += " var normalScale" + (counter + 1) + ": f32 = " + normalScale.toFixed(4) + ";\r\n";
9571
+ result += " normalColor" + (counter + 1) + " = sampleTextureAtlas2D(" + normalsSampler + ", " + normalsSampler + "Sampler, normalCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 1) + ", textureTileUV" + (counter + 1) + ", autoMipMapLevel);\r\n";
9572
+ result += " normalColor" + (counter + 1) + " = vec4<f32>(perturbNormalSamplerColor(TBN, normalColor" + (counter + 1) + ".rgb, normalScale" + (counter + 1) + "), 1.0);\r\n";
9573
+ }
9574
+ if (terrainInfo["textureRect" + (counter + 2)]) {
9575
+ const normalScale = terrainInfo["normalsScale" + (counter + 2)];
9576
+ result += " var normalScale" + (counter + 2) + ": f32 = " + normalScale.toFixed(4) + ";\r\n";
9577
+ result += " normalColor" + (counter + 2) + " = sampleTextureAtlas2D(" + normalsSampler + ", " + normalsSampler + "Sampler, normalCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 2) + ", textureTileUV" + (counter + 2) + ", autoMipMapLevel);\r\n";
9578
+ result += " normalColor" + (counter + 2) + " = vec4<f32>(perturbNormalSamplerColor(TBN, normalColor" + (counter + 2) + ".rgb, normalScale" + (counter + 2) + "), 1.0);\r\n";
9579
+ }
9580
+ if (terrainInfo["textureRect" + (counter + 3)]) {
9581
+ const normalScale = terrainInfo["normalsScale" + (counter + 3)];
9582
+ result += " var normalScale" + (counter + 3) + ": f32 = " + normalScale.toFixed(4) + ";\r\n";
9583
+ result += " normalColor" + (counter + 3) + " = sampleTextureAtlas2D(" + normalsSampler + ", " + normalsSampler + "Sampler, normalCorrection, vec2<f32>(detailTileSize, detailTileBits), textureRect" + (counter + 3) + ", textureTileUV" + (counter + 3) + ", autoMipMapLevel);\r\n";
9584
+ result += " normalColor" + (counter + 3) + " = vec4<f32>(perturbNormalSamplerColor(TBN, normalColor" + (counter + 3) + ".rgb, normalScale" + (counter + 3) + "), 1.0);\r\n";
9585
+ }
9586
+ result += " normalsBuffer = blendSplatmapAtlasColors(splatmapAlbedo" + index + ", normalColor" + (counter + 0) + ", normalColor" + (counter + 1) + ", normalColor" + (counter + 2) + ", normalColor" + (counter + 3) + ", normalsBuffer);\r\n";
9587
+ result += " #endif\r\n";
9588
+ result += "#endif\r\n";
9589
+ result += "\r\n";
9590
+ }
9591
+ }
9592
+ result += ("// Update Color Values\r\n"
9593
+ + colorName + " = splatmapBuffer.rgb;\r\n"
9594
+ + "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n"
9595
+ + " #if defined(" + normalsSampler.toUpperCase() + ")\r\n"
9596
+ + " normalW = normalsBuffer.rgb;\r\n"
9597
+ + " #endif\r\n"
9598
+ + " #if defined(FORCENORMALFORWARD) && defined(NORMAL)\r\n"
9599
+ + " var faceNormal: vec3<f32> = normalize(cross(dpdx(input.vPositionW), dpdy(input.vPositionW))) * scene.vEyePosition.w;\r\n"
9600
+ + " #if defined(TWOSIDEDLIGHTING)\r\n"
9601
+ + " faceNormal = select(-faceNormal, faceNormal, fragmentInputs.frontFacing)\r\n"
9602
+ + " #endif\r\n"
9603
+ + " normalW *= sign(dot(normalW, faceNormal));\r\n"
9604
+ + " #endif\r\n"
9605
+ + " #if defined(TWOSIDEDLIGHTING) && defined(NORMAL)\r\n"
9606
+ + " normalW = select(-normalW, normalW, fragmentInputs.frontFacing);\r\n"
9607
+ + " #endif\r\n"
9608
+ + "#endif\r\n"
9609
+ + "\r\n"
9610
+ + "#endif\r\n"
9611
+ + "\r\n"
9612
+ + "#endif\r\n\r\n");
9613
+ }
9614
+ return result;
9615
+ }
9616
+ GLSL_FormatTerrainVertexDefintions(terrainInfo) {
9617
+ let result = "";
9618
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9619
+ result = ("\r\n#ifndef TERRAIN_VERTEX_DEFINITIONS\r\n\r\n"
9620
+ + "#define TERRAIN_VERTEX_DEFINITIONS\r\n\r\n"
9621
+ + "varying vec2 vSplatmapUV;\r\n"
9622
+ + "\r\n"
9623
+ + "#endif\r\n\r\n");
9624
+ }
9625
+ return result;
9626
+ }
9627
+ GLSL_FormatTerrainVertexMainEnd(terrainInfo) {
9628
+ let result = "";
9629
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9630
+ result = ("\r\n#ifndef TERRAIN_VERTEX_MAIN_END\r\n\r\n"
9631
+ + "#define TERRAIN_VERTEX_MAIN_END\r\n\r\n"
9632
+ + "#ifdef UV1\r\n"
9633
+ + "vSplatmapUV = uv;\r\n"
9634
+ + "#endif\r\n"
9635
+ + "\r\n"
9636
+ + "#endif\r\n\r\n");
9637
+ }
9638
+ return result;
9639
+ }
9640
+ GLSL_FormatTerrainFragmentDefintions(terrainInfo, splatmapSampler, detailsSampler, normalsSampler) {
9641
+ let result = "";
9642
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9643
+ result = ("\r\n#ifndef TERRAIN_FRAGMENT_DEFNITIONS\r\n\r\n"
9644
+ + "#define TERRAIN_FRAGMENT_DEFNITIONS\r\n\r\n"
9645
+ + "varying vec2 vSplatmapUV;\r\n"
9646
+ + "\r\n"
9647
+ + "vec3 srgb_to_linear(const in vec3 c)\r\n"
9648
+ + "{\r\n"
9649
+ + " return pow(c, vec3(2.2));\r\n"
9650
+ + "}\r\n"
9651
+ + "\r\n"
9652
+ + "vec3 linear_to_srgb(const in vec3 c)\r\n"
9653
+ + "{\r\n"
9654
+ + " return pow(c, vec3(1.0 / 2.2));\r\n"
9655
+ + "}\r\n"
9656
+ + "\r\n"
9657
+ + "float calculateMipmapLevel(const in vec2 uvs, const in vec2 size)\r\n"
9658
+ + "{\r\n"
9659
+ + " vec2 dx = dFdx(uvs * size.x);\r\n"
9660
+ + " vec2 dy = dFdy(uvs * size.y);\r\n"
9661
+ + " float d = max(dot(dx, dx), dot(dy, dy));\r\n"
9662
+ + " return 0.4 * log2(d);\r\n"
9663
+ + "}\r\n"
9664
+ + "\r\n"
9665
+ + "vec4 sampleTextureAtlas2D(const in sampler2D atlas, const in float gamma, const in vec2 tile, const in vec4 rect, in vec2 uvs, in float lod)\r\n"
9666
+ + "{\r\n"
9667
+ + " if (lod < 0.0) lod = clamp(calculateMipmapLevel(uvs, vec2(tile.x, tile.x)), 0.0, tile.y); // Tile Info (tile.xy)\r\n"
9668
+ + " float size = pow(2.0, tile.y - lod); // Tile Bits (tile.y)\r\n"
9669
+ + " float sizex = size / rect.z; // Tile Width (rect.z)\r\n"
9670
+ + " float sizey = size / rect.w; // Tile Height (rect.w)\r\n"
9671
+ + " uvs = fract(uvs); // Perfrom Tiling (fract)\r\n"
9672
+ + " uvs.x = uvs.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x; // Tile Position X (rect.x)\r\n"
9673
+ + " uvs.y = uvs.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y; // Tile Position Y (rect.y)\r\n"
9674
+ + " vec4 color = texture2DLodEXT(atlas, uvs, lod);\r\n"
9675
+ + " if (gamma != 1.0) {\r\n"
9676
+ + " color.r = pow(color.r, gamma);\r\n"
9677
+ + " color.g = pow(color.g, gamma);\r\n"
9678
+ + " color.b = pow(color.b, gamma);\r\n"
9679
+ + " }\r\n"
9680
+ + " return color;\r\n"
9681
+ + "}\r\n"
9682
+ + "\r\n"
9683
+ + "vec4 sampleSplatmapAtlas2D(const in sampler2D atlas, const in vec2 tile, const in vec4 rect, in vec2 uvs)\r\n"
9684
+ + "{\r\n"
9685
+ + " float size = pow(2.0, tile.y); // Tile Bits (tile.y)\r\n"
9686
+ + " float sizex = size / rect.z; // Tile Width (rect.z)\r\n"
9687
+ + " float sizey = size / rect.w; // Tile Height (rect.w)\r\n"
9688
+ + " uvs.x = uvs.x * ((sizex * rect.z - 1.0) / sizex) + 0.5 / sizex + rect.z * rect.x; // Tile Position X (rect.x)\r\n"
9689
+ + " uvs.y = uvs.y * ((sizey * rect.w - 1.0) / sizey) + 0.5 / sizey + rect.w * rect.y; // Tile Position Y (rect.y)\r\n"
9690
+ + " return texture2D(atlas, uvs);\r\n"
9691
+ + "}\r\n"
9692
+ + "\r\n"
9693
+ + "vec3 blendSplatmapAtlasColors(const in vec4 splatmap, in vec4 color1, in vec4 color2, in vec4 color3, in vec4 color4, in vec3 mixbuffer)\r\n"
9694
+ + "{\r\n"
9695
+ + " vec3 buffer1 = mix(mixbuffer, color1.rgb, splatmap.r);\r\n"
9696
+ + " vec3 buffer2 = mix(buffer1, color2.rgb, splatmap.g);\r\n"
9697
+ + " vec3 buffer3 = mix(buffer2, color3.rgb, splatmap.b);\r\n"
9698
+ + " return mix(buffer3, color4.rgb, splatmap.a);\r\n"
9699
+ + "}\r\n"
9700
+ + "\r\n"
9701
+ + "vec3 perturbNormalSamplerColor(mat3 cotangentFrame, vec3 samplerColor, float scale)\r\n"
9702
+ + "{\r\n"
9703
+ + " vec3 map = samplerColor.xyz;\r\n"
9704
+ + " map = map * 2.00787402 - 1.00787402;\r\n"
9705
+ + " #ifdef NORMALXYSCALE\r\n"
9706
+ + " map = normalize(map * vec3(scale, scale, 1.0));\r\n"
9707
+ + " #endif\r\n"
9708
+ + " return normalize(cotangentFrame * map);\r\n"
9709
+ + "}\r\n"
9710
+ + "\r\n"
9711
+ + "\r\n"
9712
+ + "#endif\r\n\r\n");
9713
+ }
9714
+ return result;
9715
+ }
9716
+ GLSL_FormatTerrainFragmentUpdateColor(terrainInfo, colorName, splatmapSampler, detailsSampler, normalsSampler, colorCorrection = 1.0) {
9717
+ let result = "";
9718
+ if (terrainInfo != null && terrainInfo.textureAtlas != null && terrainInfo.splatmapAtlas != null && terrainInfo.splatmapCount > 0) {
9719
+ result = ("\r\n#ifndef TERRAIN_FRAGMENT_UPDATE_COLOR\r\n\r\n"
9720
+ + "#define TERRAIN_FRAGMENT_UPDATE_COLOR\r\n\r\n"
9721
+ + "vec3 normalsColor = vec3(0.5, 0.5, 1.0);\r\n"
9722
+ + "vec3 normalsBuffer = normalW.rgb;\r\n"
9723
+ + "vec3 splatmapBuffer = " + colorName + ".rgb;\r\n"
9724
+ + "float autoMipMapLevel = -1.0;\r\n"
9725
+ + "float normalCorrection = 1.0;\r\n"
9726
+ + "float detailCorrection = " + colorCorrection.toFixed(4) + ";\r\n"
9727
+ + "\r\n"
9728
+ + "#if defined(ALBEDO) && defined(" + splatmapSampler.toUpperCase() + ") && defined(" + detailsSampler.toUpperCase() + ")\r\n"
9729
+ + "\r\n"
9730
+ + "// Reset Normal Values\r\n"
9731
+ + "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n"
9732
+ + " uvOffset = vec2(0.0, 0.0);\r\n"
9733
+ + " #ifdef NORMAL\r\n"
9734
+ + " normalW = normalize(vNormalW);\r\n"
9735
+ + " #else\r\n"
9736
+ + " normalW = normalize(cross(dFdx(vPositionW), dFdy(vPositionW))) * vEyePosition.w;\r\n"
9737
+ + " #endif\r\n"
9738
+ + " #ifdef CLEARCOAT\r\n"
9739
+ + " clearCoatNormalW = normalW;\r\n"
9740
+ + " #endif\r\n"
9741
+ + " #if defined(BUMP) || defined(PARALLAX)\r\n"
9742
+ + " #if defined(TANGENT) && defined(NORMAL)\r\n"
9743
+ + " TBN = vTBN;\r\n"
9744
+ + " #else\r\n"
9745
+ + " TBN = cotangent_frame(normalW, vPositionW, vSplatmapUV);\r\n"
9746
+ + " #endif\r\n"
9747
+ + " #elif defined(ANISOTROPIC)\r\n"
9748
+ + " #if defined(TANGENT) && defined(NORMAL)\r\n"
9749
+ + " TBN = vTBN;\r\n"
9750
+ + " #else\r\n"
9751
+ + " TBN = cotangent_frame(normalW, vPositionW, vSplatmapUV, vec2(1.0, 1.0));\r\n"
9752
+ + " #endif\r\n"
9753
+ + " #endif\r\n"
9754
+ + " #ifdef PARALLAX\r\n"
9755
+ + " invTBN = transposeMat3(TBN);\r\n"
9756
+ + " #endif\r\n"
9757
+ + " normalW = perturbNormalSamplerColor(TBN, normalsColor, 1.0);\r\n"
9758
+ + "#endif\r\n"
9759
+ + "\r\n"
9760
+ + "// Global Atlas Values\r\n"
9761
+ + "float splatTileSize = " + terrainInfo.splatmapAtlas[2].toFixed(4) + ";\r\n"
9762
+ + "float splatTileBits = " + terrainInfo.splatmapAtlas[3].toFixed(4) + ";\r\n"
9763
+ + "float detailTileSize = " + terrainInfo.textureAtlas[2].toFixed(4) + ";\r\n"
9764
+ + "float detailTileBits = " + terrainInfo.textureAtlas[3].toFixed(4) + ";\r\n"
9765
+ + "\r\n"
9766
+ + "// Sample splatmap textures\r\n");
9767
+ if (terrainInfo.splatmapCount > 0) {
9768
+ let counter = 0;
9769
+ result += "normalsBuffer = vec3(0.0,0.0,0.0);\r\n";
9770
+ for (let index = 0; index < terrainInfo.splatmapCount; index++) {
9771
+ counter = (index * 4);
9772
+ const splatmapRect = terrainInfo["splatmapRect" + index];
9773
+ result += "vec4 splatmapRect" + index + " = vec4(" + splatmapRect[0].toFixed(4) + ", " + splatmapRect[1].toFixed(4) + ", " + splatmapRect[2].toFixed(4) + ", " + splatmapRect[3].toFixed(4) + ");\r\n";
9774
+ result += "vec4 splatmapAlbedo" + index + " = sampleSplatmapAtlas2D(" + splatmapSampler + ", vec2(splatTileSize, splatTileBits), splatmapRect" + index + ", (vSplatmapUV + uvOffset));\r\n";
9775
+ result += "vec4 textureAlbedo" + (counter + 0) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9776
+ result += "vec4 textureAlbedo" + (counter + 1) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9777
+ result += "vec4 textureAlbedo" + (counter + 2) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9778
+ result += "vec4 textureAlbedo" + (counter + 3) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9779
+ if (terrainInfo["textureRect" + (counter + 0)]) {
9780
+ const textureRect = terrainInfo["textureRect" + (counter + 0)];
9781
+ const textureInfo = terrainInfo["textureInfo" + (counter + 0)];
9782
+ result += "vec4 textureRect" + (counter + 0) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9783
+ result += "vec2 textureScale" + (counter + 0) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9784
+ result += "vec2 textureOffset" + (counter + 0) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9785
+ result += "vec2 textureTileUV" + (counter + 0) + " = ((vSplatmapUV + textureOffset" + (counter + 0) + ") * textureScale" + (counter + 0) + ");\r\n";
9786
+ result += "textureAlbedo" + (counter + 0) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 0) + ", textureTileUV" + (counter + 0) + ", autoMipMapLevel);\r\n";
9787
+ }
9788
+ if (terrainInfo["textureRect" + (counter + 1)]) {
9789
+ const textureRect = terrainInfo["textureRect" + (counter + 1)];
9790
+ const textureInfo = terrainInfo["textureInfo" + (counter + 1)];
9791
+ result += "vec4 textureRect" + (counter + 1) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9792
+ result += "vec2 textureScale" + (counter + 1) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9793
+ result += "vec2 textureOffset" + (counter + 1) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9794
+ result += "vec2 textureTileUV" + (counter + 1) + " = ((vSplatmapUV + textureOffset" + (counter + 1) + ") * textureScale" + (counter + 1) + ");\r\n";
9795
+ result += "textureAlbedo" + (counter + 1) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 1) + ", textureTileUV" + (counter + 1) + ", autoMipMapLevel);\r\n";
9796
+ }
9797
+ if (terrainInfo["textureRect" + (counter + 2)]) {
9798
+ const textureRect = terrainInfo["textureRect" + (counter + 2)];
9799
+ const textureInfo = terrainInfo["textureInfo" + (counter + 2)];
9800
+ result += "vec4 textureRect" + (counter + 2) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9801
+ result += "vec2 textureScale" + (counter + 2) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9802
+ result += "vec2 textureOffset" + (counter + 2) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9803
+ result += "vec2 textureTileUV" + (counter + 2) + " = ((vSplatmapUV + textureOffset" + (counter + 2) + ") * textureScale" + (counter + 2) + ");\r\n";
9804
+ result += "textureAlbedo" + (counter + 2) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 2) + ", textureTileUV" + (counter + 2) + ", autoMipMapLevel);\r\n";
9805
+ }
9806
+ if (terrainInfo["textureRect" + (counter + 3)]) {
9807
+ const textureRect = terrainInfo["textureRect" + (counter + 3)];
9808
+ const textureInfo = terrainInfo["textureInfo" + (counter + 3)];
9809
+ result += "vec4 textureRect" + (counter + 3) + " = vec4(" + textureRect[0].toFixed(4) + ", " + textureRect[1].toFixed(4) + ", " + textureRect[2].toFixed(4) + ", " + textureRect[3].toFixed(4) + ");\r\n";
9810
+ result += "vec2 textureScale" + (counter + 3) + " = vec2(" + textureInfo[0].toFixed(4) + ", " + textureInfo[1].toFixed(4) + ");\r\n";
9811
+ result += "vec2 textureOffset" + (counter + 3) + " = vec2(" + textureInfo[2].toFixed(4) + ", " + textureInfo[3].toFixed(4) + ");\r\n";
9812
+ result += "vec2 textureTileUV" + (counter + 3) + " = ((vSplatmapUV + textureOffset" + (counter + 3) + ") * textureScale" + (counter + 3) + ");\r\n";
9813
+ result += "textureAlbedo" + (counter + 3) + " = sampleTextureAtlas2D(" + detailsSampler + ", detailCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 3) + ", textureTileUV" + (counter + 3) + ", autoMipMapLevel);\r\n";
9814
+ }
9815
+ result += "splatmapBuffer = blendSplatmapAtlasColors(splatmapAlbedo" + index + ", textureAlbedo" + (counter + 0) + ", textureAlbedo" + (counter + 1) + ", textureAlbedo" + (counter + 2) + ", textureAlbedo" + (counter + 3) + ", splatmapBuffer);\r\n";
9816
+ result += "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n";
9817
+ result += " #if defined(" + normalsSampler.toUpperCase() + ")\r\n";
9818
+ result += " vec4 normalColor" + (counter + 0) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9819
+ result += " vec4 normalColor" + (counter + 1) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9820
+ result += " vec4 normalColor" + (counter + 2) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9821
+ result += " vec4 normalColor" + (counter + 3) + " = vec4(0.0, 0.0, 0.0, 1.0);\r\n";
9822
+ if (terrainInfo["textureRect" + (counter + 0)]) {
9823
+ const normalScale = terrainInfo["normalsScale" + (counter + 0)];
9824
+ result += " float normalScale" + (counter + 0) + " = " + normalScale.toFixed(4) + ";\r\n";
9825
+ result += " normalColor" + (counter + 0) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 0) + ", textureTileUV" + (counter + 0) + ", autoMipMapLevel);\r\n";
9826
+ result += " normalColor" + (counter + 0) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 0) + ".rgb, normalScale" + (counter + 0) + ");\r\n";
9827
+ }
9828
+ if (terrainInfo["textureRect" + (counter + 1)]) {
9829
+ const normalScale = terrainInfo["normalsScale" + (counter + 1)];
9830
+ result += " float normalScale" + (counter + 1) + " = " + normalScale.toFixed(4) + ";\r\n";
9831
+ result += " normalColor" + (counter + 1) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 1) + ", textureTileUV" + (counter + 1) + ", autoMipMapLevel);\r\n";
9832
+ result += " normalColor" + (counter + 1) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 1) + ".rgb, normalScale" + (counter + 1) + ");\r\n";
9833
+ }
9834
+ if (terrainInfo["textureRect" + (counter + 2)]) {
9835
+ const normalScale = terrainInfo["normalsScale" + (counter + 2)];
9836
+ result += " float normalScale" + (counter + 2) + " = " + normalScale.toFixed(4) + ";\r\n";
9837
+ result += " normalColor" + (counter + 2) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 2) + ", textureTileUV" + (counter + 2) + ", autoMipMapLevel);\r\n";
9838
+ result += " normalColor" + (counter + 2) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 2) + ".rgb, normalScale" + (counter + 2) + ");\r\n";
9839
+ }
9840
+ if (terrainInfo["textureRect" + (counter + 3)]) {
9841
+ const normalScale = terrainInfo["normalsScale" + (counter + 3)];
9842
+ result += " float normalScale" + (counter + 3) + " = " + normalScale.toFixed(4) + ";\r\n";
9843
+ result += " normalColor" + (counter + 3) + " = sampleTextureAtlas2D(" + normalsSampler + ", normalCorrection, vec2(detailTileSize, detailTileBits), textureRect" + (counter + 3) + ", textureTileUV" + (counter + 3) + ", autoMipMapLevel);\r\n";
9844
+ result += " normalColor" + (counter + 3) + ".rgb = perturbNormalSamplerColor(TBN, normalColor" + (counter + 3) + ".rgb, normalScale" + (counter + 3) + ");\r\n";
9845
+ }
9846
+ result += " normalsBuffer = blendSplatmapAtlasColors(splatmapAlbedo" + index + ", normalColor" + (counter + 0) + ", normalColor" + (counter + 1) + ", normalColor" + (counter + 2) + ", normalColor" + (counter + 3) + ", normalsBuffer);\r\n";
9847
+ result += " #endif\r\n";
9848
+ result += "#endif\r\n";
9849
+ result += "\r\n";
9850
+ }
9851
+ }
9852
+ result += ("// Update Color Values\r\n"
9853
+ + colorName + " = splatmapBuffer.rgb;\r\n"
9854
+ + "#if defined(BUMP) || defined(PARALLAX) || defined(ANISOTROPIC)\r\n"
9855
+ + " #if defined(" + normalsSampler.toUpperCase() + ")\r\n"
9856
+ + " normalW = normalsBuffer.rgb;\r\n"
9857
+ + " #endif\r\n"
9858
+ + " #if defined(FORCENORMALFORWARD) && defined(NORMAL)\r\n"
9859
+ + " vec3 faceNormal = normalize(cross(dFdx(vPositionW), dFdy(vPositionW))) * vEyePosition.w;\r\n"
9860
+ + " #if defined(TWOSIDEDLIGHTING)\r\n"
9861
+ + " faceNormal = gl_FrontFacing ? faceNormal : -faceNormal;\r\n"
9862
+ + " #endif\r\n"
9863
+ + " normalW *= sign(dot(normalW, faceNormal));\r\n"
9864
+ + " #endif\r\n"
9865
+ + " #if defined(TWOSIDEDLIGHTING) && defined(NORMAL)\r\n"
9866
+ + " normalW = gl_FrontFacing ? normalW : -normalW;\r\n"
9867
+ + " #endif\r\n"
9868
+ + "#endif\r\n"
9869
+ + "\r\n"
9870
+ + "#endif\r\n"
9871
+ + "\r\n"
9872
+ + "#endif\r\n\r\n");
9873
+ }
9874
+ return result;
9875
+ }
9876
+ }
9266
9877
  export class GrassStandardMaterial extends StandardShaderMaterial {
9267
9878
  constructor(name, scene) {
9268
9879
  super(name, scene);
@@ -10365,6 +10976,372 @@ export class GrassBillboardMaterialPlugin extends StandardShaderMaterialPlugin {
10365
10976
  this.getCustomShaderMaterial().updateCustomBindings(uniformBuffer);
10366
10977
  }
10367
10978
  }
10979
+ export class TreeBranchMaterial extends CustomShaderMaterial {
10980
+ constructor(name, scene) {
10981
+ super(name, scene);
10982
+ this._windTimeAccum = 0.0;
10983
+ this.shader = this.getShaderName();
10984
+ this.plugin = new TreeBranchMaterialPlugin(this, this.shader);
10985
+ this.addFloatUniform("g_windTime", 0.0);
10986
+ this.addFloatUniform("g_windAmount", 0.5);
10987
+ this.addFloatUniform("g_windSpeed", 1.0);
10988
+ this.addFloatUniform("g_windStrength", 0.5);
10989
+ this.addVector4Uniform("g_windDirection", new Vector4(1.0, 0.0, 0.0, 0.0));
10990
+ this.addFloatUniform("g_branchRootY", 0.0);
10991
+ this.addFloatUniform("g_branchMaxAngle", 0.35);
10992
+ this.addFloatUniform("g_branchMaxHeight", 2.0);
10993
+ this.addFloatUniform("g_useVertexColorMask", 1.0);
10994
+ this.addFloatUniform("g_spatialFrequency", 0.15);
10995
+ this._windTimeAccum = 0.0;
10996
+ }
10997
+ awake() {
10998
+ }
10999
+ update() {
11000
+ try {
11001
+ const rawDt = SceneManager.GetDeltaSeconds(this.getScene());
11002
+ const dt = Math.min(rawDt, 1 / 30);
11003
+ const speed = this.getFloatValue("g_windSpeed") ?? 0;
11004
+ if (speed > 0) {
11005
+ this._windTimeAccum += dt;
11006
+ this.setFloatValue("g_windTime", this._windTimeAccum);
11007
+ }
11008
+ }
11009
+ catch (e) {
11010
+ }
11011
+ }
11012
+ getShaderName() {
11013
+ return "TreeBranchMaterial";
11014
+ }
11015
+ setWindDirection(x, y, z) {
11016
+ const v = new Vector3(x, y, z);
11017
+ if (v.lengthSquared() < 1e-8) {
11018
+ this.setVector4Value("g_windDirection", new Vector4(1, 0, 0, 0));
11019
+ return;
11020
+ }
11021
+ v.normalize();
11022
+ this.setVector4Value("g_windDirection", new Vector4(v.x, v.y, v.z, 0.0));
11023
+ }
11024
+ getWindDirection() {
11025
+ return this.getVector4Value("g_windDirection");
11026
+ }
11027
+ }
11028
+ export class TreeBranchMaterialPlugin extends CustomShaderMaterialPlugin {
11029
+ constructor(customMaterial, shaderName) {
11030
+ super(customMaterial, shaderName, 110, { TREEBRANCHMATERIAL: false });
11031
+ }
11032
+ isCompatible(shaderLanguage) {
11033
+ return (shaderLanguage === ShaderLanguage.WGSL || shaderLanguage === ShaderLanguage.GLSL);
11034
+ }
11035
+ getCustomCode(shaderType, shaderLanguage) {
11036
+ if (shaderType === "vertex") {
11037
+ if (shaderLanguage === ShaderLanguage.WGSL) {
11038
+ return {
11039
+ CUSTOM_VERTEX_MAIN_END: `
11040
+ // -------------------------
11041
+ // TreeBranch wind bending (local-space)
11042
+ // Authoring (recommended):
11043
+ // vertexColor.r = bend weight (0 trunk .. 1 tips)
11044
+ // vertexColor.g = phase offset noise (0..1)
11045
+ // Falls back to height-based weight if vertex color missing/disabled.
11046
+ // -------------------------
11047
+
11048
+ fn safeNormalize(v: vec3f) -> vec3f {
11049
+ let lsq = dot(v, v);
11050
+ if (lsq < 1e-8) { return vec3f(1.0, 0.0, 0.0); }
11051
+ return v * inverseSqrt(lsq);
11052
+ }
11053
+
11054
+ fn rotateRodrigues(p: vec3f, axis: vec3f, angle: f32) -> vec3f {
11055
+ let a = safeNormalize(axis);
11056
+ let c = cos(angle);
11057
+ let s = sin(angle);
11058
+ return p * c + cross(a, p) * s + a * dot(a, p) * (1.0 - c);
11059
+ }
11060
+
11061
+ // Local position & normal (preferred: use normalUpdated if present in shader)
11062
+ var pL: vec3f = positionUpdated;
11063
+
11064
+ // If COLOR attribute exists, Babylon usually provides vertexInputs.color.
11065
+ // If not available, these will be 1,1,1,1 (depending on pipeline) — we guard with g_useVertexColorMask.
11066
+ var bendMask: f32 = 1.0;
11067
+ var phaseNoise: f32 = 0.0;
11068
+ if (uniforms.g_useVertexColorMask > 0.5) {
11069
+ bendMask = clamp(vertexInputs.color.r, 0.0, 1.0);
11070
+ phaseNoise = vertexInputs.color.g; // 0..1
11071
+ }
11072
+
11073
+ // Height-based fallback weight (normalized between rootY..maxHeight)
11074
+ let denom = max(uniforms.g_branchMaxHeight - uniforms.g_branchRootY, 1e-3);
11075
+ let h01 = clamp((pL.y - uniforms.g_branchRootY) / denom, 0.0, 1.0);
11076
+
11077
+ // Final per-vertex weight:
11078
+ // - If using vertex colors: bendMask * smooth tip emphasis
11079
+ // - Else: height-based
11080
+ var w: f32 = h01;
11081
+ if (uniforms.g_useVertexColorMask > 0.5) {
11082
+ w = bendMask * (h01 * h01 * (3.0 - 2.0 * h01)); // smoothstep(h01)
11083
+ } else {
11084
+ w = h01 * h01; // a bit stiffer near base
11085
+ }
11086
+
11087
+ // Wind direction in LOCAL space:
11088
+ // We transform windDirWorld by inverse world matrix rotation (approx using finalWorld columns).
11089
+ // finalWorld transforms local->world. For direction, inverse rotation is transpose of 3x3 if no shear.
11090
+ let windW = safeNormalize(uniforms.g_windDirection.xyz);
11091
+ let rightW = safeNormalize(finalWorld[0].xyz);
11092
+ let upW = safeNormalize(finalWorld[1].xyz);
11093
+ let fwdW = safeNormalize(finalWorld[2].xyz);
11094
+
11095
+ // Convert world dir to local by dot with basis
11096
+ let windL = safeNormalize(vec3f(dot(windW, rightW), dot(windW, upW), dot(windW, fwdW)));
11097
+
11098
+ // Bend axis: perpendicular to up and wind, in LOCAL space
11099
+ var axisL = safeNormalize(cross(vec3f(0.0, 1.0, 0.0), windL));
11100
+ if (dot(axisL, axisL) < 1e-6) { axisL = vec3f(1.0, 0.0, 0.0); }
11101
+
11102
+ // Spatially varying phase so whole tree doesn't move as a rigid sheet
11103
+ // Use local xz for coherence; optionally mod by vertexColor.g
11104
+ let spatial = (pL.x + pL.z) * uniforms.g_spatialFrequency;
11105
+ let phase = uniforms.g_windTime * uniforms.g_windSpeed
11106
+ + spatial * uniforms.g_windStrength
11107
+ + phaseNoise * 6.28318;
11108
+
11109
+ // Two-wave composite like Unity-ish wind
11110
+ let base = sin(phase);
11111
+ let gust = 0.5 * sin(phase * 1.7 + 1.2);
11112
+ let sway = (base + gust) * uniforms.g_windAmount;
11113
+
11114
+ // Convert sway to angle (clamped)
11115
+ var angle = sway * w * uniforms.g_branchMaxAngle;
11116
+ angle = clamp(angle, -uniforms.g_branchMaxAngle, uniforms.g_branchMaxAngle);
11117
+
11118
+ // Pivot in local space
11119
+ let pivotL = vec3f(0.0, uniforms.g_branchRootY, 0.0);
11120
+ let relL = pL - pivotL;
11121
+ let bentRelL = rotateRodrigues(relL, axisL, angle);
11122
+ let bentPL = pivotL + bentRelL;
11123
+
11124
+ // Transform to world using finalWorld (keeps proper anchoring)
11125
+ let branchWorld = (finalWorld * vec4f(bentPL, 1.0)).xyz;
11126
+
11127
+ // Rotate normal similarly (only if NORMAL is active)
11128
+ #ifdef NORMAL
11129
+ // Babylon WGSL PBR usually provides normalUpdated in local/object space
11130
+ var nL: vec3f = normalize(normalUpdated);
11131
+ let bentNL = rotateRodrigues(nL, axisL, angle);
11132
+ // Convert to world by world 3x3
11133
+ let nW = normalize(vec3f(
11134
+ dot(bentNL, vec3f(finalWorld[0].x, finalWorld[0].y, finalWorld[0].z)),
11135
+ dot(bentNL, vec3f(finalWorld[1].x, finalWorld[1].y, finalWorld[1].z)),
11136
+ dot(bentNL, vec3f(finalWorld[2].x, finalWorld[2].y, finalWorld[2].z))
11137
+ ));
11138
+ vertexOutputs.vNormalW = nW;
11139
+ #endif
11140
+
11141
+ #if defined(POSITION) || defined(BUMP)
11142
+ vertexOutputs.vPositionW = branchWorld;
11143
+ #endif
11144
+
11145
+ vertexOutputs.position = scene.viewProjection * vec4f(branchWorld, 1.0);
11146
+ `
11147
+ };
11148
+ }
11149
+ else {
11150
+ return {
11151
+ CUSTOM_VERTEX_DEFINITIONS: `
11152
+ uniform float g_windTime;
11153
+ uniform float g_windAmount;
11154
+ uniform float g_windSpeed;
11155
+ uniform float g_windStrength;
11156
+ uniform vec4 g_windDirection;
11157
+
11158
+ uniform float g_branchRootY;
11159
+ uniform float g_branchMaxAngle;
11160
+ uniform float g_branchMaxHeight;
11161
+ uniform float g_useVertexColorMask;
11162
+ uniform float g_spatialFrequency;
11163
+
11164
+ vec3 safeNormalize(vec3 v) {
11165
+ float lsq = dot(v, v);
11166
+ if (lsq < 1e-8) return vec3(1.0, 0.0, 0.0);
11167
+ return v * inversesqrt(lsq);
11168
+ }
11169
+
11170
+ vec3 rotateRodrigues(vec3 p, vec3 axis, float angle) {
11171
+ vec3 a = safeNormalize(axis);
11172
+ float c = cos(angle);
11173
+ float s = sin(angle);
11174
+ return p * c + cross(a, p) * s + a * dot(a, p) * (1.0 - c);
11175
+ }
11176
+ `,
11177
+ CUSTOM_VERTEX_MAIN_END: `
11178
+ // Local/object position
11179
+ vec3 pL = positionUpdated;
11180
+
11181
+ // Vertex color mask (recommended authoring)
11182
+ float bendMask = 1.0;
11183
+ float phaseNoise = 0.0;
11184
+ #ifdef VERTEXCOLOR
11185
+ if (g_useVertexColorMask > 0.5) {
11186
+ bendMask = clamp(color.r, 0.0, 1.0);
11187
+ phaseNoise = color.g;
11188
+ }
11189
+ #endif
11190
+
11191
+ float denom = max(g_branchMaxHeight - g_branchRootY, 1e-3);
11192
+ float h01 = clamp((pL.y - g_branchRootY) / denom, 0.0, 1.0);
11193
+
11194
+ float w = h01;
11195
+ if (g_useVertexColorMask > 0.5) {
11196
+ // smoothstep(h01)
11197
+ w = bendMask * (h01 * h01 * (3.0 - 2.0 * h01));
11198
+ } else {
11199
+ w = h01 * h01;
11200
+ }
11201
+
11202
+ // Convert wind world dir to local using world basis (transpose approx)
11203
+ vec3 windW = safeNormalize(g_windDirection.xyz);
11204
+ vec3 rightW = safeNormalize(finalWorld[0].xyz);
11205
+ vec3 upW = safeNormalize(finalWorld[1].xyz);
11206
+ vec3 fwdW = safeNormalize(finalWorld[2].xyz);
11207
+ vec3 windL = safeNormalize(vec3(dot(windW, rightW), dot(windW, upW), dot(windW, fwdW)));
11208
+
11209
+ vec3 axisL = safeNormalize(cross(vec3(0.0, 1.0, 0.0), windL));
11210
+ if (dot(axisL, axisL) < 1e-6) axisL = vec3(1.0, 0.0, 0.0);
11211
+
11212
+ float spatial = (pL.x + pL.z) * g_spatialFrequency;
11213
+ float phase = g_windTime * g_windSpeed
11214
+ + spatial * g_windStrength
11215
+ + phaseNoise * 6.2831853;
11216
+
11217
+ float base = sin(phase);
11218
+ float gust = 0.5 * sin(phase * 1.7 + 1.2);
11219
+ float sway = (base + gust) * g_windAmount;
11220
+
11221
+ float angle = clamp(sway * w * g_branchMaxAngle, -g_branchMaxAngle, g_branchMaxAngle);
11222
+
11223
+ vec3 pivotL = vec3(0.0, g_branchRootY, 0.0);
11224
+ vec3 relL = pL - pivotL;
11225
+ vec3 bentPL = pivotL + rotateRodrigues(relL, axisL, angle);
11226
+
11227
+ vec3 branchWorld = (finalWorld * vec4(bentPL, 1.0)).xyz;
11228
+
11229
+ #if defined(POSITION) || defined(BUMP)
11230
+ vPositionW = branchWorld;
11231
+ #endif
11232
+
11233
+ #ifdef NORMAL
11234
+ // normalUpdated is local/object normal
11235
+ vec3 nL = normalize(normalUpdated);
11236
+ vec3 bentNL = rotateRodrigues(nL, axisL, angle);
11237
+
11238
+ // world normal from world basis (approx; assumes no non-uniform scale/shear)
11239
+ vec3 nW = normalize(bentNL.x * rightW + bentNL.y * upW + bentNL.z * fwdW);
11240
+ vNormalW = nW;
11241
+ #endif
11242
+
11243
+ gl_Position = viewProjection * vec4(branchWorld, 1.0);
11244
+ `
11245
+ };
11246
+ }
11247
+ }
11248
+ if (shaderType === "fragment") {
11249
+ if (shaderLanguage === ShaderLanguage.WGSL) {
11250
+ return {
11251
+ CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION: `
11252
+ // no-op
11253
+ `
11254
+ };
11255
+ }
11256
+ else {
11257
+ return {
11258
+ CUSTOM_FRAGMENT_BEFORE_FINALCOLORCOMPOSITION: `
11259
+ // no-op
11260
+ `
11261
+ };
11262
+ }
11263
+ }
11264
+ return null;
11265
+ }
11266
+ getUniforms(shaderLanguage) {
11267
+ const wgsl = (shaderLanguage === ShaderLanguage.WGSL);
11268
+ this.vertexDefinitions = this.getCustomShaderMaterial().getCustomVertexCode(wgsl);
11269
+ this.fragmentDefinitions = (wgsl === true) ? this.getCustomShaderMaterial().getCustomFragmentCode(wgsl) : null;
11270
+ return this.getCustomShaderMaterial().getCustomUniforms(wgsl);
11271
+ }
11272
+ getSamplers(samplers) {
11273
+ const customSamplers = this.getCustomShaderMaterial().getCustomSamplers();
11274
+ if (customSamplers && customSamplers.length > 0)
11275
+ samplers.push(...customSamplers);
11276
+ }
11277
+ getAttributes(attributes, scene, mesh) {
11278
+ const customAttributes = this.getCustomShaderMaterial().getCustomAttributes();
11279
+ if (customAttributes && customAttributes.length > 0)
11280
+ attributes.push(...customAttributes);
11281
+ if (attributes.indexOf("color") === -1)
11282
+ attributes.push("color");
11283
+ }
11284
+ prepareDefines(defines, scene, mesh) {
11285
+ if (!this.getIsEnabled())
11286
+ return;
11287
+ this.getCustomShaderMaterial().prepareCustomDefines(defines);
11288
+ }
11289
+ bindForSubMesh(uniformBuffer, scene, engine, subMesh) {
11290
+ if (!this.getIsEnabled())
11291
+ return;
11292
+ this.getCustomShaderMaterial().updateCustomBindings(uniformBuffer);
11293
+ }
11294
+ static ExtractWindZoneOverride(properties, terrainTransform, builderInstance) {
11295
+ const candidates = [];
11296
+ if (properties)
11297
+ candidates.push(properties);
11298
+ if (properties && properties.properties)
11299
+ candidates.push(properties.properties);
11300
+ const meta = terrainTransform?.metadata;
11301
+ if (meta)
11302
+ candidates.push(meta);
11303
+ if (meta && meta.toolkit)
11304
+ candidates.push(meta.toolkit);
11305
+ if (meta && meta.properties)
11306
+ candidates.push(meta.properties);
11307
+ const bmeta = builderInstance?.metadata;
11308
+ if (bmeta)
11309
+ candidates.push(bmeta);
11310
+ if (bmeta && bmeta.toolkit)
11311
+ candidates.push(bmeta.toolkit);
11312
+ const tmeta = builderInstance?.transform?.metadata;
11313
+ if (tmeta)
11314
+ candidates.push(tmeta);
11315
+ if (tmeta && tmeta.toolkit)
11316
+ candidates.push(tmeta.toolkit);
11317
+ let zones = null;
11318
+ for (const c of candidates) {
11319
+ if (!c)
11320
+ continue;
11321
+ if (Array.isArray(c.windzones)) {
11322
+ zones = c.windzones;
11323
+ break;
11324
+ }
11325
+ }
11326
+ if (!zones || zones.length === 0)
11327
+ return null;
11328
+ let best = null;
11329
+ let bestScore = -1e9;
11330
+ for (const z of zones) {
11331
+ if (!z)
11332
+ continue;
11333
+ const t = ((z.type != null) ? z.type : (z.mode != null ? z.mode : "")).toString().toLowerCase();
11334
+ const isDirectional = (t.indexOf("direction") >= 0);
11335
+ const main = (z.windMain != null) ? z.windMain : 0.0;
11336
+ const score = (isDirectional ? 1000.0 : 0.0) + main;
11337
+ if (score > bestScore) {
11338
+ bestScore = score;
11339
+ best = z;
11340
+ }
11341
+ }
11342
+ return best;
11343
+ }
11344
+ }
10368
11345
  export class VertexAnimationMaterial extends CustomShaderMaterial {
10369
11346
  constructor(name, scene) {
10370
11347
  super(name, scene);
@@ -15070,7 +16047,7 @@ export class WindowManager {
15070
16047
  printer.style.left = "6px";
15071
16048
  printer.style.bottom = "3px";
15072
16049
  printer.style.fontSize = "12px";
15073
- printer.style.zIndex = "10000";
16050
+ printer.style.zIndex = "9001";
15074
16051
  printer.style.color = "#0c0";
15075
16052
  printer.style.pointerEvents = "none";
15076
16053
  document.body.appendChild(printer);
@@ -15845,9 +16822,6 @@ export class AnimationState extends ScriptComponent {
15845
16822
  if (this.onAnimationAwakeObservable && this.onAnimationAwakeObservable.hasObservers()) {
15846
16823
  this.onAnimationAwakeObservable.notifyObservers(this.transform);
15847
16824
  }
15848
- console.warn("Animation State Mahine: " + this.transform.name);
15849
- console.log(this);
15850
- SceneManager.SetWindowState(this.transform.name, this);
15851
16825
  }
15852
16826
  updateStateMachine(deltaTime = null) {
15853
16827
  if (this.delayUpdateUntilReady === false || (this.delayUpdateUntilReady === true && this.isReady())) {
@@ -18797,44 +19771,6 @@ export class btRaycastVehicle {
18797
19771
  this.isArcadeHandBrakeActive = false;
18798
19772
  this.isArcadeWheelSkidActive = false;
18799
19773
  this.isArcadeYawAssistActive = false;
18800
- this.arcadeYawAssistDebugLogEnabled = false;
18801
- this.arcadeYawAssistDebugLogIntervalFrames = 30;
18802
- this.arcadeYawAssistDebugLogEdgeEvents = true;
18803
- this.arcadeHandbrakeKickStrengthDegPerSec = 120.0;
18804
- this.arcadeHandbrakeKickFrames = 6;
18805
- this.arcadeHandbrakeYawAuthority = 1.8;
18806
- this.arcadeHandbrakeMaxYawRateDegPerSec = 180.0;
18807
- this.arcadeHandbrakeReferenceSpeedKmh = 90.0;
18808
- this.arcadeHandbrakeSpeedGateEnabled = true;
18809
- this.arcadeHandbrakeLowSpeedShape = 2.0;
18810
- this.arcadeHandbrakeDirectYawEnabled = true;
18811
- this.arcadeHandbrakeDirectYawDegPerSec = 60.0;
18812
- this.arcadeHandbrakeDirectYawDurationMs = 0;
18813
- this.arcadeHandbrakeDirectYawFadeMs = 200;
18814
- this.arcadeDonutDirectYawEnabled = true;
18815
- this.arcadeDonutDirectYawDegPerSec = 5.0;
18816
- this.arcadeDonutDirectYawDurationMs = 0;
18817
- this.arcadeDonutDirectYawFadeMs = 200;
18818
- this.arcadeHandbrakeMaxSlideAngleDeg = 220.0;
18819
- this.arcadeHandbrakeCounterSteerClampEnabled = true;
18820
- this.arcadeHandbrakeCounterSteerYawThresholdDegPerSec = 10.0;
18821
- this.arcadeHandbrakeClampReleaseFadeMs = 250;
18822
- this.arcadeHandbrakeSteerSlewLimitEnabled = false;
18823
- this.arcadeHandbrakeSteerSlewLimitDegPerSec = 360.0;
18824
- this._wasArcadeHandBrakeActive = false;
18825
- this._wasArcadeYawAssistApplyingForce = false;
18826
- this._handbrakeKickJzRemaining = 0;
18827
- this._handbrakeKickFramesRemaining = 0;
18828
- this._arcadeHandbrakeLatchedDriveSign = 0;
18829
- this._arcadeHandbrakeHoldElapsedSec = 0;
18830
- this._arcadeHandbrakeSlewedSteerRad = 0;
18831
- this._arcadeHandbrakeClampReleaseFadeSec = 999;
18832
- this._arcadeDonutHoldElapsedSec = 0;
18833
- this._arcadeYawAssistDebugFrameCounter = 0;
18834
- this._arcadeYawAssistLastKickRad = 0;
18835
- this._arcadeYawAssistLastIaddPerWheel = 0;
18836
- this._arcadeYawAssistLastClampScalar = 1.0;
18837
- this._arcadeYawAssistLastLeverSum = 0;
18838
19774
  this.burnoutFrictionFloor = 1.0;
18839
19775
  this.frictionRestoreSpeed = 8.0;
18840
19776
  this.arcadeBurnoutWheelSpinGain = 1.0;
@@ -18847,16 +19783,20 @@ export class btRaycastVehicle {
18847
19783
  this.arcadeWheelSpinMaxAngularVelocity = 160.0;
18848
19784
  this.arcadeStationaryBurnoutWheelSpinGain = 3.25;
18849
19785
  this.arcadeStationaryBurnoutMinAngularVelocity = 65.0;
19786
+ this.arcadeHandbrakeYawCapMultiplier = 1.85;
19787
+ this.arcadeBurnoutYawCapMultiplier = 1.25;
19788
+ this.arcadeDonutYawCapMultiplier = 1.15;
18850
19789
  this.arcadeSkidFadeInSpeed = 18.0;
18851
19790
  this.arcadeSkidFadeOutSpeed = 6.0;
18852
- this.arcadeYawCapMultiplier = 1.5;
18853
19791
  this.wheelAtRestSpeedThresholdKmh = 1.0;
18854
- this.wheelSpinDebugLogEnabled = false;
18855
- this.wheelSpinDebugLogIntervalFrames = 6;
18856
- this._wheelSpinDebugLogCounter = 0;
18857
- this._postHandbrakeLogFrames = 0;
18858
- this._postHandbrakeLogCounter = 0;
18859
- this._wasAnyArcadeModeActive = false;
19792
+ this.arcadeHandbrakeAssistEnabled = true;
19793
+ this.currentSteeringInput = 0;
19794
+ this.arcadeDonutDirectYawEnabled = true;
19795
+ this.arcadeDonutDirectYawDegPerSec = 60.0;
19796
+ this.arcadeDonutDirectYawDurationMs = 0;
19797
+ this.arcadeDonutDirectYawFadeMs = 120;
19798
+ this._arcadeDonutHoldElapsedSec = 0;
19799
+ this._arcadeDonutDirectionSign = 0;
18860
19800
  this._forwardWS = [];
18861
19801
  this._axle = [];
18862
19802
  this._forwardImpulse = [];
@@ -18864,6 +19804,11 @@ export class btRaycastVehicle {
18864
19804
  this._arcadeSkidInfo = [];
18865
19805
  this._arcadePreviousWheelSpin = [];
18866
19806
  this.sideFrictionStiffness = 1.0;
19807
+ this.arcadeSideSlipSaturationEnabled = true;
19808
+ this.arcadeSideSlipPeakDeg = 8.0;
19809
+ this.arcadeSideSlipFalloffDeg = 45.0;
19810
+ this.arcadeSideSlipFalloffFactor = 0.4;
19811
+ this.arcadeSideSlipMinSpeedMps = 1.5;
18867
19812
  this._chassisMass = 0;
18868
19813
  this._chassisInvMass = 0;
18869
19814
  this._chassisTransform = Matrix.Identity();
@@ -18988,6 +19933,9 @@ export class btRaycastVehicle {
18988
19933
  return this.isArcadeBurnoutModeActive;
18989
19934
  }
18990
19935
  setIsArcadeDonutActive(active) {
19936
+ if (active !== true) {
19937
+ this._arcadeDonutDirectionSign = 0;
19938
+ }
18991
19939
  this.isArcadeDonutModeActive = active;
18992
19940
  }
18993
19941
  getIsArcadeDonutActive() {
@@ -19608,29 +20556,6 @@ export class btRaycastVehicle {
19608
20556
  const chassisAtRest = (this.wheelAtRestSpeedThresholdKmh > 0
19609
20557
  && Math.abs(this._currentVehicleSpeedKmHour) < this.wheelAtRestSpeedThresholdKmh
19610
20558
  && (!arcadeModeActiveAny || !anyWheelEngineForceActive));
19611
- let wheelSpinLogThisTick = false;
19612
- if (this.wheelSpinDebugLogEnabled === true) {
19613
- this._wheelSpinDebugLogCounter++;
19614
- const intv = (this.wheelSpinDebugLogIntervalFrames > 0)
19615
- ? this.wheelSpinDebugLogIntervalFrames : 1;
19616
- if ((this._wheelSpinDebugLogCounter % intv) === 0) {
19617
- wheelSpinLogThisTick = true;
19618
- }
19619
- }
19620
- let chassisAngVelY = 0;
19621
- if (wheelSpinLogThisTick === true) {
19622
- this._chassisBody.getAngularVelocityToRef(this._sv8);
19623
- chassisAngVelY = this._sv8.y;
19624
- console.log("[HavokVehicle.wheelSpin] tick=" + this._wheelSpinDebugLogCounter
19625
- + " speedKmh=" + this._currentVehicleSpeedKmHour.toFixed(3)
19626
- + " yawRadPerSec=" + chassisAngVelY.toFixed(4)
19627
- + " atRest=" + chassisAtRest
19628
- + " arcadeAny=" + arcadeModeActiveAny
19629
- + " burn=" + this.isArcadeBurnoutModeActive
19630
- + " donut=" + this.isArcadeDonutModeActive
19631
- + " hand=" + this.isArcadeHandBrakeActive
19632
- + " skid=" + this.isArcadeWheelSkidActive);
19633
- }
19634
20559
  for (var i = 0; i < numWheels; i++) {
19635
20560
  var wheel = this._wheelInfo[i];
19636
20561
  var groundAngularVelocity = 0.0;
@@ -19646,29 +20571,12 @@ export class btRaycastVehicle {
19646
20571
  if (chassisAtRest === true) {
19647
20572
  wheel.rotationBoost = 0;
19648
20573
  wheel.deltaRotation = 0;
19649
- if (wheelSpinLogThisTick === true) {
19650
- console.log(" wheel[" + i + "] SUPPRESSED"
19651
- + " contact=" + wheel.raycastInfo.isInContact
19652
- + " groundAngVel=" + groundAngularVelocity.toFixed(4)
19653
- + " rotation=" + wheel.rotation.toFixed(4));
19654
- }
19655
20574
  continue;
19656
20575
  }
19657
20576
  var wheelAngularVelocity = this.getWheelAngularVelocity(i, groundAngularVelocity, step, wheel.raycastInfo.isInContact);
19658
20577
  wheel.deltaRotation = wheelAngularVelocity * step;
19659
20578
  wheel.rotation += wheel.deltaRotation;
19660
20579
  wheel.deltaRotation *= 0.99;
19661
- if (wheelSpinLogThisTick === true) {
19662
- console.log(" wheel[" + i + "]"
19663
- + " contact=" + wheel.raycastInfo.isInContact
19664
- + " groundAngVel=" + groundAngularVelocity.toFixed(4)
19665
- + " rotBoost=" + wheel.rotationBoost.toFixed(4)
19666
- + " wheelAngVel=" + wheelAngularVelocity.toFixed(4)
19667
- + " deltaRot=" + wheel.deltaRotation.toFixed(5)
19668
- + " rotation=" + wheel.rotation.toFixed(4)
19669
- + " engineForce=" + wheel.engineForce.toFixed(2)
19670
- + " brake=" + wheel.brake.toFixed(2));
19671
- }
19672
20580
  }
19673
20581
  this.clampChassisYawRate(step);
19674
20582
  }
@@ -19719,9 +20627,20 @@ export class btRaycastVehicle {
19719
20627
  }
19720
20628
  }
19721
20629
  }
20630
+ getSteeringAuthorityInput() {
20631
+ var steeringInput = Scalar.Clamp(this.currentSteeringInput, -1.0, 1.0);
20632
+ if (Math.abs(steeringInput) > 0.001)
20633
+ return steeringInput;
20634
+ var signedFrontSteer = this.getSignedFrontSteeringAngleRad();
20635
+ return Scalar.Clamp(signedFrontSteer / 0.6, -1.0, 1.0);
20636
+ }
20637
+ getSteeringMagnitude() {
20638
+ return Math.abs(this.getSteeringAuthorityInput());
20639
+ }
19722
20640
  applyEasyDonutYawAssist(timeStep, absSpeedKmh, signedSpeedKmh) {
19723
20641
  if (this.isArcadeDonutModeActive !== true) {
19724
20642
  this._arcadeDonutHoldElapsedSec = 0;
20643
+ this._arcadeDonutDirectionSign = 0;
19725
20644
  return;
19726
20645
  }
19727
20646
  if (this.arcadeDonutDirectYawEnabled !== true) {
@@ -19734,14 +20653,6 @@ export class btRaycastVehicle {
19734
20653
  this._arcadeDonutHoldElapsedSec += timeStep;
19735
20654
  return;
19736
20655
  }
19737
- var L = this.getApproxWheelbaseMeters();
19738
- if (L < 0.5)
19739
- L = 2.5;
19740
- var Iz = 0.22 * this._chassisMass * L * L;
19741
- if (Iz <= 0 || this._chassisMass <= 0) {
19742
- this._arcadeDonutHoldElapsedSec += timeStep;
19743
- return;
19744
- }
19745
20656
  var durSec = this.arcadeDonutDirectYawDurationMs > 0 ? this.arcadeDonutDirectYawDurationMs / 1000.0 : 0;
19746
20657
  var fadeSec = this.arcadeDonutDirectYawFadeMs > 0 ? this.arcadeDonutDirectYawFadeMs / 1000.0 : 0;
19747
20658
  var elapsed = this._arcadeDonutHoldElapsedSec;
@@ -19770,337 +20681,76 @@ export class btRaycastVehicle {
19770
20681
  chassisUp.set(cm[ui * 4], cm[ui * 4 + 1], cm[ui * 4 + 2]);
19771
20682
  this._chassisBody.getLinearVelocityToRef(this._sv8);
19772
20683
  var signedVxMps = Vector3.Dot(this._sv8, chassisForward);
19773
- var driveSign = Math.abs(signedVxMps) > 0.25 ? (signedVxMps >= 0 ? 1 : -1) : 1;
19774
20684
  var steerSign = signedFrontSteerRad >= 0 ? 1 : -1;
19775
- var extraRad = Tools.ToRadians(this.arcadeDonutDirectYawDegPerSec)
19776
- * driveSign * steerSign
19777
- * fade * this.arcadeSteeringAssist;
19778
- var donutYawJz = Iz * extraRad * timeStep;
19779
- if (Math.abs(donutYawJz) <= 1e-6) {
19780
- this._arcadeDonutHoldElapsedSec += timeStep;
19781
- return;
20685
+ if (this._arcadeDonutDirectionSign === 0) {
20686
+ this._arcadeDonutDirectionSign = Math.abs(signedVxMps) > 0.25 ? (signedVxMps >= 0 ? 1 : -1) : steerSign;
19782
20687
  }
19783
- this._chassisBody.getObjectCenterWorldToRef(this._sv1);
19784
- var leverSum = 0;
19785
- var rearIdxList = [];
19786
- var rearLeverList = [];
19787
- for (var w = 0; w < this._wheelInfo.length; w++) {
19788
- var wi = this._wheelInfo[w];
19789
- if (!wi.isFrontWheel && wi.raycastInfo.groundObject) {
19790
- wi.raycastInfo.contactPointWS.subtractToRef(this._sv1, this._sv2);
19791
- Vector3.CrossToRef(this._sv2, this._axle[w], this._sv3);
19792
- var lever_w = Vector3.Dot(this._sv3, chassisUp);
19793
- rearIdxList.push(w);
19794
- rearLeverList.push(lever_w);
19795
- leverSum += lever_w;
19796
- }
19797
- }
19798
- if (rearIdxList.length === 0 || Math.abs(leverSum) <= MIN_LEVER_EPS) {
20688
+ var targetYawRad = Tools.ToRadians(this.arcadeDonutDirectYawDegPerSec)
20689
+ * this._arcadeDonutDirectionSign * steerSign
20690
+ * fade * this.arcadeSteeringAssist;
20691
+ if (Math.abs(targetYawRad) <= 1e-5) {
19799
20692
  this._arcadeDonutHoldElapsedSec += timeStep;
19800
20693
  return;
19801
20694
  }
19802
- var IaddPerWheel = donutYawJz / leverSum;
19803
- for (var ii = 0; ii < rearIdxList.length; ii++) {
19804
- var w2 = rearIdxList[ii];
19805
- var wi2 = this._wheelInfo[w2];
19806
- this._axle[w2].scaleToRef(IaddPerWheel, this._sv2);
19807
- this._chassisBody.applyImpulse(this._sv2, wi2.raycastInfo.contactPointWS);
19808
- }
20695
+ this._chassisBody.getAngularVelocityToRef(this._sv9);
20696
+ var currentYawRad = Vector3.Dot(this._sv9, chassisUp);
20697
+ var lockRate = 14.0;
20698
+ var lockBlend = Scalar.Clamp(1.0 - Math.exp(-lockRate * timeStep), 0.0, 1.0);
20699
+ var nextYawRad = currentYawRad + (targetYawRad - currentYawRad) * lockBlend;
20700
+ var minHoldYawRad = Math.abs(targetYawRad) * 0.55;
20701
+ if (Math.sign(nextYawRad) !== Math.sign(targetYawRad) || Math.abs(nextYawRad) < minHoldYawRad) {
20702
+ nextYawRad = Math.sign(targetYawRad) * minHoldYawRad;
20703
+ }
20704
+ chassisUp.scaleToRef(currentYawRad, this._sv1);
20705
+ this._sv9.subtractToRef(this._sv1, this._sv2);
20706
+ chassisUp.scaleToRef(nextYawRad, this._sv3);
20707
+ this._sv2.addToRef(this._sv3, this._sv9);
20708
+ this._chassisBody.setAngularVelocity(this._sv9);
19809
20709
  this._arcadeDonutHoldElapsedSec += timeStep;
19810
20710
  }
19811
20711
  applyHandbrakeYawAssist(timeStep, absSpeedKmh, signedSpeedKmh) {
19812
20712
  this.isArcadeYawAssistActive = false;
19813
- if (this.isArcadeHandBrakeActive === false) {
19814
- this._wasArcadeHandBrakeActive = false;
19815
- this._wasArcadeYawAssistApplyingForce = false;
19816
- this._handbrakeKickJzRemaining = 0;
19817
- this._handbrakeKickFramesRemaining = 0;
19818
- this._arcadeHandbrakeLatchedDriveSign = 0;
19819
- this._arcadeHandbrakeHoldElapsedSec = 0;
19820
- this._arcadeHandbrakeSlewedSteerRad = 0;
19821
- this._arcadeHandbrakeClampReleaseFadeSec = 999;
20713
+ if (!this.arcadeHandbrakeAssistEnabled)
19822
20714
  return;
19823
- }
19824
- const TAU_RESPONSE = 0.18;
19825
- const V_ENGAGE_MIN_KMH = 7.0;
19826
- const V_ENGAGE_FULL_KMH = 65.0;
19827
- const V_KICK_FULL_KMH = 65.0;
19828
- const I_Z_FACTOR = 0.22;
19829
- const MIN_STEER_RAD = 0.02;
19830
- const MIN_LEVER_EPS = 0.001;
19831
- const ASSIST_MIN_NM = 25.0;
19832
- const SLIDE_TAPER_RANGE_RAD = Math.PI / 4;
19833
- const K_ASSIST = this.arcadeHandbrakeYawAuthority;
19834
- const R_MAX_DEG_PER_SEC = this.arcadeHandbrakeMaxYawRateDegPerSec;
19835
- const K_KICK_DEG_PER_SEC = this.arcadeHandbrakeKickStrengthDegPerSec;
19836
- const V_REF_MPS = this.arcadeHandbrakeReferenceSpeedKmh / 3.6;
19837
- const MAX_SLIDE_ANGLE_RAD = Tools.ToRadians(this.arcadeHandbrakeMaxSlideAngleDeg);
19838
- const KICK_FRAMES = Math.max(1, this.arcadeHandbrakeKickFrames | 0);
19839
- this._arcadeYawAssistDebugFrameCounter++;
19840
- var debugEnabled = this.arcadeYawAssistDebugLogEnabled === true;
19841
- var debugIntervalHit = debugEnabled && this.arcadeYawAssistDebugLogIntervalFrames > 0 && (this._arcadeYawAssistDebugFrameCounter % this.arcadeYawAssistDebugLogIntervalFrames) === 0;
19842
- var signedFrontSteerRad = this.getSignedFrontSteeringAngleRad();
19843
- var steeringMagnitude = Math.abs(signedFrontSteerRad);
19844
- var eligible = this.isArcadeHandBrakeActive === true && this.arcadeSteeringAssist > 0 && steeringMagnitude > MIN_STEER_RAD && absSpeedKmh > V_ENGAGE_MIN_KMH;
19845
- var risingEdge = this.isArcadeHandBrakeActive === true && this._wasArcadeHandBrakeActive === false;
19846
- if (this.arcadeHandbrakeSteerSlewLimitEnabled === true && !risingEdge) {
19847
- var slewMaxRad = Tools.ToRadians(this.arcadeHandbrakeSteerSlewLimitDegPerSec) * timeStep;
19848
- var steerDelta = signedFrontSteerRad - this._arcadeHandbrakeSlewedSteerRad;
19849
- if (steerDelta > slewMaxRad)
19850
- steerDelta = slewMaxRad;
19851
- else if (steerDelta < -slewMaxRad)
19852
- steerDelta = -slewMaxRad;
19853
- this._arcadeHandbrakeSlewedSteerRad += steerDelta;
19854
- }
19855
- else {
19856
- this._arcadeHandbrakeSlewedSteerRad = signedFrontSteerRad;
19857
- }
19858
- var effectiveSteerRad = this._arcadeHandbrakeSlewedSteerRad;
19859
- const cm = this._chassisTransform.m;
20715
+ var steeringMagnitude = this.getSteeringMagnitude();
20716
+ var steeringAuthorityInput = this.getSteeringAuthorityInput();
20717
+ if (!this.isArcadeHandBrakeActive || steeringMagnitude < 0.02)
20718
+ return;
20719
+ var speedMps = Math.max(0.0, absSpeedKmh / 3.6);
20720
+ var speedScale = Scalar.Clamp((speedMps - 0.5) / 3.0, 0.0, 1.0);
20721
+ if (speedScale <= 0.0)
20722
+ return;
20723
+ const m = this._chassisTransform.m;
20724
+ const ri = this._indexRightAxis;
19860
20725
  const ui = this._indexUpAxis;
19861
20726
  const fi = this._indexForwardAxis;
19862
- const ri = this._indexRightAxis;
19863
- const chassisUp = this._sv5;
19864
- const chassisForward = this._sv6;
19865
- const chassisRight = this._sv9;
19866
- chassisUp.set(cm[ui * 4], cm[ui * 4 + 1], cm[ui * 4 + 2]);
19867
- chassisForward.set(cm[fi * 4], cm[fi * 4 + 1], cm[fi * 4 + 2]);
19868
- chassisRight.set(cm[ri * 4], cm[ri * 4 + 1], cm[ri * 4 + 2]);
19869
- this._chassisBody.getLinearVelocityToRef(this._sv8);
19870
- var signedVxMps = Vector3.Dot(this._sv8, chassisForward);
19871
- var velRightMps = Vector3.Dot(this._sv8, chassisRight);
19872
- var speedMagMps = Math.sqrt(this._sv8.x * this._sv8.x + this._sv8.y * this._sv8.y + this._sv8.z * this._sv8.z);
19873
- var slipAngleRad = (speedMagMps > 0.1) ? Math.atan2(velRightMps, signedVxMps) : 0;
19874
- var driveSign = this._arcadeHandbrakeLatchedDriveSign !== 0
19875
- ? this._arcadeHandbrakeLatchedDriveSign
19876
- : (signedVxMps >= 0 ? 1 : -1);
19877
- var relSlipRad;
19878
- if (driveSign > 0) {
19879
- relSlipRad = slipAngleRad;
19880
- }
19881
- else {
19882
- relSlipRad = slipAngleRad >= 0 ? slipAngleRad - Math.PI : slipAngleRad + Math.PI;
19883
- }
19884
- var relSlipAbs = Math.abs(relSlipRad);
19885
- var relSlipSign = relSlipAbs > 1e-3 ? (relSlipRad > 0 ? 1 : -1) : 0;
19886
- var L = this.getApproxWheelbaseMeters();
19887
- if (L < 0.5)
19888
- L = 2.5;
19889
- var Iz = I_Z_FACTOR * this._chassisMass * L * L;
19890
- var rTarget = 0;
19891
- var currentYawRate = 0;
19892
- var gate = 0;
19893
- var slideTaper = 1.0;
19894
- var desiredMz = 0;
19895
- this._chassisBody.getAngularVelocityToRef(this._sv4);
19896
- currentYawRate = Vector3.Dot(this._sv4, chassisUp);
19897
- if (eligible) {
19898
- var gateRange = V_ENGAGE_FULL_KMH - V_ENGAGE_MIN_KMH;
19899
- var gateRaw = gateRange > 0 ? (absSpeedKmh - V_ENGAGE_MIN_KMH) / gateRange : 1.0;
19900
- gate = Scalar.Clamp(gateRaw, 0, 1);
19901
- gate = gate * gate * (3.0 - 2.0 * gate);
19902
- var vForTarget = driveSign * Math.min(speedMagMps, V_REF_MPS);
19903
- var rTargetRaw = K_ASSIST * vForTarget * effectiveSteerRad / L;
19904
- var rMaxLinear = Scalar.Clamp(speedMagMps / V_REF_MPS, 0, 1);
19905
- var lowSpeedShape = this.arcadeHandbrakeLowSpeedShape > 0 ? this.arcadeHandbrakeLowSpeedShape : 1.0;
19906
- var rMaxSpeedFactor = this.arcadeHandbrakeSpeedGateEnabled ? Math.pow(rMaxLinear, lowSpeedShape) : 1.0;
19907
- var rMax = Tools.ToRadians(R_MAX_DEG_PER_SEC) * rMaxSpeedFactor;
19908
- if (rTargetRaw > rMax)
19909
- rTargetRaw = rMax;
19910
- else if (rTargetRaw < -rMax)
19911
- rTargetRaw = -rMax;
19912
- rTarget = rTargetRaw * gate * this.arcadeSteeringAssist;
19913
- var counterSteerClampActive = false;
19914
- if (this.arcadeHandbrakeCounterSteerClampEnabled === true) {
19915
- var yawThresholdRadPerSec = Tools.ToRadians(this.arcadeHandbrakeCounterSteerYawThresholdDegPerSec);
19916
- if (Math.abs(currentYawRate) > yawThresholdRadPerSec && rTarget * currentYawRate < 0) {
19917
- rTarget = 0;
19918
- counterSteerClampActive = true;
19919
- }
19920
- }
19921
- var releaseFadeMs = this.arcadeHandbrakeClampReleaseFadeMs;
19922
- var clampFadeActive = false;
19923
- var clampFadeMul = 1.0;
19924
- if (counterSteerClampActive) {
19925
- this._arcadeHandbrakeClampReleaseFadeSec = 0;
19926
- }
19927
- else {
19928
- this._arcadeHandbrakeClampReleaseFadeSec += timeStep;
19929
- if (releaseFadeMs > 0) {
19930
- var fadeSec = releaseFadeMs / 1000.0;
19931
- if (this._arcadeHandbrakeClampReleaseFadeSec < fadeSec) {
19932
- var fadeT = this._arcadeHandbrakeClampReleaseFadeSec / fadeSec;
19933
- clampFadeMul = fadeT * fadeT * (3.0 - 2.0 * fadeT);
19934
- rTarget *= clampFadeMul;
19935
- clampFadeActive = true;
19936
- }
19937
- }
19938
- }
19939
- if (relSlipAbs > MAX_SLIDE_ANGLE_RAD) {
19940
- var pastMax = (relSlipAbs - MAX_SLIDE_ANGLE_RAD) / SLIDE_TAPER_RANGE_RAD;
19941
- if (pastMax > 1.0)
19942
- pastMax = 1.0;
19943
- slideTaper = 1.0 - pastMax;
19944
- rTarget *= slideTaper;
19945
- }
19946
- if (rTarget > rMax)
19947
- rTarget = rMax;
19948
- else if (rTarget < -rMax)
19949
- rTarget = -rMax;
19950
- desiredMz = Iz * (rTarget - currentYawRate) / TAU_RESPONSE;
19951
- }
19952
- var newMz;
19953
- if (eligible) {
19954
- newMz = desiredMz;
20727
+ this._stb1.set(m[ri * 4], m[ri * 4 + 1], m[ri * 4 + 2]);
20728
+ this._stb2.set(m[ui * 4], m[ui * 4 + 1], m[ui * 4 + 2]);
20729
+ this._stb3.set(m[fi * 4], m[fi * 4 + 1], m[fi * 4 + 2]);
20730
+ this._chassisBody.getAngularVelocityToRef(this._sv9);
20731
+ var localRoll = Vector3.Dot(this._sv9, this._stb1);
20732
+ var localYaw = Vector3.Dot(this._sv9, this._stb2);
20733
+ var localPitch = Vector3.Dot(this._sv9, this._stb3);
20734
+ var signedFrontSteerRad = this.getSignedFrontSteeringAngleRad();
20735
+ var wheelbaseMeters = this.getApproxWheelbaseMeters();
20736
+ var bicycleYawRate = 0.0;
20737
+ if (wheelbaseMeters > 0.5 && speedMps > 0.2 && Math.abs(signedFrontSteerRad) > 1e-4) {
20738
+ }
20739
+ var minimumYawRate = steeringAuthorityInput * this.arcadeSteeringAssist * (0.25 + steeringMagnitude * 1.0) * speedScale;
20740
+ var desiredYawBoost = Math.abs(bicycleYawRate) > Math.abs(minimumYawRate) ? bicycleYawRate : minimumYawRate;
20741
+ var sameDir = (desiredYawBoost * localYaw) >= 0.0;
20742
+ if (sameDir && Math.abs(localYaw) >= Math.abs(desiredYawBoost)) {
20743
+ this.isArcadeYawAssistActive = true;
20744
+ return;
19955
20745
  }
19956
- else {
19957
- newMz = 0;
19958
- }
19959
- var assistApplyingForce = Math.abs(newMz) > ASSIST_MIN_NM;
19960
- if (eligible && this._wasArcadeHandBrakeActive === false) {
19961
- this._arcadeHandbrakeLatchedDriveSign = signedVxMps >= 0 ? 1 : -1;
19962
- driveSign = this._arcadeHandbrakeLatchedDriveSign;
19963
- this._arcadeHandbrakeHoldElapsedSec = 0;
19964
- var kickRange = V_KICK_FULL_KMH - V_ENGAGE_MIN_KMH;
19965
- var kickRaw = kickRange > 0 ? (absSpeedKmh - V_ENGAGE_MIN_KMH) / kickRange : 1.0;
19966
- var kickGate = Scalar.Clamp(kickRaw, 0, 1);
19967
- kickGate = kickGate * kickGate * (3.0 - 2.0 * kickGate);
19968
- var steerSign = signedFrontSteerRad >= 0 ? 1 : -1;
19969
- var velSign = signedVxMps >= 0 ? 1 : -1;
19970
- var kickLinear = Scalar.Clamp(speedMagMps / V_REF_MPS, 0, 1);
19971
- var kickShape = this.arcadeHandbrakeLowSpeedShape > 0 ? this.arcadeHandbrakeLowSpeedShape : 1.0;
19972
- var kickSpeedFactor = this.arcadeHandbrakeSpeedGateEnabled ? Math.pow(kickLinear, kickShape) : 1.0;
19973
- var kickRadTotal = Tools.ToRadians(K_KICK_DEG_PER_SEC) * steerSign * velSign * kickGate * kickSpeedFactor * this.arcadeSteeringAssist;
19974
- var kickSuppressed = false;
19975
- if (this.arcadeHandbrakeCounterSteerClampEnabled === true) {
19976
- var kickYawThresholdRadPerSec = Tools.ToRadians(this.arcadeHandbrakeCounterSteerYawThresholdDegPerSec);
19977
- if (Math.abs(currentYawRate) > kickYawThresholdRadPerSec && kickRadTotal * currentYawRate < 0) {
19978
- kickRadTotal = 0;
19979
- kickSuppressed = true;
19980
- }
19981
- }
19982
- this._handbrakeKickJzRemaining = Iz * kickRadTotal;
19983
- this._handbrakeKickFramesRemaining = kickSuppressed ? 0 : KICK_FRAMES;
19984
- }
19985
- var kickAngularImpulseJz = 0;
19986
- var kickRad = 0;
19987
- if (eligible && this._handbrakeKickFramesRemaining > 0) {
19988
- var thisFrameKick = this._handbrakeKickJzRemaining / this._handbrakeKickFramesRemaining;
19989
- kickAngularImpulseJz += thisFrameKick;
19990
- this._handbrakeKickJzRemaining -= thisFrameKick;
19991
- this._handbrakeKickFramesRemaining--;
19992
- }
19993
- else if (!eligible) {
19994
- this._handbrakeKickJzRemaining = 0;
19995
- this._handbrakeKickFramesRemaining = 0;
19996
- }
19997
- if (Iz > 0)
19998
- kickRad = kickAngularImpulseJz / Iz;
19999
- var directYawJz = 0;
20000
- if (eligible && this.arcadeHandbrakeDirectYawEnabled === true && Iz > 0) {
20001
- var directDurSec = this.arcadeHandbrakeDirectYawDurationMs > 0 ? this.arcadeHandbrakeDirectYawDurationMs / 1000.0 : 0;
20002
- var directFadeSec = this.arcadeHandbrakeDirectYawFadeMs > 0 ? this.arcadeHandbrakeDirectYawFadeMs / 1000.0 : 0;
20003
- var elapsed = this._arcadeHandbrakeHoldElapsedSec;
20004
- var fadeIn = directFadeSec > 0 ? Scalar.Clamp(elapsed / directFadeSec, 0, 1) : 1.0;
20005
- var fadeOut = 1.0;
20006
- if (directDurSec > 0) {
20007
- var timeLeft = directDurSec - elapsed;
20008
- if (timeLeft <= 0) {
20009
- fadeOut = 0;
20010
- }
20011
- else if (directFadeSec > 0 && timeLeft < directFadeSec) {
20012
- fadeOut = timeLeft / directFadeSec;
20013
- }
20014
- }
20015
- var directFade = Math.min(fadeIn, fadeOut);
20016
- if (directFade > 0) {
20017
- var dyLinear = Scalar.Clamp(speedMagMps / V_REF_MPS, 0, 1);
20018
- var dyShape = this.arcadeHandbrakeLowSpeedShape > 0 ? this.arcadeHandbrakeLowSpeedShape : 1.0;
20019
- var dySpeedFactor = this.arcadeHandbrakeSpeedGateEnabled ? Math.pow(dyLinear, dyShape) : 1.0;
20020
- var dySteerSign = signedFrontSteerRad >= 0 ? 1 : -1;
20021
- var dyExtraRad = Tools.ToRadians(this.arcadeHandbrakeDirectYawDegPerSec)
20022
- * driveSign * dySteerSign
20023
- * gate * dySpeedFactor
20024
- * directFade * this.arcadeSteeringAssist;
20025
- directYawJz = Iz * dyExtraRad * timeStep;
20026
- }
20027
- }
20028
- if (eligible) {
20029
- this._arcadeHandbrakeHoldElapsedSec += timeStep;
20030
- }
20031
- var leverSum = 0;
20032
- var rearGroundedCount = 0;
20033
- var IaddCtrlDesired = 0;
20034
- var IaddCtrlAppliedSum = 0;
20035
- var IaddKickPerWheel = 0;
20036
- var IaddKickAppliedSum = 0;
20037
- var IaddDirectPerWheel = 0;
20038
- var IaddDirectAppliedSum = 0;
20039
- var Jz_through_tires = 0;
20040
- var minCtrlClamp = 1.0;
20041
- var perWheelLog = "";
20042
- var rearIdxList = [];
20043
- var rearLeverList = [];
20044
- var hasController = Math.abs(newMz) > 0;
20045
- var hasKick = Math.abs(kickAngularImpulseJz) > 0;
20046
- var hasDirect = Math.abs(directYawJz) > 0;
20047
- if ((hasController || hasKick || hasDirect) && this._chassisMass > 0) {
20048
- this._chassisBody.getObjectCenterWorldToRef(this._sv1);
20049
- for (var w = 0; w < this._wheelInfo.length; w++) {
20050
- var wi = this._wheelInfo[w];
20051
- if (!wi.isFrontWheel && wi.raycastInfo.groundObject) {
20052
- wi.raycastInfo.contactPointWS.subtractToRef(this._sv1, this._sv2);
20053
- Vector3.CrossToRef(this._sv2, this._axle[w], this._sv3);
20054
- var lever_w = Vector3.Dot(this._sv3, chassisUp);
20055
- rearIdxList.push(w);
20056
- rearLeverList.push(lever_w);
20057
- leverSum += lever_w;
20058
- rearGroundedCount++;
20059
- }
20060
- }
20061
- if (rearGroundedCount > 0 && Math.abs(leverSum) > MIN_LEVER_EPS) {
20062
- if (hasController)
20063
- IaddCtrlDesired = (newMz * timeStep) / leverSum;
20064
- if (hasKick)
20065
- IaddKickPerWheel = kickAngularImpulseJz / leverSum;
20066
- if (hasDirect)
20067
- IaddDirectPerWheel = directYawJz / leverSum;
20068
- for (var ii = 0; ii < rearIdxList.length; ii++) {
20069
- var w2 = rearIdxList[ii];
20070
- var lever_w2 = rearLeverList[ii];
20071
- var wi2 = this._wheelInfo[w2];
20072
- var IaddCtrlW = hasController ? IaddCtrlDesired : 0;
20073
- var IaddTotalW = IaddCtrlW + IaddKickPerWheel + IaddDirectPerWheel;
20074
- if (IaddTotalW !== 0) {
20075
- this._axle[w2].scaleToRef(IaddTotalW, this._sv2);
20076
- this._chassisBody.applyImpulse(this._sv2, wi2.raycastInfo.contactPointWS);
20077
- IaddCtrlAppliedSum += IaddCtrlW;
20078
- IaddKickAppliedSum += IaddKickPerWheel;
20079
- IaddDirectAppliedSum += IaddDirectPerWheel;
20080
- Jz_through_tires += lever_w2 * IaddTotalW;
20081
- }
20082
- if (debugEnabled) {
20083
- perWheelLog += " w" + w2
20084
- + "{maximp=" + (wi2.suspensionForce * timeStep * wi2.frictionSlip).toFixed(1)
20085
- + " side=" + this._sideImpulse[w2].toFixed(1)
20086
- + " fwd=" + (this._forwardImpulse[w2] * 0.5).toFixed(1)
20087
- + " ctrlDes=" + IaddCtrlDesired.toFixed(1)
20088
- + " ctrl=" + IaddCtrlW.toFixed(1)
20089
- + " kick=" + IaddKickPerWheel.toFixed(1)
20090
- + " direct=" + IaddDirectPerWheel.toFixed(1)
20091
- + " tot=" + IaddTotalW.toFixed(1) + "}";
20092
- }
20093
- }
20094
- }
20095
- }
20096
- this._arcadeYawAssistLastKickRad = kickRad;
20097
- this._arcadeYawAssistLastIaddPerWheel = IaddCtrlDesired + IaddKickPerWheel + IaddDirectPerWheel;
20098
- this._arcadeYawAssistLastClampScalar = minCtrlClamp;
20099
- this._arcadeYawAssistLastLeverSum = leverSum;
20100
- var assistActiveThisFrame = eligible || assistApplyingForce;
20101
- this.isArcadeYawAssistActive = assistActiveThisFrame;
20102
- this._wasArcadeHandBrakeActive = this.isArcadeHandBrakeActive;
20103
- this._wasArcadeYawAssistApplyingForce = assistApplyingForce;
20746
+ var yawDeltaMax = (0.15 + steeringMagnitude * 0.35) * speedScale;
20747
+ var yawDelta = Scalar.Clamp(desiredYawBoost * (2.0 + steeringMagnitude * 4.0) * timeStep, -yawDeltaMax, yawDeltaMax);
20748
+ if (Math.abs(yawDelta) < 1e-6)
20749
+ return;
20750
+ localYaw += yawDelta;
20751
+ this._sv9.set(this._stb1.x * localRoll + this._stb2.x * localYaw + this._stb3.x * localPitch, this._stb1.y * localRoll + this._stb2.y * localYaw + this._stb3.y * localPitch, this._stb1.z * localRoll + this._stb2.z * localYaw + this._stb3.z * localPitch);
20752
+ this._chassisBody.setAngularVelocity(this._sv9);
20753
+ this.isArcadeYawAssistActive = true;
20104
20754
  }
20105
20755
  resolveWheelSpinDirection(wheelIndex, requestedDriveImpulse, appliedDriveImpulse) {
20106
20756
  if (Math.abs(requestedDriveImpulse) > 0.0001) {
@@ -20298,6 +20948,24 @@ export class btRaycastVehicle {
20298
20948
  this._forwardWS[i].normalize();
20299
20949
  this._sideImpulse[i] = this.resolveSingleBilateral(this._chassisBody, wheel.raycastInfo.contactPointWS, this._axle[i]);
20300
20950
  this._sideImpulse[i] *= this.sideFrictionStiffness;
20951
+ if (this.arcadeSideSlipSaturationEnabled && this._sideImpulse[i] !== 0) {
20952
+ this.velocityAtWorldPoint(this._chassisBody, wheel.raycastInfo.contactPointWS, this._sv8);
20953
+ var vLat = Vector3.Dot(this._axle[i], this._sv8);
20954
+ var vFwd = Vector3.Dot(this._forwardWS[i], this._sv8);
20955
+ var wheelSpeedSqr = vLat * vLat + vFwd * vFwd;
20956
+ var minSpeed = this.arcadeSideSlipMinSpeedMps;
20957
+ if (wheelSpeedSqr > minSpeed * minSpeed) {
20958
+ var slipRad = Math.atan2(Math.abs(vLat), Math.max(Math.abs(vFwd), 1e-3));
20959
+ var peakRad = Tools.ToRadians(Math.max(this.arcadeSideSlipPeakDeg, 0.1));
20960
+ if (slipRad > peakRad) {
20961
+ var holdFactor = Math.sin(peakRad) / Math.max(Math.sin(slipRad), 1e-3);
20962
+ var falloffSpan = Math.max(this.arcadeSideSlipFalloffDeg - this.arcadeSideSlipPeakDeg, 0.1);
20963
+ var t = Math.min(Math.max((Tools.ToDegrees(slipRad) - this.arcadeSideSlipPeakDeg) / falloffSpan, 0.0), 1.0);
20964
+ var rolloff = Scalar.Lerp(1.0, this.arcadeSideSlipFalloffFactor, t);
20965
+ this._sideImpulse[i] *= holdFactor * rolloff;
20966
+ }
20967
+ }
20968
+ }
20301
20969
  }
20302
20970
  }
20303
20971
  var sideFactor = 1.0;
@@ -20423,12 +21091,6 @@ export class btRaycastVehicle {
20423
21091
  this._wheelInfo[wheel_i].skidInfo = Math.min(this._wheelInfo[wheel_i].skidInfo, arcadeSkidInfo);
20424
21092
  }
20425
21093
  }
20426
- var isAnyArcadeModeActiveNow = this.isArcadeHandBrakeActive || this.isArcadeWheelSkidActive || this.isArcadeBurnoutModeActive || this.isArcadeDonutModeActive;
20427
- if (this._wasAnyArcadeModeActive && !isAnyArcadeModeActiveNow) {
20428
- this._postHandbrakeLogFrames = 180;
20429
- this._postHandbrakeLogCounter = 0;
20430
- }
20431
- this._wasAnyArcadeModeActive = isAnyArcadeModeActiveNow;
20432
21094
  if (sliding) {
20433
21095
  for (var wheel_i = 0; wheel_i < numWheels; wheel_i++) {
20434
21096
  if (this._sideImpulse[wheel_i] !== 0) {
@@ -20452,7 +21114,8 @@ export class btRaycastVehicle {
20452
21114
  avgSideImpulse /= groundedSideCount;
20453
21115
  var sideBlendToCoM = 0;
20454
21116
  var absSpeedKmh = Math.abs(this._currentVehicleSpeedKmHour);
20455
- if (this.sideToSideStabilityEnabled && absSpeedKmh > this.sideToSideStabilityStartKmh) {
21117
+ var arcadeSlideActive = this.isArcadeHandBrakeActive || this.isArcadeWheelSkidActive || this.isArcadeBurnoutModeActive || this.isArcadeDonutModeActive;
21118
+ if (!arcadeSlideActive && this.sideToSideStabilityEnabled && absSpeedKmh > this.sideToSideStabilityStartKmh) {
20456
21119
  var sideBlendRange = this.sideToSideStabilityFullKmh - this.sideToSideStabilityStartKmh;
20457
21120
  if (sideBlendRange > 0) {
20458
21121
  sideBlendToCoM = 0.7 * Math.min((absSpeedKmh - this.sideToSideStabilityStartKmh) / sideBlendRange, 1.0);
@@ -20521,12 +21184,22 @@ export class btRaycastVehicle {
20521
21184
  clampChassisYawRate(deltaTime) {
20522
21185
  if (this.maximumYawRateLow <= 0 && this.maximumYawRateHigh <= 0)
20523
21186
  return;
20524
- const arcadeActive = (this.isArcadeHandBrakeActive === true || this.isArcadeBurnoutModeActive === true || this.isArcadeDonutModeActive === true || this.isArcadeWheelSkidActive === true);
20525
- if (arcadeActive === true && this.arcadeYawCapMultiplier <= 0)
21187
+ const arcadeActive = (this.isArcadeHandBrakeActive === true || this.isArcadeBurnoutModeActive === true || this.isArcadeDonutModeActive === true);
21188
+ if (arcadeActive === true && this.isArcadeHandBrakeActive === true && this.arcadeHandbrakeYawCapMultiplier <= 0)
21189
+ return;
21190
+ if (arcadeActive === true && this.isArcadeBurnoutModeActive === true && this.arcadeBurnoutYawCapMultiplier <= 0)
21191
+ return;
21192
+ if (arcadeActive === true && this.isArcadeDonutModeActive === true && this.arcadeDonutYawCapMultiplier <= 0)
20526
21193
  return;
20527
21194
  let capDeg = Scalar.Lerp(this.maximumYawRateLow, this.maximumYawRateHigh, this.smoothedGradientSpeed);
20528
- if (arcadeActive === true)
20529
- capDeg *= this.arcadeYawCapMultiplier;
21195
+ if (arcadeActive === true) {
21196
+ if (this.isArcadeHandBrakeActive === true)
21197
+ capDeg *= this.arcadeHandbrakeYawCapMultiplier;
21198
+ if (this.isArcadeBurnoutModeActive === true)
21199
+ capDeg *= this.arcadeBurnoutYawCapMultiplier;
21200
+ if (this.isArcadeDonutModeActive === true)
21201
+ capDeg *= this.arcadeDonutYawCapMultiplier;
21202
+ }
20530
21203
  if (capDeg <= 0)
20531
21204
  return;
20532
21205
  const capRad = Tools.ToRadians(capDeg);
@@ -22189,81 +22862,59 @@ export class RaycastVehicle {
22189
22862
  this.m_vehicle.arcadeSteeringAssist = value;
22190
22863
  }
22191
22864
  }
22192
- getArcadeDonutDirectYawDegPerSec() {
22193
- if (this.m_vehicle != null) {
22194
- return this.m_vehicle.arcadeDonutDirectYawDegPerSec;
22195
- }
22196
- return 1.0;
22197
- }
22198
- setArcadeDonutDirectYawDegPerSec(value) {
22199
- if (this.m_vehicle != null) {
22200
- this.m_vehicle.arcadeDonutDirectYawDegPerSec = value;
22201
- }
22202
- }
22203
- getArcadeHandbrakeDirectYawDegPerSec() {
22204
- if (this.m_vehicle != null) {
22205
- return this.m_vehicle.arcadeHandbrakeDirectYawDegPerSec;
22206
- }
22207
- return 1.0;
22208
- }
22209
- setArcadeHandbrakeDirectYawDegPerSec(value) {
22210
- if (this.m_vehicle != null) {
22211
- this.m_vehicle.arcadeHandbrakeDirectYawDegPerSec = value;
22212
- }
22213
- }
22214
- getArcadeHandbrakeMaxSlideAngleDeg() {
22865
+ getArcadeDonutDirectYawEnabled() {
22215
22866
  if (this.m_vehicle != null) {
22216
- return this.m_vehicle.arcadeHandbrakeMaxSlideAngleDeg;
22867
+ return this.m_vehicle.arcadeDonutDirectYawEnabled;
22217
22868
  }
22218
- return 1.0;
22869
+ return false;
22219
22870
  }
22220
- setArcadeHandbrakeMaxSlideAngleDeg(value) {
22871
+ setArcadeDonutDirectYawEnabled(value) {
22221
22872
  if (this.m_vehicle != null) {
22222
- this.m_vehicle.arcadeHandbrakeMaxSlideAngleDeg = value;
22873
+ this.m_vehicle.arcadeDonutDirectYawEnabled = value;
22223
22874
  }
22224
22875
  }
22225
- getArcadeHandbrakeBicycleYawAuthority() {
22876
+ getArcadeDonutDirectYawDegPerSec() {
22226
22877
  if (this.m_vehicle != null) {
22227
- return this.m_vehicle.arcadeHandbrakeYawAuthority;
22878
+ return this.m_vehicle.arcadeDonutDirectYawDegPerSec;
22228
22879
  }
22229
22880
  return 1.0;
22230
22881
  }
22231
- setArcadeHandbrakeBicycleYawAuthority(value) {
22882
+ setArcadeDonutDirectYawDegPerSec(value) {
22232
22883
  if (this.m_vehicle != null) {
22233
- this.m_vehicle.arcadeHandbrakeYawAuthority = value;
22884
+ this.m_vehicle.arcadeDonutDirectYawDegPerSec = value;
22234
22885
  }
22235
22886
  }
22236
- getArcadeHandbrakeMaxYawRateDegPerSec() {
22887
+ getArcadeHandbrakeYawCapMultiplier() {
22237
22888
  if (this.m_vehicle != null) {
22238
- return this.m_vehicle.arcadeHandbrakeMaxYawRateDegPerSec;
22889
+ return this.m_vehicle.arcadeHandbrakeYawCapMultiplier;
22239
22890
  }
22240
22891
  return 1.0;
22241
22892
  }
22242
- setArcadeHandbrakeMaxYawRateDegPerSec(value) {
22893
+ setArcadeHandbrakeYawCapMultiplier(value) {
22243
22894
  if (this.m_vehicle != null) {
22244
- this.m_vehicle.arcadeHandbrakeMaxYawRateDegPerSec = value;
22895
+ this.m_vehicle.arcadeHandbrakeYawCapMultiplier = value;
22245
22896
  }
22246
22897
  }
22247
- getArcadeHandbrakeReferenceSpeedKmh() {
22898
+ getArcadeBurnoutYawCapMultiplier() {
22248
22899
  if (this.m_vehicle != null) {
22249
- return this.m_vehicle.arcadeHandbrakeReferenceSpeedKmh;
22900
+ return this.m_vehicle.arcadeBurnoutYawCapMultiplier;
22250
22901
  }
22251
22902
  return 1.0;
22252
22903
  }
22253
- setArcadeHandbrakeReferenceSpeedKmh(value) {
22904
+ setArcadeBurnoutYawCapMultiplier(value) {
22254
22905
  if (this.m_vehicle != null) {
22255
- this.m_vehicle.arcadeHandbrakeReferenceSpeedKmh = value;
22906
+ this.m_vehicle.arcadeBurnoutYawCapMultiplier = value;
22256
22907
  }
22257
22908
  }
22258
- getArcadeHandbrakeKickStrengthDegPerSec() {
22909
+ getArcadeDonutYawCapMultiplier() {
22259
22910
  if (this.m_vehicle != null) {
22260
- return this.m_vehicle.arcadeHandbrakeKickStrengthDegPerSec;
22911
+ return this.m_vehicle.arcadeDonutYawCapMultiplier;
22261
22912
  }
22262
22913
  return 1.0;
22263
22914
  }
22264
- setArcadeHandbrakeKickStrengthDegPerSec(value) {
22915
+ setArcadeDonutYawCapMultiplier(value) {
22265
22916
  if (this.m_vehicle != null) {
22266
- this.m_vehicle.arcadeHandbrakeKickStrengthDegPerSec = value;
22917
+ this.m_vehicle.arcadeDonutYawCapMultiplier = value;
22267
22918
  }
22268
22919
  }
22269
22920
  getArcadeBurnoutActive() {
@@ -22525,22 +23176,6 @@ export class RaycastVehicle {
22525
23176
  this.m_vehicle.wheelAtRestSpeedThresholdKmh = value;
22526
23177
  }
22527
23178
  }
22528
- setWheelSpinDebugLogEnabled(enabled) {
22529
- if (this.m_vehicle != null) {
22530
- this.m_vehicle.wheelSpinDebugLogEnabled = enabled;
22531
- }
22532
- }
22533
- getWheelSpinDebugLogEnabled() {
22534
- if (this.m_vehicle != null) {
22535
- return this.m_vehicle.wheelSpinDebugLogEnabled;
22536
- }
22537
- return false;
22538
- }
22539
- setWheelSpinDebugLogIntervalFrames(frames) {
22540
- if (this.m_vehicle != null) {
22541
- this.m_vehicle.wheelSpinDebugLogIntervalFrames = frames;
22542
- }
22543
- }
22544
23179
  updateWheelInformation() {
22545
23180
  const wheels = this.getNumWheels();
22546
23181
  if (wheels > 0) {