From a22392844904e13be8c5aba80c042014670631b9 Mon Sep 17 00:00:00 2001 From: "Babylon.js CI" Date: Fri, 10 Jul 2026 09:09:13 +0000 Subject: [PATCH] chore(compat): Babylon.js compat-layer sync (2026-07-10) --- packages/babylon-lite-compat/COMPAT-STATUS.md | 7 +- .../babylon-lite-compat/src/engine/engine.ts | 14 ++++ .../babylon-lite-compat/src/lights/lights.ts | 79 ++++++++++++------- .../tests/engine-scene-api.test.ts | 16 +++- .../babylon-lite-compat/tests/lights.test.ts | 67 ++++++++++++++++ 5 files changed, 149 insertions(+), 34 deletions(-) create mode 100644 packages/babylon-lite-compat/tests/lights.test.ts diff --git a/packages/babylon-lite-compat/COMPAT-STATUS.md b/packages/babylon-lite-compat/COMPAT-STATUS.md index 476beacb50..7b63aea694 100644 --- a/packages/babylon-lite-compat/COMPAT-STATUS.md +++ b/packages/babylon-lite-compat/COMPAT-STATUS.md @@ -7,8 +7,8 @@ updated by the `update-compat-layer` skill. -- **Last synced BJS commit:** `b1d90d67bc09f56bf2882671f4484997630c4482` -- **Last sync date:** 2026-07-09 +- **Last synced BJS commit:** `958a8dbed4e263d412ee9255ae747299043de19f` +- **Last sync date:** 2026-07-10 - **Lite compat package version:** 0.0.1 > The "Last synced BJS commit" is the `BabylonJS/Babylon.js` `master` HEAD that the @@ -89,6 +89,7 @@ date` markers above record the `BabylonJS/Babylon.js` `master` HEAD the surface | `engine.runRenderLoop` / `stopRenderLoop` | ⚡ Partial | engine (async startup; N callbacks) | | `engine.resize` / `setSize` / `dispose` / `getRenderingCanvas` | ✅ Full | engine | | `engine.getCaps()` (compressed-format flags `astc` / `s3tc` / `etc2`) | ✅ Full | engine (derived from the Lite WebGPU device's enabled features) | +| `AbstractEngine.Version` / `AbstractEngine.NpmPackage` (static) | ✅ Full | engine (constant version string tracking the last-synced `@babylonjs/core` release — currently `9.16.1`) | | `engine.beginFrame` / `endFrame` | ❌ Not supported | — | | `NullEngine` | ✅ Full | [engine/engine.ts](src/engine/engine.ts) (backed by Lite's real device-less `createNullEngine`; scene built with `defaultRenderTask: false` — no swapchain/GPU resource — and advanced via Lite `stepScene`, which fires the same before-render hook as the GPU path) | | `AbstractScene` / `Scene` | ⚡ Partial | [scene/scene.ts](src/scene/scene.ts) over [scene/abstract-scene.ts](src/scene/abstract-scene.ts) (entity collections on `AbstractScene`, as in BJS) | @@ -141,7 +142,7 @@ date` markers above record the `BabylonJS/Babylon.js` `master` HEAD the surface | `PointLight` | ✅ Full | lights | | `SpotLight` | ✅ Full | lights | | `light.diffuse/specular/intensity/position/direction` | ✅ Full | lights | -| `light.setEnabled(false)` | 🔧 Needs Lite core | per-light visibility toggle | +| `light.setEnabled(false)` | ✅ Full | lights (no per-light flag in Lite; a disabled light zeroes its intensity in the shared lights UBO and restores the logical value on re-enable) | | `RectAreaLight` | ❌ Not supported | not in Lite | | `ClusteredLightContainer` | ❌ Not supported | throwing stub; not in Lite's public API | diff --git a/packages/babylon-lite-compat/src/engine/engine.ts b/packages/babylon-lite-compat/src/engine/engine.ts index fddbe4a2b4..ce26690a4a 100644 --- a/packages/babylon-lite-compat/src/engine/engine.ts +++ b/packages/babylon-lite-compat/src/engine/engine.ts @@ -40,6 +40,20 @@ import { Observable } from "../misc/observable.js"; import type { Scene } from "../scene/scene.js"; export abstract class AbstractEngine { + /** + * Babylon.js `AbstractEngine.Version` — the `@babylonjs/core` version this + * compat layer targets. Kept in sync with the last-reconciled BJS release + * (see `COMPAT-STATUS.md` › "Last synced BJS commit"). + */ + public static get Version(): string { + return "9.16.1"; + } + + /** Babylon.js `AbstractEngine.NpmPackage` — the npm package + version string. */ + public static get NpmPackage(): string { + return `babylonjs@${AbstractEngine.Version}`; + } + /** @internal The underlying Lite engine context. Populated by `initAsync()`. */ public _lite!: EngineContext; diff --git a/packages/babylon-lite-compat/src/lights/lights.ts b/packages/babylon-lite-compat/src/lights/lights.ts index 7a959abc4f..2d68a2d48e 100644 --- a/packages/babylon-lite-compat/src/lights/lights.ts +++ b/packages/babylon-lite-compat/src/lights/lights.ts @@ -43,8 +43,55 @@ export abstract class Light extends Node { return "Light"; } - public abstract get intensity(): number; - public abstract set intensity(value: number); + /** + * @internal When the light is disabled via {@link setEnabled}, its Lite + * intensity is forced to `0` (zero contribution) and the caller-visible + * intensity is parked here so {@link intensity} keeps reporting the real + * value and re-enabling restores it. `null` means the light is enabled. + */ + private _disabledIntensity: number | null = null; + + /** @internal Typed accessor for the Lite light's `intensity` scalar. */ + private get _liteIntensity(): number { + return (this._lite as unknown as { intensity: number }).intensity; + } + private set _liteIntensity(value: number) { + (this._lite as unknown as { intensity: number }).intensity = value; + } + + /** + * Babylon.js `light.intensity`. While the light is disabled the Lite light + * carries `0`, but the getter still returns the logical value the caller set. + */ + public get intensity(): number { + return this._disabledIntensity ?? this._liteIntensity; + } + public set intensity(value: number) { + if (this._disabledIntensity !== null) { + this._disabledIntensity = value; + } else { + this._liteIntensity = value; + } + } + + /** + * Babylon.js `light.setEnabled(false)` — toggle the light's contribution. + * Babylon Lite has no per-light enable flag, so a disabled light is expressed + * by zeroing its intensity in the shared lights UBO (and restoring it on + * re-enable). The logical intensity value is preserved across the toggle. + */ + public override setEnabled(value: boolean): void { + if (value !== this.isEnabled()) { + if (!value) { + this._disabledIntensity = this._liteIntensity; + this._liteIntensity = 0; + } else if (this._disabledIntensity !== null) { + this._liteIntensity = this._disabledIntensity; + this._disabledIntensity = null; + } + } + super.setEnabled(value); + } /** Detach this light's shadow generator (compat for `light.shadowEnabled = false`). */ public set shadowEnabled(enabled: boolean) { @@ -92,13 +139,6 @@ export class HemisphericLight extends Light { return "HemisphericLight"; } - public get intensity(): number { - return this._lite.intensity; - } - public set intensity(value: number) { - this._lite.intensity = value; - } - public get direction(): Vector3 { return readVector(this._lite.direction); } @@ -144,13 +184,6 @@ export class DirectionalLight extends Light { return "DirectionalLight"; } - public get intensity(): number { - return this._lite.intensity; - } - public set intensity(value: number) { - this._lite.intensity = value; - } - public get direction(): Vector3 { return readVector(this._lite.direction); } @@ -196,13 +229,6 @@ export class PointLight extends Light { return "PointLight"; } - public get intensity(): number { - return this._lite.intensity; - } - public set intensity(value: number) { - this._lite.intensity = value; - } - public get range(): number { return this._lite.range; } @@ -248,13 +274,6 @@ export class SpotLight extends Light { return "SpotLight"; } - public get intensity(): number { - return this._lite.intensity; - } - public set intensity(value: number) { - this._lite.intensity = value; - } - public get angle(): number { return this._lite.angle; } diff --git a/packages/babylon-lite-compat/tests/engine-scene-api.test.ts b/packages/babylon-lite-compat/tests/engine-scene-api.test.ts index 545b96ab69..748f30108a 100644 --- a/packages/babylon-lite-compat/tests/engine-scene-api.test.ts +++ b/packages/babylon-lite-compat/tests/engine-scene-api.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { WebGPUEngine } from "../src/engine/engine"; +import { AbstractEngine, Engine, ThinEngine, WebGPUEngine } from "../src/engine/engine"; import { Scene } from "../src/scene/scene"; /** @@ -167,3 +167,17 @@ describe("Scene entity registries", () => { expect(b.getUniqueId()).toBe(2); }); }); + +describe("AbstractEngine version statics", () => { + it("reports the targeted @babylonjs/core version", () => { + expect(AbstractEngine.Version).toBe("9.16.1"); + expect(AbstractEngine.NpmPackage).toBe("babylonjs@9.16.1"); + }); + + it("inherits the version statics on every engine subclass", () => { + for (const Ctor of [ThinEngine, Engine, WebGPUEngine]) { + expect(Ctor.Version).toBe("9.16.1"); + expect(Ctor.NpmPackage).toBe("babylonjs@9.16.1"); + } + }); +}); diff --git a/packages/babylon-lite-compat/tests/lights.test.ts b/packages/babylon-lite-compat/tests/lights.test.ts new file mode 100644 index 0000000000..896be2bcbb --- /dev/null +++ b/packages/babylon-lite-compat/tests/lights.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from "vitest"; + +import { DirectionalLight, HemisphericLight, PointLight, SpotLight } from "../src/lights/lights"; +import { Vector3 } from "../src/math/vector"; + +/** + * The light wrappers forward to Babylon Lite's device-free light factories, so + * their scalar/color proxying and the `setEnabled` visibility toggle can be + * exercised under Node without a GPU. `setEnabled(false)` has no per-light flag + * in Lite, so the wrapper zeroes the underlying intensity while preserving the + * caller-visible value — this test pins that behaviour. + */ + +describe("Light.setEnabled visibility toggle", () => { + it("zeroes the Lite intensity when disabled and restores it when enabled", () => { + const light = new DirectionalLight("d", new Vector3(0, -1, 0)); + light.intensity = 0.8; + expect(light.intensity).toBeCloseTo(0.8); + expect(light._lite.intensity).toBeCloseTo(0.8); + + light.setEnabled(false); + expect(light.isEnabled()).toBe(false); + // Caller-visible intensity is preserved; the Lite light contributes nothing. + expect(light.intensity).toBeCloseTo(0.8); + expect(light._lite.intensity).toBe(0); + + light.setEnabled(true); + expect(light.isEnabled()).toBe(true); + expect(light.intensity).toBeCloseTo(0.8); + expect(light._lite.intensity).toBeCloseTo(0.8); + }); + + it("remembers intensity changes made while disabled", () => { + const light = new PointLight("p", new Vector3(0, 1, 0)); + light.intensity = 1; + light.setEnabled(false); + expect(light._lite.intensity).toBe(0); + + light.intensity = 2.5; + // Still contributes nothing while disabled, but the value is remembered. + expect(light._lite.intensity).toBe(0); + expect(light.intensity).toBeCloseTo(2.5); + + light.setEnabled(true); + expect(light._lite.intensity).toBeCloseTo(2.5); + }); + + it("is idempotent for repeated toggles of the same state", () => { + const light = new SpotLight("s", new Vector3(0, 5, 0), new Vector3(0, -1, 0), Math.PI / 4, 2); + light.intensity = 0.5; + light.setEnabled(false); + light.setEnabled(false); + expect(light._lite.intensity).toBe(0); + expect(light.intensity).toBeCloseTo(0.5); + light.setEnabled(true); + expect(light._lite.intensity).toBeCloseTo(0.5); + }); + + it("supports the hemispheric light too", () => { + const light = new HemisphericLight("h", new Vector3(0, 1, 0)); + light.intensity = 0.7; + light.setEnabled(false); + expect(light._lite.intensity).toBe(0); + light.setEnabled(true); + expect(light._lite.intensity).toBeCloseTo(0.7); + }); +});