Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8ebe5d3
feat: add support of selection outliner
julien-moreau Apr 2, 2026
62555a5
feat: add support of HDR texture for environment texture and outline …
julien-moreau Apr 3, 2026
4feb201
feat: add support of HDR as environment texture when exporting scene …
julien-moreau Apr 3, 2026
b10ca01
feat: adding support of clustered lights
julien-moreau Apr 4, 2026
2296b38
fix: usage of HDR environment texture in CLI
julien-moreau Apr 5, 2026
4f3c5eb
v5.4.1-alpha.0
julien-moreau Apr 5, 2026
7c298d2
feat: make clustered light container editable in inspector
julien-moreau Apr 5, 2026
1adb62d
v5.4.1-alpha.1
julien-moreau Apr 6, 2026
1abdafb
fix: compute clustered light before checking scene is ready in babylo…
Apr 8, 2026
713f888
feat: add support of LOD inspector to setup custom LODs
Apr 8, 2026
51b4ce9
fix: compute clustered light container after parenting is resolved
julien-moreau Apr 8, 2026
efcc405
chore: use installed typescript version
julien-moreau Apr 8, 2026
a3fcc48
feat: drag'n'drop lights in clustered light container
Apr 9, 2026
67da372
v5.4.1-alpha.2
julien-moreau Apr 9, 2026
f7c2452
feat: add menu to merge animations groups in scene inspector
julien-moreau Apr 9, 2026
49a2bae
feat: add @componentFromScene decorator in babylonjs-editor-tools
Apr 10, 2026
7922584
docs: add documentation for common decorators
Apr 10, 2026
d2b8f57
fix: handle cli and tools dependency in case of mono-repo
julien-moreau Apr 10, 2026
9b99df0
v5.4.1-alpha.3
julien-moreau Apr 10, 2026
a8832d4
feat: add support of decal map in standard and pbr material inspectors
julien-moreau Apr 11, 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
2 changes: 1 addition & 1 deletion 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
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