Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
323d08d
feat: add support of selection outliner
julien-moreau Apr 2, 2026
1f7ca33
feat: add support of HDR texture for environment texture and outline …
julien-moreau Apr 3, 2026
45e4607
feat: add support of HDR as environment texture when exporting scene …
julien-moreau Apr 3, 2026
19a37b3
feat: adding support of clustered lights
julien-moreau Apr 4, 2026
6663ad9
fix: usage of HDR environment texture in CLI
julien-moreau Apr 5, 2026
5f4967c
v5.4.1-alpha.0
julien-moreau Apr 5, 2026
033c23a
feat: make clustered light container editable in inspector
julien-moreau Apr 5, 2026
5fb8d43
v5.4.1-alpha.1
julien-moreau Apr 6, 2026
83a7c0f
fix: compute clustered light before checking scene is ready in babylo…
Apr 8, 2026
5537396
feat: add support of LOD inspector to setup custom LODs
Apr 8, 2026
498ab9b
fix: compute clustered light container after parenting is resolved
julien-moreau Apr 8, 2026
e9fc3c4
chore: use installed typescript version
julien-moreau Apr 8, 2026
5db51a6
feat: drag'n'drop lights in clustered light container
Apr 9, 2026
e6117b6
v5.4.1-alpha.2
julien-moreau Apr 9, 2026
74764a4
feat: add menu to merge animations groups in scene inspector
julien-moreau Apr 9, 2026
3434ccd
feat: add @componentFromScene decorator in babylonjs-editor-tools
Apr 10, 2026
a242b56
docs: add documentation for common decorators
Apr 10, 2026
eb252a1
fix: handle cli and tools dependency in case of mono-repo
julien-moreau Apr 10, 2026
483eba2
v5.4.1-alpha.3
julien-moreau Apr 10, 2026
133424a
feat: add support of decal map in standard and pbr material inspectors
julien-moreau Apr 11, 2026
02a30df
chore: bump Babylon.js to v9.2.1
julien-moreau Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
"source.fixAll.eslint": "always"
},
"files.insertFinalNewline": true,
"javascript.format.semicolons": "insert",
"typescript.format.semicolons": "insert",
"typescript.preferences.quoteStyle": "double",
"javascript.preferences.quoteStyle": "double",
"js/ts.format.semicolons": "insert",
"js/ts.preferences.quoteStyle": "double",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
Expand All @@ -35,6 +33,8 @@
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"typescript.preferences.importModuleSpecifier": "project-relative",
"typescript.preferences.autoImportFileExcludePatterns": ["**/export.ts"]
"js/ts.preferences.importModuleSpecifier": "project-relative",
"js/ts.preferences.autoImportFileExcludePatterns": ["**/export.ts"],
"js/ts.tsdk.path": "node_modules/typescript/lib",
"js/ts.tsdk.promptToUseWorkspaceVersion": true
}
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "babylonjs-editor-cli",
"version": "5.4.0",
"version": "5.4.1-alpha.3",
"description": "Babylon.js Editor CLI is a command line interface to help you package your scenes made using the Babylon.js Editor",
"productName": "Babylon.js Editor CLI",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/pack/assets/process.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { processExportedMaterial } from "./material.mjs";
import { processExportedNodeParticleSystemSet } from "./particle-system.mjs";

const supportedImagesExtensions: string[] = [".jpg", ".jpeg", ".webp", ".png", ".bmp"];
const supportedCubeTexturesExtensions: string[] = [".env", ".dds"];
const supportedCubeTexturesExtensions: string[] = [".env", ".dds", ".hdr"];
const supportedAudioExtensions: string[] = [".mp3", ".wav", ".wave", ".ogg"];
const supportedJsonExtensions: string[] = [".material", ".gui", ".cinematic", ".npss", ".ragdoll", ".json"];
const supportedMiscExtensions: string[] = [".3dl", ".exr", ".hdr"];
Expand Down
16 changes: 14 additions & 2 deletions cli/src/pack/scene.mts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,11 @@ export async function createBabylonScene(options: ICreateBabylonSceneOptions) {
physicsGravity: options.config.physics.gravity,
physicsEngine: "HavokPlugin",

metadata: options.config.metadata,
metadata: {
...options.config.metadata,
rendering: options.config.rendering,
clusteredLight: options.config.clusteredLight,
},

morphTargetManagers,
lights,
Expand Down Expand Up @@ -522,7 +526,7 @@ export async function createBabylonScene(options: ICreateBabylonSceneOptions) {
postProcesses: [],
spriteManagers: [],
reflectionProbes: [],
};
} as any;

// Resolve parenting for mesh instances.
const allNodes = [...scene.meshes, ...scene.cameras, ...scene.lights, ...scene.transformNodes, ...scene.meshes.map((m) => m.instances ?? []).flat()];
Expand All @@ -539,6 +543,14 @@ export async function createBabylonScene(options: ICreateBabylonSceneOptions) {
}
});

// Configue ennviornment texture
if (scene.environmentTexture?.name && scene.environmentTexture.customType === "BABYLON.HDRCubeTexture") {
scene.environmentTextureSize = 512;
scene.environmentTextureType = "BABYLON.HDRCubeTexture";
scene.environmentTextureRotationY = scene.environmentTexture.rotationY;
scene.environmentTexture = scene.environmentTexture.name;
}

// Write final scene file.
const destination = join(options.publicDir, `${options.sceneName}.babylon`);
await fs.writeJSON(destination, scene, {
Expand Down
28 changes: 14 additions & 14 deletions editor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "babylonjs-editor",
"version": "5.4.0",
"version": "5.4.1-alpha.3",
"description": "Babylon.js Editor is a Web Application helping artists to work with Babylon.js",
"productName": "Babylon.js Editor",
"main": "build/src/index.js",
Expand Down Expand Up @@ -40,9 +40,9 @@
"vitest": "4.0.17"
},
"dependencies": {
"@babylonjs/addons": "9.0.0",
"@babylonjs/core": "9.0.0",
"@babylonjs/havok": "1.3.10",
"@babylonjs/addons": "9.2.1",
"@babylonjs/core": "9.2.1",
"@babylonjs/havok": "1.3.12",
"@blueprintjs/core": "^5.10.0",
"@blueprintjs/select": "^5.1.2",
"@emotion/react": "^11.13.3",
Expand Down Expand Up @@ -75,18 +75,18 @@
"@xterm/xterm": "6.1.0-beta.22",
"assimpjs": "0.0.10",
"axios": "1.15.0",
"babylonjs": "9.0.0",
"babylonjs-addons": "9.0.0",
"babylonjs": "9.2.1",
"babylonjs-addons": "9.2.1",
"babylonjs-editor-cli": "latest",
"babylonjs-editor-tools": "latest",
"babylonjs-gui": "9.0.0",
"babylonjs-gui-editor": "9.0.0",
"babylonjs-loaders": "9.0.0",
"babylonjs-materials": "9.0.0",
"babylonjs-node-editor": "9.0.0",
"babylonjs-node-particle-editor": "9.0.0",
"babylonjs-post-process": "9.0.0",
"babylonjs-procedural-textures": "9.0.0",
"babylonjs-gui": "9.2.1",
"babylonjs-gui-editor": "9.2.1",
"babylonjs-loaders": "9.2.1",
"babylonjs-materials": "9.2.1",
"babylonjs-node-editor": "9.2.1",
"babylonjs-node-particle-editor": "9.2.1",
"babylonjs-post-process": "9.2.1",
"babylonjs-procedural-textures": "9.2.1",
"chokidar": "^4.0.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand Down
1 change: 1 addition & 0 deletions editor/src/editor/layout/assets-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,7 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
return openModelViewer(this.props.editor, item.props.absolutePath);

case ".env":
case ".hdr":
return openEnvViewer(item.props.absolutePath);

case ".ts":
Expand Down
31 changes: 21 additions & 10 deletions editor/src/editor/layout/assets-browser/viewers/env-viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { basename } from "path/posix";
import { basename, extname } from "path/posix";

import { useEffect, useRef } from "react";

import { Engine, Scene, CreateSphere, ArcRotateCamera, Vector3, PBRMaterial, CubeTexture, Texture } from "babylonjs";
import { Engine, Scene, CreateSphere, ArcRotateCamera, Vector3, PBRMaterial, CubeTexture, Texture, HDRCubeTexture } from "babylonjs";

import { showAlert } from "../../../../ui/dialog";

Expand Down Expand Up @@ -30,16 +30,27 @@ function AssetBrowserEnvViewer(props: IAssetBrowserEnvViewerProps) {
const scene = new Scene(engine);
scene.clearColor.set(0, 0, 0, 0);

const texture = CubeTexture.CreateFromPrefilteredData(props.absolutePath, scene);
texture.coordinatesMode = Texture.CUBIC_MODE;
let texture: CubeTexture | HDRCubeTexture | null = null;
switch (extname(props.absolutePath).toLowerCase()) {
case ".env":
texture = CubeTexture.CreateFromPrefilteredData(props.absolutePath, scene);
break;
case ".hdr":
texture = new HDRCubeTexture(props.absolutePath, scene, 512);
break;
}

const material = new PBRMaterial("material", scene);
material.metallic = 1;
material.roughness = 0;
material.reflectionTexture = texture;
if (texture) {
texture.coordinatesMode = Texture.CUBIC_MODE;

const sphere = CreateSphere("sphere", { diameter: 100 }, scene);
sphere.material = material;
const material = new PBRMaterial("material", scene);
material.metallic = 1;
material.roughness = 0;
material.reflectionTexture = texture;

const sphere = CreateSphere("sphere", { diameter: 100 }, scene);
sphere.material = material;
}

const camera = new ArcRotateCamera("camera", Math.PI / 2, Math.PI / 2, 150, Vector3.Zero(), scene, true);
camera.lowerRadiusLimit = 75;
Expand Down
73 changes: 45 additions & 28 deletions editor/src/editor/layout/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { MdOutlineQuestionMark } from "react-icons/md";
import { GiBrickWall, GiSparkles } from "react-icons/gi";
import { HiOutlineCubeTransparent } from "react-icons/hi";
import { IoCheckmark, IoSparklesSharp } from "react-icons/io5";
import { FaCamera, FaImage, FaLightbulb, FaBone } from "react-icons/fa";
import { TbGhost2Filled, TbServerSpark, TbBrandAdobeIndesign } from "react-icons/tb";
import { FaCamera, FaImage, FaLightbulb, FaBone, FaRegLightbulb } from "react-icons/fa";

import { AdvancedDynamicTexture } from "babylonjs-gui";
import { BaseTexture, Node, Scene, Sound, Tools, IParticleSystem, Sprite, Skeleton, TransformNode } from "babylonjs";
Expand All @@ -37,6 +37,7 @@ import { registerUndoRedo } from "../../tools/undoredo";
import { isDomTextInputFocused } from "../../tools/dom";
import { isSceneLinkNode } from "../../tools/guards/scene";
import { updateAllLights } from "../../tools/light/shadows";
import { isClusteredLight } from "../../tools/light/cluster";
import { getCollisionMeshFor } from "../../tools/mesh/collision";
import { isNodeVisibleInGraph } from "../../tools/node/metadata";
import { isAdvancedDynamicTexture } from "../../tools/guards/texture";
Expand All @@ -52,6 +53,7 @@ import {
isAbstractMesh,
isAnyTransformNode,
isCamera,
isClusteredLightContainer,
isCollisionInstancedMesh,
isCollisionMesh,
isEditorCamera,
Expand Down Expand Up @@ -80,7 +82,8 @@ import { getSpriteCommands } from "../dialogs/command-palette/sprite";
import { onProjectConfigurationChangedObservable } from "../../project/configuration";

import { EditorGraphLabel } from "./graph/label";
import { EditorGraphContextMenu } from "./graph/graph";
import { EditorGraphContextMenu } from "./graph/context-menu";
import { setNewParentForGraphSelectedNodes } from "./graph/move";

export interface IEditorGraphProps {
/**
Expand Down Expand Up @@ -288,14 +291,15 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
*/
public refresh(): Promise<void> {
const scene = this.props.editor.layout.preview.scene;
const clusteredLightContainer = this.props.editor.layout.preview.clusteredLightContainer;

this._soundsList = scene.soundTracks?.map((st) => st.soundCollection).flat() ?? [];

let nodes: (TreeNodeInfo | null)[] = [];

if (this.state.showOnlyLights || this.state.showOnlyDecals) {
if (this.state.showOnlyLights) {
nodes.push(...scene.lights.map((light) => this._parseSceneNode(light, true)));
nodes.push(...scene.lights.concat(clusteredLightContainer.lights).map((light) => this._parseSceneNode(light, true)));
}

if (this.state.showOnlyDecals) {
Expand Down Expand Up @@ -350,6 +354,10 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
source = getSpriteManagerNodeFromSprite(source);
}

if (isLight(source) && this.props.editor.layout.preview.clusteredLightContainer.lights.includes(source)) {
source = this.props.editor.layout.preview.clusteredLightContainer;
}

const idsToExpand: string[] = [];

while (source) {
Expand Down Expand Up @@ -522,17 +530,17 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
return;
}

const sourcePosition = this._nodeToCopyTransform["position"];
const sourceRotation = this._nodeToCopyTransform["rotation"];
const sourceScaling = this._nodeToCopyTransform["scaling"];
const sourceRotationQuaternion = this._nodeToCopyTransform["rotationQuaternion"];
const sourceDirection = this._nodeToCopyTransform["direction"];
const sourcePosition = (this._nodeToCopyTransform as any)["position"];
const sourceRotation = (this._nodeToCopyTransform as any)["rotation"];
const sourceScaling = (this._nodeToCopyTransform as any)["scaling"];
const sourceRotationQuaternion = (this._nodeToCopyTransform as any)["rotationQuaternion"];
const sourceDirection = (this._nodeToCopyTransform as any)["direction"];

const targetPosition = node["position"];
const targetRotation = node["rotation"];
const targetScaling = node["scaling"];
const targetRotationQuaternion = node["rotationQuaternion"];
const targetDirection = node["direction"];
const targetPosition = (node as any)["position"];
const targetRotation = (node as any)["rotation"];
const targetScaling = (node as any)["scaling"];
const targetRotationQuaternion = (node as any)["rotationQuaternion"];
const targetDirection = (node as any)["direction"];

const savedTargetPosition = targetPosition?.clone();
const savedTargetRotation = targetRotation?.clone();
Expand All @@ -557,7 +565,7 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>

if (targetRotationQuaternion) {
if (!savedTargetRotationQuaternion) {
node["rotationQuaternion"] = null;
(node as any)["rotationQuaternion"] = null;
} else {
targetRotationQuaternion.copyFrom(savedTargetRotationQuaternion);
}
Expand All @@ -584,7 +592,7 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
if (targetRotationQuaternion) {
targetRotationQuaternion.copyFrom(sourceRotationQuaternion);
} else {
node["rotationQuaternion"] = sourceRotationQuaternion.clone();
(node as any)["rotationQuaternion"] = sourceRotationQuaternion.clone();
}
}

Expand Down Expand Up @@ -918,14 +926,18 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
return null;
}

if (isLight(node) && !node._scene.lights.includes(node)) {
if (isLight(node) && !node._scene.lights.includes(node) && !isClusteredLight(node, this.props.editor)) {
return null;
}

if (isCamera(node) && !node._scene.cameras.includes(node)) {
return null;
}

if (isClusteredLightContainer(node) && (this.state.showOnlyLights || this.state.showOnlyDecals)) {
return null;
}

node.id ??= Tools.RandomId();

const info = {
Expand All @@ -939,7 +951,10 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
} as TreeNodeInfo;

if (!isSceneLinkNode(node) && !noChildren) {
const children = node.getDescendants(true);
const children = isClusteredLightContainer(node)
? node.getDescendants(true)
: node.getDescendants(true, (n) => !(isLight(n) && isClusteredLight(n, this.props.editor)));

if (children.length) {
info.childNodes = children.map((c) => this._parseSceneNode(c)).filter((c) => c !== null) as TreeNodeInfo[];
}
Expand Down Expand Up @@ -976,6 +991,13 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
});
}

// Handle clustered lights
if (isClusteredLightContainer(node) && !noChildren) {
node.lights.forEach((light) => {
info.childNodes?.push(this._parseSceneNode(light, false) as TreeNodeInfo);
});
}

if (info.childNodes?.length) {
info.hasCaret = true;
} else {
Expand Down Expand Up @@ -1009,7 +1031,7 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
}

selectedNodeData.forEach((node) => {
if (isNode(node)) {
if (isNode(node) || isClusteredLightContainer(node)) {
node.setEnabled(enabled);
}
});
Expand Down Expand Up @@ -1064,6 +1086,10 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
return <FaLightbulb className="w-4 h-4" />;
}

if (isClusteredLightContainer(object)) {
return <FaRegLightbulb className="w-4 h-4" />;
}

if (isCamera(object)) {
return <FaCamera className="w-4 h-4" />;
}
Expand Down Expand Up @@ -1133,15 +1159,6 @@ export class EditorGraph extends Component<IEditorGraphProps, IEditorGraphState>
return;
}

const nodesToMove: TreeNodeInfo[] = [];
this._forEachNode(this.state.nodes, (n) => n.isSelected && nodesToMove.push(n));

nodesToMove.forEach((n) => {
if (n.nodeData && isNode(n.nodeData)) {
n.nodeData.parent = null;
}
});

this.refresh();
setNewParentForGraphSelectedNodes(this.props.editor, this.props.editor.layout.preview.scene, ev.shiftKey);
}
}
Loading