diff --git a/cli/package.json b/cli/package.json index 365735db7..4d18a85bb 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "babylonjs-editor-cli", - "version": "5.4.3-alpha.4", + "version": "5.4.3-alpha.5", "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": { diff --git a/cli/src/pack/scene.mts b/cli/src/pack/scene.mts index 347c80f5b..ae4ebbaeb 100644 --- a/cli/src/pack/scene.mts +++ b/cli/src/pack/scene.mts @@ -88,6 +88,36 @@ export async function createBabylonScene(options: ICreateBabylonSceneOptions) { const meshesResult = await Promise.all( options.directories.meshesFiles.map(async (file) => { const data = await fs.readJSON(join(options.sceneFile, "meshes", file)); + + if (data.type === "GaussianSplattingMesh") { + data.splatDataPath = join(options.sceneName, basename(data.splatDataPath)); + + data.shDataPaths?.forEach((shDataPath: string, index: number) => { + data.shDataPaths[index] = join(options.sceneName, basename(shDataPath)); + }); + + const splatDataDestination = join(options.publicDir, data.splatDataPath); + const shDataDestinations = data.shDataPaths?.map((shDataPath: string) => join(options.publicDir, shDataPath)); + + const promises = [fs.copyFile(join(options.sceneFile, "splats", basename(data.splatDataPath)), splatDataDestination)]; + shDataDestinations?.forEach((shDataDestination: string, index: number) => { + promises.push(fs.copyFile(join(options.sceneFile, "splats", basename(data.shDataPaths[index])), shDataDestination)); + }); + + await Promise.all(promises); + + options.exportedAssets.push(splatDataDestination); + shDataDestinations?.forEach((shDataDestination: string) => { + options.exportedAssets.push(shDataDestination); + }); + + return { + mesh: data, + lodMeshes: [], + effectiveMaterials: [], + }; + } + const mesh = data.meshes[0]; if (mesh.metadata?.doNotSerialize) { @@ -650,10 +680,15 @@ export async function createBabylonScene(options: ICreateBabylonSceneOptions) { reflectionProbes: [], } as any; - // Resolve parenting for mesh instances. + // Remove debug-only scripts for scene + if (scene.metadata?.scripts?.length) { + scene.metadata.scripts = scene.metadata.scripts.filter((script) => !script.debugOnly); + } + const allNodes = [...scene.meshes, ...scene.cameras, ...scene.lights, ...scene.transformNodes, ...scene.meshes.map((m) => m.instances ?? []).flat()]; allNodes.forEach((node) => { + // Resolve parenting for mesh instances. if (node.parentId !== undefined && node.parentInstanceIndex !== undefined) { const effectiveMesh = scene.meshes.find((mesh) => { return mesh.instances?.find((instance) => instance.uniqueId === node.parentId); @@ -663,6 +698,11 @@ export async function createBabylonScene(options: ICreateBabylonSceneOptions) { node.parentId = effectiveMesh.uniqueId; } } + + // Remove debug-only scripts + if (node.metadata?.scripts?.length) { + node.metadata.scripts = node.metadata.scripts.filter((script) => !script.debugOnly); + } }); // Configue ennviornment texture diff --git a/cli/src/pack/scripts.mts b/cli/src/pack/scripts.mts index 97024138b..b03428464 100644 --- a/cli/src/pack/scripts.mts +++ b/cli/src/pack/scripts.mts @@ -106,6 +106,10 @@ export async function createScriptsFile(projectDir: string): Promise { const promises: Promise[] = []; availableMetadata.forEach((configuration) => { configuration.metadata.scripts?.forEach((script) => { + if (!script.enabled || script.debugOnly) { + return; + } + promises.push( new Promise(async (resolve) => { const path = join(projectDir, "src", script.key); diff --git a/cli/src/tools/scene.mts b/cli/src/tools/scene.mts index a61f7066e..cccdfe027 100644 --- a/cli/src/tools/scene.mts +++ b/cli/src/tools/scene.mts @@ -24,6 +24,7 @@ export async function ensureSceneDirectories(scenePath: string) { fs.ensureDir(join(scenePath, "sprite-maps")), fs.ensureDir(join(scenePath, "sprite-managers")), fs.ensureDir(join(scenePath, "nodeParticleSystemSets")), + fs.ensureDir(join(scenePath, "splats")), ]); } @@ -48,6 +49,7 @@ export async function readSceneDirectories(scenePath: string) { spriteManagerFiles, geometryFiles, nodeParticleSystemSetFiles, + gaussianSplattingFiles, ] = await Promise.all([ readdir(join(scenePath, "nodes")), readdir(join(scenePath, "meshes")), @@ -68,6 +70,7 @@ export async function readSceneDirectories(scenePath: string) { readdir(join(scenePath, "sprite-managers")), readdir(join(scenePath, "geometries")), readdir(join(scenePath, "nodeParticleSystemSets")), + readdir(join(scenePath, "splats")), ]); return { @@ -90,5 +93,6 @@ export async function readSceneDirectories(scenePath: string) { spriteManagerFiles, geometryFiles, nodeParticleSystemSetFiles, + gaussianSplattingFiles, }; } diff --git a/editor/package.json b/editor/package.json index 0e302a04d..02c08a89c 100644 --- a/editor/package.json +++ b/editor/package.json @@ -1,6 +1,6 @@ { "name": "babylonjs-editor", - "version": "5.4.3-alpha.4", + "version": "5.4.3-alpha.5", "description": "Babylon.js Editor is a Web Application helping artists to work with Babylon.js", "productName": "Babylon.js Editor", "main": "build/src/index.js", @@ -31,7 +31,7 @@ "@types/react-dom": "18.2.18", "@vitest/coverage-v8": "4.0.17", "concurrently": "9.2.0", - "electron": "39.8.5", + "electron": "42.8.0", "electron-builder": "26.0.12", "electron-reloader": "1.2.3", "postcss-import": "16.1.0", @@ -40,6 +40,7 @@ "vitest": "4.1.0" }, "dependencies": { + "@adobe/spz": "0.2.2", "@babylonjs/addons": "9.12.1", "@babylonjs/core": "9.12.1", "@babylonjs/havok": "1.3.12", @@ -98,8 +99,9 @@ "decompress-targz": "^4.1.1", "dotenv": "17.2.3", "dunder-proto": "1.0.1", - "electron-updater": "6.6.2", + "electron-updater": "6.8.9", "esbuild": "0.28.1", + "fflate": "0.8.3", "filenamify": "4.3.0", "flexlayout-react": "0.7.15", "fluent-ffmpeg": "^2.1.3", @@ -111,7 +113,7 @@ "md5": "2.3.0", "motion": "12.23.24", "next-themes": "^0.3.0", - "node-pty": "1.2.0-beta.12", + "node-pty": "1.2.0-beta.15", "node-stream-zip": "1.15.0", "pngjs": "7.0.0", "react": "18.2.0", diff --git a/editor/src/editor/layout.tsx b/editor/src/editor/layout.tsx index e5e736d0a..3ff34e716 100644 --- a/editor/src/editor/layout.tsx +++ b/editor/src/editor/layout.tsx @@ -1,7 +1,7 @@ import { platform } from "os"; import { Component, ReactNode } from "react"; -import { Actions, IJsonModel, Layout, Model, TabNode, TabSetNode } from "flexlayout-react"; +import { Actions, ICloseType, IJsonModel, Layout, Model, TabNode, TabSetNode } from "flexlayout-react"; import { Observable, Tools } from "babylonjs"; import { ipcRenderer } from "electron"; @@ -204,7 +204,7 @@ export class EditorLayout extends Component { * @param component defines the reference to the React component to draw in. * @param options defines the options of the tab such as the title etc. */ - public addLayoutTab(component: ReactNode, options: IEditorLayoutTabOptions): void { + public addLayoutTab(component: ReactNode, options: IEditorLayoutTabOptions): string { options.id ??= Tools.RandomId(); const activeTabId = this._layoutRef?.props.model.getActiveTabset()?.getSelectedNode()?.getId(); @@ -238,11 +238,14 @@ export class EditorLayout extends Component { type: "tab", component: options.id, enableClose: options.enableClose, + closeType: ICloseType.Visible, }); if (activeTabId && !options.setAsActiveTab) { this._layoutRef?.props.model.doAction(Actions.selectTab(activeTabId)); } + + return options.id; } /** diff --git a/editor/src/editor/layout/assets-browser.tsx b/editor/src/editor/layout/assets-browser.tsx index c46e29888..a482e4be2 100644 --- a/editor/src/editor/layout/assets-browser.tsx +++ b/editor/src/editor/layout/assets-browser.tsx @@ -841,13 +841,14 @@ export class EditorAssetsBrowser extends Component this._handleAddNodeParticleSystem()}>Node Particle System + + this._handleAddNavmesh()}>Navmesh + {this.props.editor.state.enableExperimentalFeatures && ( <> this._handleAddCinematic()}>Cinematic - this._handleAddNavmesh()}>Navmesh - this._handleAddRagdoll()}>Ragdoll )} @@ -895,6 +896,10 @@ export class EditorAssetsBrowser extends Component; @@ -1474,6 +1479,10 @@ export class EditorAssetsBrowser extends Component { + const existingConfiguration = editedNavMeshConfigurations.find((c) => c.absolutePath === this.props.absolutePath); + if (existingConfiguration) { + this.props.editor.layout.selectTab(existingConfiguration.tabId); + return; + } + const data = await readJSON(join(this.props.absolutePath, "config.json")); - this.props.editor.layout.addLayoutTab(, { - setAsActiveTab: true, - title: "NavMesh Editor", + const tabId = this.props.editor.layout.addLayoutTab( + { + const index = editedNavMeshConfigurations.findIndex((c) => c.tabId === tabId); + if (index !== -1) { + editedNavMeshConfigurations.splice(index, 1); + } + }} + />, + { + setAsActiveTab: true, + title: "NavMesh Editor", + neighborId: "inspector", + } + ); + + editedNavMeshConfigurations.push({ + tabId, + absolutePath: this.props.absolutePath, }); } } diff --git a/editor/src/editor/layout/assets-browser/viewers/model-viewer.tsx b/editor/src/editor/layout/assets-browser/viewers/model-viewer.tsx index 6a41adad3..17a5edaf4 100644 --- a/editor/src/editor/layout/assets-browser/viewers/model-viewer.tsx +++ b/editor/src/editor/layout/assets-browser/viewers/model-viewer.tsx @@ -9,6 +9,7 @@ import { showAlert } from "../../../../ui/dialog"; import { Progress } from "../../../../ui/shadcn/ui/progress"; import { isMesh } from "../../../../tools/guards/nodes"; +import { getLoaderPluginOptions } from "../../../../tools/assets/loader"; import { projectConfiguration } from "../../../../project/configuration"; @@ -73,6 +74,7 @@ function AssetBrowserModelViewer(props: IAssetBrowserModelViewerProps) { await AppendSceneAsync(source, scene, { rootUrl, onProgress: (ev) => setProgress((ev.loaded / ev.total) * 100), + pluginOptions: getLoaderPluginOptions(props.editor.path ?? ""), }); if (props.options?.overrideMaterialAbsolutePath) { diff --git a/editor/src/editor/layout/graph.tsx b/editor/src/editor/layout/graph.tsx index 20eed09af..0cc09fe41 100644 --- a/editor/src/editor/layout/graph.tsx +++ b/editor/src/editor/layout/graph.tsx @@ -1,17 +1,17 @@ import { extname } from "path/posix"; import { Component, DragEvent, ReactNode } from "react"; -import { Button, Tree, TreeNodeInfo } from "@blueprintjs/core"; +import { Button as BPButton, Tree, TreeNodeInfo } from "@blueprintjs/core"; import { FaLink } from "react-icons/fa6"; import { IoMdCube } from "react-icons/io"; import { AiOutlinePlus } from "react-icons/ai"; import { HiSpeakerWave } from "react-icons/hi2"; import { SiBabylondotjs } from "react-icons/si"; -import { MdOutlineQuestionMark } from "react-icons/md"; import { GiBrickWall, GiSparkles } from "react-icons/gi"; import { HiOutlineCubeTransparent } from "react-icons/hi"; import { IoCheckmark, IoPlay, IoSparklesSharp } from "react-icons/io5"; +import { MdOutlineQuestionMark, MdOutlineRefresh } from "react-icons/md"; import { TbGhost2Filled, TbServerSpark, TbBrandAdobeIndesign } from "react-icons/tb"; import { FaCamera, FaImage, FaLightbulb, FaBone, FaRegLightbulb } from "react-icons/fa"; @@ -21,6 +21,7 @@ import { BaseTexture, Node, Scene, Tools, IParticleSystem, Sprite, Skeleton, Tra import { Editor } from "../main"; import { Badge } from "../../ui/shadcn/ui/badge"; +import { Button } from "../../ui/shadcn/ui/button"; import { SpinnerUIComponent } from "../../ui/spinner"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "../../ui/shadcn/ui/dropdown-menu"; import { @@ -60,6 +61,7 @@ import { isCollisionInstancedMesh, isCollisionMesh, isEditorCamera, + isGaussianSplattingPartProxyMesh, isInstancedMesh, isLight, isMesh, @@ -90,6 +92,7 @@ import { applySoundAsset } from "./preview/import/sound"; import { EditorGraphLabel } from "./graph/label"; import { EditorGraphContextMenu } from "./graph/context-menu"; import { setNewParentForGraphSelectedNodes } from "./graph/move"; +import { addGaussianSplattingMeshPartProxyMesh } from "../../tools/mesh/gaussian-splatting"; export interface IEditorGraphProps { /** @@ -135,6 +138,11 @@ export interface IEditorGraphState { * Defines the reference to the play scene if the player is running, null otherwise. */ playScene: Scene | null; + /** + * Defines wether or not the graph should automatically refresh when the play scene is running. This is useful to see changes in the graph when the play scene is running. + * But in case of performance issues, this can be disabled to improve performance in-game. + */ + autoRefreshPlayScene: boolean; } export class EditorGraph extends Component { @@ -158,6 +166,7 @@ export class EditorGraph extends Component playScene: null, isLoading: false, + autoRefreshPlayScene: true, }; onNodesAddedObservable.add(() => this.refresh()); @@ -187,7 +196,17 @@ export class EditorGraph extends Component Runtime scene. Changes are not saved. -
+ +
+ {!this.state.autoRefreshPlayScene && ( + + )} + +
)} @@ -203,7 +222,7 @@ export class EditorGraph extends Component - + + setEnabled(v)} /> -
props.onRemove()}> +
props.onRemove()}>
diff --git a/editor/src/editor/layout/navmesh/editor.tsx b/editor/src/editor/layout/navmesh/editor.tsx index 1add38f7a..83968174e 100644 --- a/editor/src/editor/layout/navmesh/editor.tsx +++ b/editor/src/editor/layout/navmesh/editor.tsx @@ -19,7 +19,7 @@ import { Editor } from "../../main"; import { INavMeshConfiguration } from "./types"; import { NavMeshEditorToolbar } from "./toolbar"; -import { NavMeshEditorPreview } from "./preview"; +// import { NavMeshEditorPreview } from "./preview"; import { NavMeshEditorMeshesList } from "./meshes"; import { NavMeshEditorObstacles } from "./obstacles"; import { NavMeshEditorInspector } from "./inspector"; @@ -30,6 +30,8 @@ export interface INavmeshEditorProps { configuration: INavMeshConfiguration; absolutePath: string; + + onClose: () => void; } export interface INavmeshEditorState { @@ -38,7 +40,7 @@ export interface INavmeshEditorState { export class NavMeshEditor extends Component { public configuration: INavMeshConfiguration; - public plugin: RecastNavigationJSPluginV2; + public plugin: RecastNavigationJSPluginV2 | null = null; public result: CreateNavMeshResult | null = null; @@ -64,13 +66,21 @@ export class NavMeshEditor extends Component -
- +
Navmesh Editor
+
+ + {/*
+ + + + + +
*/}
); } @@ -78,11 +88,23 @@ export class NavMeshEditor extends Component { await this._createPlugin(); + // Clean data + this.props.configuration.staticMeshes = this.props.configuration.staticMeshes.filter((mesh) => { + const sceneMesh = this.props.editor.layout.preview.scene.getMeshById(mesh.id); + return sceneMesh !== null; + }); + + this.props.configuration.obstacleMeshes = this.props.configuration.obstacleMeshes.filter((mesh) => { + const sceneMesh = this.props.editor.layout.preview.scene.getMeshById(mesh.id); + return sceneMesh !== null; + }); + + // Build navmesh from data if exists const navMeshBinPath = join(this.props.absolutePath, "navmesh.bin"); const tilecacheBinPath = join(this.props.absolutePath, "tilecache.bin"); if ((await pathExists(navMeshBinPath)) && (await pathExists(tilecacheBinPath))) { - this.plugin.buildFromNavmeshData(await readFile(navMeshBinPath)); - this.plugin.buildFromTileCacheData(await readFile(tilecacheBinPath)); + this.plugin!.buildFromNavmeshData(await readFile(navMeshBinPath)); + this.plugin!.buildFromTileCacheData(await readFile(tilecacheBinPath)); this._createDebugNavMesh(); this.createDebugObstacles(); } @@ -96,6 +118,8 @@ export class NavMeshEditor extends Component {} @@ -110,7 +134,7 @@ export class NavMeshEditor extends Component { try { - await Promise?.all([ + const promises = [ writeJSON(join(this.props.absolutePath, "config.json"), this.configuration, { spaces: "\t", encoding: "utf-8", }), - writeFile(join(this.props.absolutePath, "navmesh.bin"), Buffer.from(this.plugin.getNavmeshData())), - writeFile(join(this.props.absolutePath, "tilecache.bin"), Buffer.from(this.plugin.getTileCacheData())), - ]); + ]; + + if (this.plugin) { + promises.push( + writeFile(join(this.props.absolutePath, "navmesh.bin"), Buffer.from(this.plugin.getNavmeshData())), + writeFile(join(this.props.absolutePath, "tilecache.bin"), Buffer.from(this.plugin.getTileCacheData())) + ); + } + + await Promise.all(promises); toast.success("NavMesh saved successfully."); } catch (e) { @@ -237,6 +268,7 @@ export class NavMeshEditor extends Component -
Inspector
+
+ {/*
Inspector
*/} diff --git a/editor/src/editor/layout/navmesh/meshes.tsx b/editor/src/editor/layout/navmesh/meshes.tsx index 0305b894e..2cd2295b8 100644 --- a/editor/src/editor/layout/navmesh/meshes.tsx +++ b/editor/src/editor/layout/navmesh/meshes.tsx @@ -19,10 +19,9 @@ export function NavMeshEditorMeshesList(props: INavMeshEditorMeshesListProps) { const [search, setSearch] = useState(""); return ( -
+
-
Static meshes
- +
Static meshes
diff --git a/editor/src/editor/layout/navmesh/obstacles.tsx b/editor/src/editor/layout/navmesh/obstacles.tsx index 1bf698763..214880dcd 100644 --- a/editor/src/editor/layout/navmesh/obstacles.tsx +++ b/editor/src/editor/layout/navmesh/obstacles.tsx @@ -17,9 +17,9 @@ export function NavMeshEditorObstacles(props: INavMeshEditorObstaclesProps) { const [search, setSearch] = useState(""); return ( -
+
-
Obstacle meshes
+
Obstacle meshes
diff --git a/editor/src/editor/layout/navmesh/preview.tsx b/editor/src/editor/layout/navmesh/preview.tsx index 8edd5f9f7..191200470 100644 --- a/editor/src/editor/layout/navmesh/preview.tsx +++ b/editor/src/editor/layout/navmesh/preview.tsx @@ -5,7 +5,7 @@ import { Engine, Scene, Mesh, StandardMaterial, Color3, Vector3, ArcRotateCamera export interface INavMeshEditorPreviewProps { mesh: Mesh | null; - plugin: RecastNavigationJSPluginV2; + plugin: RecastNavigationJSPluginV2 | null; } export function NavMeshEditorPreview(props: INavMeshEditorPreviewProps) { @@ -64,7 +64,10 @@ export function NavMeshEditorPreview(props: INavMeshEditorPreviewProps) { useEffect(() => { if (scene && props.mesh) { - const mesh = props.plugin.createDebugNavMesh(scene); + const mesh = props.plugin?.createDebugNavMesh(scene); + if (!mesh) { + return; + } const debugMaterial = new StandardMaterial("navmesh-debug-material", scene); debugMaterial.emissiveColor = Color3.Magenta(); diff --git a/editor/src/editor/layout/preview.tsx b/editor/src/editor/layout/preview.tsx index e6550581d..ba8f8e67f 100644 --- a/editor/src/editor/layout/preview.tsx +++ b/editor/src/editor/layout/preview.tsx @@ -37,6 +37,7 @@ import { ClusteredLightContainer, Tools, _GetAudioEngine, + GaussianSplattingCompoundMesh, } from "babylonjs"; import { SpinnerUIComponent } from "../../ui/spinner"; @@ -191,6 +192,11 @@ export class EditorPreview extends Component { if (pick.pickedPoint) { diff --git a/editor/src/editor/layout/preview/import/import.ts b/editor/src/editor/layout/preview/import/import.ts index b5bc3273d..c88e97d9d 100644 --- a/editor/src/editor/layout/preview/import/import.ts +++ b/editor/src/editor/layout/preview/import/import.ts @@ -1,5 +1,5 @@ import { isAbsolute } from "path"; -import { join, dirname, basename } from "path/posix"; +import { join, dirname, basename, extname } from "path/posix"; import { pathExists, readFile, readJSON, writeFile } from "fs-extra"; import axios from "axios"; @@ -20,19 +20,24 @@ import { Sprite, IParticleSystem, HDRCubeTexture, + GaussianSplattingMesh, } from "babylonjs"; import { UniqueNumber } from "../../../../tools/tools"; -import { isMesh } from "../../../../tools/guards/nodes"; import { isSprite } from "../../../../tools/guards/sprites"; import { isTexture } from "../../../../tools/guards/texture"; import { executeSimpleWorker } from "../../../../tools/worker"; import { isMultiMaterial } from "../../../../tools/guards/material"; +import { getLoaderPluginOptions } from "../../../../tools/assets/loader"; import { configureSimultaneousLightsForMaterial } from "../../../../tools/material/material"; import { onNodesAddedObservable, onTextureAddedObservable } from "../../../../tools/observables"; +import { addGaussianSplattingMeshPartProxyMesh } from "../../../../tools/mesh/gaussian-splatting"; +import { isGaussianSplattingMesh, isGaussianSplattingPartProxyMesh, isMesh } from "../../../../tools/guards/nodes"; import { projectConfiguration } from "../../../../project/configuration"; +import { Editor } from "../../../main"; + export async function tryConvertSceneFile(absolutePath: string, progress?: (percent: number) => void) { const toolsUrl = process.env.EDITOR_TOOLS_URL ?? "https://editor.babylonjs.com"; const buffer = (await readFile(absolutePath)) as Buffer; @@ -62,7 +67,7 @@ export async function tryConvertSceneFile(absolutePath: string, progress?: (perc } } -export async function loadImportedSceneFile(scene: Scene, absolutePath: string) { +export async function loadImportedSceneFile(scene: Scene, absolutePath: string, editor: Editor) { if (!projectConfiguration.path) { return null; } @@ -70,10 +75,29 @@ export async function loadImportedSceneFile(scene: Scene, absolutePath: string) let result: ISceneLoaderAsyncResult; try { + let gaussianSplattingMesh: GaussianSplattingMesh | undefined = undefined; + + switch (extname(absolutePath).toLowerCase()) { + case ".ply": + case ".sog": + case ".spz": + case ".splat": + gaussianSplattingMesh = new GaussianSplattingMesh(basename(absolutePath), null, scene, true); + break; + } + + const pluginOptions = getLoaderPluginOptions(editor.path ?? ""); + result = await ImportMeshAsync(basename(absolutePath), scene, { rootUrl: join(dirname(absolutePath), "/"), + pluginOptions: { + ...pluginOptions, + splat: { + gaussianSplattingMesh, + ...pluginOptions.splat, + }, + }, }); - // result = await SceneLoader.ImportMeshAsync("", join(dirname(absolutePath), "/"), basename(absolutePath), scene); } catch (e) { console.error(e); toast.error("Failed to load the scene file."); @@ -89,10 +113,34 @@ export async function loadImportedSceneFile(scene: Scene, absolutePath: string) // cleanImportedGltf(result); } - result.meshes.forEach((mesh) => { + for (const mesh of result.meshes) { configureImportedNodeIds(mesh); - mesh.receiveShadows = true; + if (isGaussianSplattingMesh(mesh)) { + const meshIndex = result.meshes.indexOf(mesh); + if (meshIndex !== -1) { + result.meshes.splice(meshIndex, 1); + } + + scene.removeMesh(mesh); + + const proxyMesh = addGaussianSplattingMeshPartProxyMesh(mesh, editor); + if (proxyMesh) { + proxyMesh.scaling.scaleInPlace(100); + + switch (extname(absolutePath).toLowerCase()) { + case ".sog": + case ".spz": + proxyMesh.rotation.x = Math.PI; + break; + } + + configureImportedNodeIds(proxyMesh); + result.meshes.push(proxyMesh); + } + } else { + mesh.receiveShadows = true; + } if (mesh.skeleton) { mesh.skeleton.id = Tools.RandomId(); @@ -114,7 +162,7 @@ export async function loadImportedSceneFile(scene: Scene, absolutePath: string) target.name = `${mesh.name}_${target.name}`; } } - }); + } result.lights.forEach((light) => configureImportedNodeIds(light)); result.transformNodes.forEach((transformNode) => configureImportedNodeIds(transformNode)); @@ -127,7 +175,9 @@ export async function loadImportedSceneFile(scene: Scene, absolutePath: string) } result.meshes.forEach((mesh) => { - shadowMap.renderList!.push(mesh); + if (!isGaussianSplattingPartProxyMesh(mesh) && !isGaussianSplattingMesh(mesh)) { + shadowMap.renderList!.push(mesh); + } }); }); diff --git a/editor/src/editor/layout/preview/play.tsx b/editor/src/editor/layout/preview/play.tsx index 141a68d23..46a7dfa4e 100644 --- a/editor/src/editor/layout/preview/play.tsx +++ b/editor/src/editor/layout/preview/play.tsx @@ -6,6 +6,7 @@ import { basename, dirname, join } from "path/posix"; import { Button } from "@blueprintjs/core"; import { Component, ReactNode } from "react"; +import { toast } from "sonner"; import { Grid } from "react-loader-spinner"; import { IoPlay, IoStop, IoRefresh } from "react-icons/io5"; @@ -273,6 +274,7 @@ export class EditorPreviewPlayComponent extends Component { - exportProject(this.props.editor, { optimize: false })}> + exportProject(this.props.editor, { optimize: false, debugMode: false })}> Generate Current Scene CTRL+G this.props.editor.setState({ generateProject: true })}>Generate All Scenes and Assets... diff --git a/editor/src/editor/main.tsx b/editor/src/editor/main.tsx index b5234b6c0..7e12c0f05 100644 --- a/editor/src/editor/main.tsx +++ b/editor/src/editor/main.tsx @@ -235,7 +235,7 @@ export class Editor extends Component { public async componentDidMount(): Promise { ipcRenderer.on("save", () => saveProject(this)); - ipcRenderer.on("generate", () => exportProject(this, { optimize: false })); + ipcRenderer.on("generate", () => exportProject(this, { optimize: false, debugMode: false })); ipcRenderer.on("editor:edit-project", () => this.setState({ editProject: true })); ipcRenderer.on("editor:edit-preferences", () => this.setState({ editPreferences: true })); diff --git a/editor/src/editor/nodes/sound.ts b/editor/src/editor/nodes/sound.ts index 2f9f56412..7daac5b7b 100644 --- a/editor/src/editor/nodes/sound.ts +++ b/editor/src/editor/nodes/sound.ts @@ -187,6 +187,10 @@ export class SoundNode extends TransformNode { super.dispose(false, true); } + public clone(name: string): SoundNode { + return SerializationHelper.Clone(() => new SoundNode(name, this.getScene()), this); + } + /** * Gets the current object class name. * @return the class name diff --git a/editor/src/loader/assimpjs.ts b/editor/src/loader/assimpjs.ts index 25593ef79..81ee29251 100644 --- a/editor/src/loader/assimpjs.ts +++ b/editor/src/loader/assimpjs.ts @@ -20,9 +20,9 @@ export class AssimpJSLoader implements ISceneLoaderPluginAsync { ".x": { isBinary: true, }, - // ".fbx": { - // isBinary: true, - // }, + ".fbx": { + isBinary: true, + }, ".3ds": { isBinary: true, }, diff --git a/editor/src/loader/maps.ts b/editor/src/loader/maps.ts index 1afb35d17..078193529 100644 --- a/editor/src/loader/maps.ts +++ b/editor/src/loader/maps.ts @@ -6,4 +6,5 @@ export const materialPropertyMap: Record = { "$raw.SpecularColor|file": "reflectivityTexture", "$raw.AmbientColor|file": "ambientTexture", "$raw.Bump|file": "bumpTexture", + "$raw.NormalMap|file": "bumpTexture", }; diff --git a/editor/src/loader/material.ts b/editor/src/loader/material.ts index e5db4e918..609945314 100644 --- a/editor/src/loader/material.ts +++ b/editor/src/loader/material.ts @@ -19,6 +19,7 @@ export function parseMaterial(runtime: AssimpJSRuntime, data: IAssimpJSMaterialD // Textures case "$raw.Bump|file": + case "$raw.NormalMap|file": case "$raw.DiffuseColor|file": case "$raw.AmbientColor|file": case "$raw.SpecularColor|file": diff --git a/editor/src/mcp/assets/assets.ts b/editor/src/mcp/assets/assets.ts index 00d901261..dc8edbdf1 100644 --- a/editor/src/mcp/assets/assets.ts +++ b/editor/src/mcp/assets/assets.ts @@ -152,7 +152,7 @@ export async function getAssetPreview(_scene: Scene, data: any): Promise { export async function instantiateMeshAsset(scene: Scene, data: any, options: IMCPActionOptions): Promise { const absolutePath = resolveProjectPath(data.path); - const result = await loadImportedSceneFile(scene, absolutePath); + const result = await loadImportedSceneFile(scene, absolutePath, options.editor); if (!result) { throw new Error(`Failed to load mesh asset: ${data.path}`); } diff --git a/editor/src/mcp/lights/lights.ts b/editor/src/mcp/lights/lights.ts index d5cf64f5e..0b7201471 100644 --- a/editor/src/mcp/lights/lights.ts +++ b/editor/src/mcp/lights/lights.ts @@ -1,6 +1,6 @@ import { Scene, Node, Light, ShadowGenerator, CascadedShadowGenerator, IShadowLight, DirectionalLight, SpotLight, PointLight } from "babylonjs"; -import { isLight } from "../../tools/guards/nodes"; +import { isGaussianSplattingMesh, isGaussianSplattingPartProxyMesh, isLight } from "../../tools/guards/nodes"; import { addPointLight, addDirectionalLight, addSpotLight, addHemisphericLight } from "../../project/add/light"; @@ -133,7 +133,7 @@ export function setLightShadows(scene: Scene, data: any, options: IMCPActionOpti generator.setDarkness(data.darkness); } - generator.getShadowMap()?.renderList?.push(...scene.meshes); + generator.getShadowMap()?.renderList?.push(...scene.meshes.filter((m) => !isGaussianSplattingPartProxyMesh(m) && !isGaussianSplattingMesh(m))); } options.editor.layout.inspector.setEditedObject(node); diff --git a/editor/src/project/export/export.tsx b/editor/src/project/export/export.tsx index 786aae63d..e8bebd418 100644 --- a/editor/src/project/export/export.tsx +++ b/editor/src/project/export/export.tsx @@ -1,7 +1,7 @@ import { join, dirname, basename, extname } from "path/posix"; -import { readJSON, readdir, remove, writeJSON } from "fs-extra"; +import { readJSON, readdir, remove, writeFile, writeJSON } from "fs-extra"; -import { RenderTargetTexture, SceneSerializer } from "babylonjs"; +import { RenderTargetTexture, SceneSerializer, GaussianSplattingMesh } from "babylonjs"; import { toast } from "sonner"; @@ -11,8 +11,8 @@ import { getCollisionMeshFor } from "../../tools/mesh/collision"; import { storeTexturesBaseSize } from "../../tools/material/texture"; import { extractNodeMaterialTextures } from "../../tools/material/extract"; import { createDirectoryIfNotExist, normalizedGlob } from "../../tools/fs"; -import { isCollisionMesh, isEditorCamera, isMesh } from "../../tools/guards/nodes"; import { extractNodeParticleSystemSetTextures, extractParticleSystemTextures } from "../../tools/particles/extract"; +import { isCollisionMesh, isEditorCamera, isGaussianSplattingPartProxyMesh, isMesh } from "../../tools/guards/nodes"; import { taaPipelineCameraConfigurations } from "../../editor/rendering/taa"; import { vlsPostProcessCameraConfigurations } from "../../editor/rendering/vls"; @@ -38,6 +38,7 @@ import { ExportSceneProgressComponent, showExportSceneProgressDialog } from "./d export type IExportProjectOptions = { optimize: boolean; + debugMode: boolean; noDialog?: boolean; noProgress?: boolean; }; @@ -113,7 +114,7 @@ async function _exportProject(editor: Editor, options: IExportProjectOptions): P storeTexturesBaseSize(scene); - scene.meshes.forEach((mesh) => (mesh.doNotSerialize = mesh.metadata?.doNotSerialize ?? false)); + scene.meshes.forEach((mesh) => (mesh.doNotSerialize = ((mesh.metadata?.doNotSerialize ?? false) || mesh.reservedDataStore?.hidden) ?? false)); scene.lights.forEach((light) => (light.doNotSerialize = light.metadata?.doNotSerialize ?? false)); scene.cameras.forEach((camera) => (camera.doNotSerialize = camera.metadata?.doNotSerialize ?? false)); scene.transformNodes.forEach((transformNode) => (transformNode.doNotSerialize = transformNode.metadata?.doNotSerialize ?? false)); @@ -263,6 +264,72 @@ async function _exportProject(editor: Editor, options: IExportProjectOptions): P }) ); + // Add gaussian splatting meshes to the list + const computedGaussianSplattingMeshes: GaussianSplattingMesh[] = []; + + await Promise.all( + scene.meshes.map(async (mesh) => { + if (!isGaussianSplattingPartProxyMesh(mesh) || !mesh.baseGaussianSplattingMesh?.splatsData) { + return; + } + + if (computedGaussianSplattingMeshes.includes(mesh.baseGaussianSplattingMesh)) { + return; + } + + computedGaussianSplattingMeshes.push(mesh.baseGaussianSplattingMesh); + + const splatDataPath = join(scenePath, sceneName, `${mesh.baseGaussianSplattingMesh.id}.babylonbinarysplatdata`); + const shPaths = mesh.baseGaussianSplattingMesh.shData?.map((_, index) => + join(scenePath, sceneName, `${mesh.baseGaussianSplattingMesh!.id}-sh${index}.babylonbinarysplatshdata`) + ); + + try { + const promises = [writeFile(splatDataPath, Buffer.from(mesh.baseGaussianSplattingMesh.splatsData))]; + + mesh.baseGaussianSplattingMesh.shData?.forEach((shData, index) => { + promises.push(writeFile(shPaths![index], Buffer.from(shData))); + }); + + await Promise.all(promises); + + const gaussianSplatData = mesh.baseGaussianSplattingMesh.serialize( + { + proxies: [], + metadata: mesh.metadata, + isEnabled: mesh.isEnabled(false), + splatDataPath: `${sceneName}/${mesh.baseGaussianSplattingMesh.id}.babylonbinarysplatdata`, + shDataPaths: mesh.baseGaussianSplattingMesh.shData?.map( + (_, index) => `${sceneName}/${mesh.baseGaussianSplattingMesh!.id}-sh${index}.babylonbinarysplatshdata` + ), + }, + "binary" + ); + + delete gaussianSplatData.shData; + delete gaussianSplatData.splatsData; + + const allMeshProxies = scene.meshes.filter((m) => isGaussianSplattingPartProxyMesh(m) && m.baseGaussianSplattingMesh === mesh.baseGaussianSplattingMesh); + allMeshProxies.forEach((proxy) => { + const proxyData = proxy.serialize(); + proxyData.parentId = proxy.parent?.id; + delete proxyData.compoundSplatMeshId; + gaussianSplatData.proxies.push(proxyData); + }); + + data.meshes?.push(gaussianSplatData); + + savedGeometries.push(`${mesh.baseGaussianSplattingMesh.id}.babylonbinarysplatdata`); + + mesh.baseGaussianSplattingMesh.shData?.forEach((_, index) => { + savedGeometries.push(`${mesh.baseGaussianSplattingMesh!.id}-sh${index}.babylonbinarysplatshdata`); + }); + } catch (e) { + editor.layout.console.error(`Export: Failed to write gaussian splatting data for mesh ${mesh.name}`); + } + }) + ); + // Configure lights data.shadowGenerators?.forEach((shadowGenerator) => { const instantiatedLight = scene.getLightById(shadowGenerator.lightId); @@ -349,7 +416,7 @@ async function _exportProject(editor: Editor, options: IExportProjectOptions): P }); // Export scripts - await handleExportScripts(editor); + await handleExportScripts(editor, options.debugMode); // Export assets const promises: Promise[] = []; diff --git a/editor/src/project/export/scripts.ts b/editor/src/project/export/scripts.ts index e4e2d727d..4109f4d87 100644 --- a/editor/src/project/export/scripts.ts +++ b/editor/src/project/export/scripts.ts @@ -26,6 +26,10 @@ import { getVLSPostProcess, } from "babylonjs-editor-tools"; +// Request all plugins +import "babylonjs-editor-tools/loading/gaussian-splatting"; +import "babylonjs-editor-tools/loading/script/preload/plugins/navmesh"; + {{imports}} export const scriptsMap = { @@ -62,7 +66,7 @@ interface ICollectedMetadata { metadata: any; } -export async function handleExportScripts(editor: Editor): Promise { +export async function handleExportScripts(editor: Editor, debugMode: boolean): Promise { if (!editor.state.projectPath) { return; } @@ -74,12 +78,15 @@ export async function handleExportScripts(editor: Editor): Promise { }); const scriptsMap: Record = {}; - const availableMetadata: ICollectedMetadata[] = []; // Check on all scenes in assets await Promise.all( sceneFolders.map(async (file) => { + if (file === editor.state.lastOpenedScenePath) { + return; + } + try { const config = await readJSON(join(file, "config.json")); if (config.metadata) { @@ -141,6 +148,13 @@ export async function handleExportScripts(editor: Editor): Promise { ); // Check on all nodes in current scene + if (editor.layout.preview.scene.metadata) { + availableMetadata.push({ + entityName: "currentScene", + metadata: editor.layout.preview.scene.metadata, + }); + } + const entities = [ ...editor.layout.preview.scene.meshes, ...editor.layout.preview.scene.lights, @@ -165,6 +179,10 @@ export async function handleExportScripts(editor: Editor): Promise { const promises: Promise[] = []; availableMetadata.forEach((configuration) => { configuration.metadata.scripts?.forEach((script) => { + if (!script.enabled || (!debugMode && script.debugOnly)) { + return; + } + promises.push( new Promise(async (resolve) => { const path = join(projectPath, "src", script.key); diff --git a/editor/src/project/load/plugins/meshes.ts b/editor/src/project/load/plugins/meshes.ts index 449f34a3c..4dfc8333a 100644 --- a/editor/src/project/load/plugins/meshes.ts +++ b/editor/src/project/load/plugins/meshes.ts @@ -1,7 +1,7 @@ import { join } from "path/posix"; -import { readJSON } from "fs-extra"; +import { readFile, readJSON } from "fs-extra"; -import { Scene, Constants, Matrix, Mesh, SceneLoader, MultiMaterial, Geometry } from "babylonjs"; +import { Scene, Constants, Matrix, Mesh, SceneLoader, MultiMaterial, Geometry, GaussianSplattingMesh, GaussianSplattingPartProxyMesh } from "babylonjs"; import { ISceneLoaderPluginOptions } from "../scene"; @@ -10,10 +10,13 @@ import { isCollisionMesh, isMesh } from "../../../tools/guards/nodes"; import { isMultiMaterial, isNodeMaterial } from "../../../tools/guards/material"; import { parsePhysicsAggregate } from "../../../tools/physics/serialization/aggregate"; import { configureSimultaneousLightsForMaterial, normalizeNodeMaterialUniqueIds } from "../../../tools/material/material"; +import { addGaussianSplattingMeshPartProxyMesh, configureGaussianSplattingMeshFromData } from "../../../tools/mesh/gaussian-splatting"; import { CollisionMesh } from "../../../editor/nodes/collision"; -export async function loadMeshes(meshesFiles: string[], scene: Scene, options: ISceneLoaderPluginOptions) { +import { Editor } from "../../../editor/main"; + +export async function loadMeshes(editor: Editor, meshesFiles: string[], scene: Scene, options: ISceneLoaderPluginOptions) { const loadedMeshes = await Promise.all( meshesFiles.map(async (file) => { if (file.startsWith(".")) { @@ -26,6 +29,37 @@ export async function loadMeshes(meshesFiles: string[], scene: Scene, options: I return; } + if (initialData.type === "GaussianSplattingMesh") { + const promises = [readFile(join(options.projectPath, initialData.splatDataPath))]; + + initialData.shDataPaths?.forEach((shDataPath) => { + promises.push(readFile(join(options.projectPath, shDataPath))); + }); + + const [splatBuffer, ...shData] = await Promise.all(promises); + + initialData.splatsData = splatBuffer.buffer; + initialData.shData = shData?.map((buffer) => new Uint8Array(buffer.buffer)); + + const parsedMesh = GaussianSplattingMesh.Parse(initialData, scene); + parsedMesh.id = initialData.id; + parsedMesh.uniqueId = initialData.uniqueId; + + scene.removeMesh(parsedMesh); + + const proxyMeshes: GaussianSplattingPartProxyMesh[] = []; + + initialData.proxies?.forEach((proxy) => { + const proxyMesh = addGaussianSplattingMeshPartProxyMesh(parsedMesh, editor); + if (proxyMesh) { + configureGaussianSplattingMeshFromData(proxyMesh, proxy); + proxyMeshes.push(proxyMesh); + } + }); + + return proxyMeshes; + } + const filesToLoad = [join(options.relativeScenePath, "meshes", file), ...(initialData.lods?.map((file) => join(options.relativeScenePath, "lods", file)) ?? [])]; return await Promise.all( diff --git a/editor/src/project/load/scene.ts b/editor/src/project/load/scene.ts index f165bad50..0b4a6a8fb 100644 --- a/editor/src/project/load/scene.ts +++ b/editor/src/project/load/scene.ts @@ -287,7 +287,7 @@ export async function loadScene(editor: Editor, projectPath: string, scenePath: await loadTransformNodes(editor, nodesFiles, scene, pluginLoadOptions); await loadSkeletons(editor, skeletonFiles, scene, pluginLoadOptions); - await loadMeshes(meshesFiles, scene, pluginLoadOptions); + await loadMeshes(editor, meshesFiles, scene, pluginLoadOptions); await loadMorphTargetManagers(editor, morphTargetManagerFiles, scene, pluginLoadOptions); await loadLights(editor, lightsFiles, scene, pluginLoadOptions); await loadCameras(editor, cameraFiles, scene, pluginLoadOptions); diff --git a/editor/src/project/save/scene.ts b/editor/src/project/save/scene.ts index 2faf4a9b3..226cd3a15 100644 --- a/editor/src/project/save/scene.ts +++ b/editor/src/project/save/scene.ts @@ -3,7 +3,7 @@ import { pathExists, readJSON, remove, stat, writeFile, writeJSON } from "fs-ext import filenamify from "filenamify"; -import { RenderTargetTexture, SceneSerializer } from "babylonjs"; +import { RenderTargetTexture, SceneSerializer, GaussianSplattingMesh } from "babylonjs"; import { Editor } from "../../editor/main"; @@ -19,7 +19,16 @@ import { isSpriteManagerNode, isSpriteMapNode } from "../../tools/guards/sprites import { serializePhysicsAggregate } from "../../tools/physics/serialization/aggregate"; import { isAnimationGroupFromSceneLink, isFromSceneLink } from "../../tools/scene/scene-link"; import { isGPUParticleSystem, isNodeParticleSystemSetMesh, isParticleSystem } from "../../tools/guards/particles"; -import { isAnyTransformNode, isClusteredLightContainer, isCollisionMesh, isEditorCamera, isMesh, isTransformNode } from "../../tools/guards/nodes"; +import { + isAnyTransformNode, + isClusteredLightContainer, + isCollisionMesh, + isEditorCamera, + isGaussianSplattingMesh, + isGaussianSplattingPartProxyMesh, + isMesh, + isTransformNode, +} from "../../tools/guards/nodes"; import { taaPipelineCameraConfigurations } from "../../editor/rendering/taa"; import { vlsPostProcessCameraConfigurations } from "../../editor/rendering/vls"; @@ -56,6 +65,7 @@ export function ensureSceneFolders(scenePath: string) { createDirectoryIfNotExist(join(scenePath, "sprite-maps")), createDirectoryIfNotExist(join(scenePath, "sprite-managers")), createDirectoryIfNotExist(join(scenePath, "nodeParticleSystemSets")), + createDirectoryIfNotExist(join(scenePath, "splats")), ]); } @@ -99,7 +109,16 @@ export async function saveScene(editor: Editor, projectPath: string, scenePath: // Write geometries and meshes await Promise.all( meshesToSave.map(async (mesh) => { - if ((!isMesh(mesh) && !isCollisionMesh(mesh)) || mesh._masterMesh || isFromSceneLink(mesh) || !isNodeVisibleInGraph(mesh)) { + if ( + (!isMesh(mesh) && !isCollisionMesh(mesh)) || + mesh._masterMesh || + isFromSceneLink(mesh) || + !isNodeVisibleInGraph(mesh) || + isGaussianSplattingPartProxyMesh(mesh) || + isGaussianSplattingMesh(mesh) || + mesh === editor.layout.preview.gaussianSplattingCompoundMesh || + mesh.reservedDataStore?.hidden + ) { return; } @@ -291,6 +310,72 @@ export async function saveScene(editor: Editor, projectPath: string, scenePath: }) ); + // Write gaussian splatting meshes + const computedGaussianSplattingMeshes: GaussianSplattingMesh[] = []; + + await Promise.all( + scene.meshes.map(async (mesh) => { + if (!isGaussianSplattingPartProxyMesh(mesh) || isFromSceneLink(mesh) || !mesh.baseGaussianSplattingMesh) { + return; + } + + if (computedGaussianSplattingMeshes.includes(mesh.baseGaussianSplattingMesh)) { + return; + } + + computedGaussianSplattingMeshes.push(mesh.baseGaussianSplattingMesh); + + const meshPath = join(scenePath, "meshes", `${mesh.id}.json`); + const splatPath = join(scenePath, "splats", `${mesh.id}.babylonbinarysplatdata`); + const shPaths = mesh.baseGaussianSplattingMesh.shData?.map((_, index) => join(scenePath, "splats", `${mesh.id}-sh${index}.babylonbinarysplatshdata`)) ?? []; + + try { + const data = mesh.baseGaussianSplattingMesh.serialize( + { + proxies: [], + metadata: mesh.metadata, + isEnabled: mesh.isEnabled(false), + splatDataPath: join(relativeScenePath, `splats/${mesh.id}.babylonbinarysplatdata`), + shDataPaths: mesh.baseGaussianSplattingMesh.shData?.map((_, index) => join(relativeScenePath, `splats/${mesh.id}-sh${index}.babylonbinarysplatshdata`)), + }, + "binary" + ); + + const allMeshProxies = scene.meshes.filter((m) => isGaussianSplattingPartProxyMesh(m) && m.baseGaussianSplattingMesh === mesh.baseGaussianSplattingMesh); + allMeshProxies.forEach((proxy) => { + const proxyData = proxy.serialize(); + proxyData.metadata ??= {}; + proxyData.metadata.parentId = proxy.parent?.uniqueId; + + delete proxyData.compoundSplatMeshId; + + data.proxies.push(proxyData); + }); + + const promises = [writeFile(splatPath, Buffer.from(data.splatsData))]; + + data.shData?.forEach((shData, index) => { + promises.push(writeFile(shPaths[index], Buffer.from(shData))); + }); + + await Promise.all(promises); + + delete data.shData; + delete data.splatsData; + + await writeJSON(meshPath, data, { + spaces: 4, + }); + } catch (e) { + editor.layout.console.error(`Failed to write gaussian splatting mesh ${mesh.name}`); + } finally { + savedFiles.push(meshPath, splatPath, ...shPaths); + } + + dialog.step(progressStep); + }) + ); + // Write skeletons await Promise.all( scene.skeletons.map(async (skeleton) => { diff --git a/editor/src/tools/assets/loader.ts b/editor/src/tools/assets/loader.ts new file mode 100644 index 000000000..65c404286 --- /dev/null +++ b/editor/src/tools/assets/loader.ts @@ -0,0 +1,17 @@ +import { join } from "path/posix"; + +import * as fflate from "fflate"; + +import { SPLATLoadingOptions } from "babylonjs-loaders"; + +export function getLoaderPluginOptions(appPath: string) { + const nodeModules = process.env.DEBUG ? "../node_modules" : "node_modules"; + + return { + splat: { + fflate, + keepInRam: true, + spzLibraryUrl: join(appPath ?? "", nodeModules, "@adobe/spz/dist/spz.js"), + } satisfies Partial, + }; +} diff --git a/editor/src/tools/guards/nodes.ts b/editor/src/tools/guards/nodes.ts index d441dfbd9..1aa054b9b 100644 --- a/editor/src/tools/guards/nodes.ts +++ b/editor/src/tools/guards/nodes.ts @@ -15,6 +15,8 @@ import { HemisphericLight, Skeleton, ClusteredLightContainer, + GaussianSplattingMesh, + GaussianSplattingPartProxyMesh, } from "babylonjs"; import { EditorCamera } from "../../editor/nodes/camera"; @@ -35,6 +37,9 @@ export function isAbstractMesh(object: any): object is Mesh { case "GroundMesh": case "InstancedMesh": case "NodeParticleSystemSetMesh": + case "GaussianSplattingMesh": + case "GaussianSplattingMeshBase": + case "GaussianSplattingPartProxyMesh": return true; } @@ -49,6 +54,8 @@ export function isMesh(object: any): object is Mesh { switch (object.getClassName?.()) { case "Mesh": case "GroundMesh": + case "GaussianSplattingMesh": + case "GaussianSplattingMeshBase": return true; } @@ -231,3 +238,27 @@ export function isClusteredLightContainer(object: any): object is ClusteredLight export function isNode(object: any): object is Node { return isAbstractMesh(object) || isAnyTransformNode(object) || isLight(object) || isCamera(object); } + +/** + * Returns wether or not the given object is a GaussianSplattingMesh. + * @param object defines the reference to the object to test its class name. + */ +export function isGaussianSplattingMesh(object: any): object is GaussianSplattingMesh { + return object.getClassName?.() === "GaussianSplattingMesh"; +} + +/** + * Returns wether or not the given object is a GaussianSplattingPartProxyMesh. + * @param object defines the reference to the object to test its class name. + */ +export function isGaussianSplattingPartProxyMesh(object: any): object is GaussianSplattingPartProxyMesh { + return object.getClassName?.() === "GaussianSplattingPartProxyMesh"; +} + +/** + * Returns wether or not the given object is a GaussianSplattingMesh or a GaussianSplattingPartProxyMesh. + * @param object defines the reference to the object to test its class name. + */ +export function isAnyGaussianSplattingMesh(object: any): object is GaussianSplattingMesh | GaussianSplattingPartProxyMesh { + return isGaussianSplattingMesh(object) || isGaussianSplattingPartProxyMesh(object); +} diff --git a/editor/src/tools/light/ibl.ts b/editor/src/tools/light/ibl.ts index ab6759150..c1604079c 100644 --- a/editor/src/tools/light/ibl.ts +++ b/editor/src/tools/light/ibl.ts @@ -4,7 +4,7 @@ import { getIblShadowsRenderingPipeline } from "../../editor/rendering/ibl-shado import { unique } from "../tools"; -import { isMesh } from "../guards/nodes"; +import { isGaussianSplattingMesh, isMesh } from "../guards/nodes"; export function updateIblShadowsRenderPipeline(scene: Scene, updateVoxelization?: boolean) { const iblShadowRenderPipeline = getIblShadowsRenderingPipeline(); @@ -23,7 +23,7 @@ export function updateIblShadowsRenderPipeline(scene: Scene, updateVoxelization? const shadowGenerators = scene.lights.map((l) => l.getShadowGenerator()); shadowGenerators.forEach((shadowGenerator) => { shadowGenerator?.getShadowMap()?.renderList?.forEach((mesh) => { - if (isMesh(mesh)) { + if (isMesh(mesh) && !isGaussianSplattingMesh(mesh)) { meshes.push(mesh); } }); diff --git a/editor/src/tools/light/shadows.ts b/editor/src/tools/light/shadows.ts index d70c0960a..ece4dec6a 100644 --- a/editor/src/tools/light/shadows.ts +++ b/editor/src/tools/light/shadows.ts @@ -1,6 +1,6 @@ import { Light, RenderTargetTexture, Scene, Vector3 } from "babylonjs"; -import { isPointLight, isSpotLight } from "../guards/nodes"; +import { isGaussianSplattingMesh, isPointLight, isSpotLight } from "../guards/nodes"; /** * Updates the shadow map render list predicate of the given point light. @@ -19,6 +19,10 @@ export function updatePointLightShadowMapRenderListPredicate(light: Light): void } shadowMap.renderListPredicate = (mesh) => { + if (isGaussianSplattingMesh(mesh)) { + return false; + } + const distance = Vector3.Distance(mesh.getAbsolutePosition(), light.getAbsolutePosition()); return distance <= light.range; }; diff --git a/editor/src/tools/mesh/augmentations.ts b/editor/src/tools/mesh/augmentations.ts index 82b103448..e3d6d22fa 100644 --- a/editor/src/tools/mesh/augmentations.ts +++ b/editor/src/tools/mesh/augmentations.ts @@ -1,6 +1,6 @@ -import { PhysicsAggregate } from "babylonjs"; +import { GaussianSplattingMesh, PhysicsAggregate } from "babylonjs"; -export { AbstractMesh } from "babylonjs"; +import { GaussianSplattingPartProxyMesh as _GaussianSplattingPartProxyMesh } from "babylonjs/Meshes/GaussianSplatting/gaussianSplattingPartProxyMesh"; declare module "babylonjs" { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -8,4 +8,16 @@ declare module "babylonjs" { _waitingLod?: any; physicsAggregate?: PhysicsAggregate | null; } + + // eslint-disable-next-line @typescript-eslint/naming-convention + export interface GaussianSplattingPartProxyMesh extends _GaussianSplattingPartProxyMesh { + baseGaussianSplattingMesh?: GaussianSplattingMesh; + } +} + +declare module "babylonjs/Meshes/GaussianSplatting/gaussianSplattingPartProxyMesh" { + // eslint-disable-next-line @typescript-eslint/naming-convention + export interface GaussianSplattingPartProxyMesh { + baseGaussianSplattingMesh?: GaussianSplattingMesh; + } } diff --git a/editor/src/tools/mesh/gaussian-splatting.ts b/editor/src/tools/mesh/gaussian-splatting.ts new file mode 100644 index 000000000..5327c0aa2 --- /dev/null +++ b/editor/src/tools/mesh/gaussian-splatting.ts @@ -0,0 +1,53 @@ +import { GaussianSplattingMesh, GetGaussianSplattingMaxPartCount, GaussianSplattingCompoundMesh, GaussianSplattingPartProxyMesh } from "babylonjs"; + +import { Editor } from "../../editor/main"; +import { configureImportedNodeIds } from "../../editor/layout/preview/import/import"; + +import { setNodeSerializable, setNodeVisibleInGraph } from "../node/metadata"; + +export function configureGaussianSplattingMeshFromData(gaussianSplattingMesh: GaussianSplattingPartProxyMesh, data: any) { + gaussianSplattingMesh.name = data.name; + gaussianSplattingMesh.id = data.id; + gaussianSplattingMesh.uniqueId = data.uniqueId; + + gaussianSplattingMesh.metadata = data.metadata ?? {}; + gaussianSplattingMesh.metadata._waitingParentId = data.metadata?.parentId; + + gaussianSplattingMesh.setEnabled(data.isEnabled ?? true); + + if (data.position) { + gaussianSplattingMesh.position.copyFromFloats(data.position[0], data.position[1], data.position[2]); + } + if (data.rotation) { + gaussianSplattingMesh.rotation.copyFromFloats(data.rotation[0], data.rotation[1], data.rotation[2]); + } + if (data.rotationQuaternion) { + gaussianSplattingMesh.rotationQuaternion?.copyFromFloats(data.rotationQuaternion[0], data.rotationQuaternion[1], data.rotationQuaternion[2], data.rotationQuaternion[3]); + } + if (data.scaling) { + gaussianSplattingMesh.scaling.copyFromFloats(data.scaling[0], data.scaling[1], data.scaling[2]); + } +} + +export function addGaussianSplattingMeshPartProxyMesh(mesh: GaussianSplattingMesh, editor: Editor) { + const scene = editor.layout.preview.scene; + const maxGaussianSplattingPartCount = GetGaussianSplattingMaxPartCount(scene.getEngine()); + + let gaussianSplattingCompoundMesh = editor.layout.preview.gaussianSplattingCompoundMesh; + if (!gaussianSplattingCompoundMesh) { + gaussianSplattingCompoundMesh = new GaussianSplattingCompoundMesh("GaussianSplattingCompoundMesh", undefined, scene, true); + configureImportedNodeIds(gaussianSplattingCompoundMesh); + + setNodeSerializable(gaussianSplattingCompoundMesh, false); + setNodeVisibleInGraph(gaussianSplattingCompoundMesh, false); + + editor.layout.preview.gaussianSplattingCompoundMesh = gaussianSplattingCompoundMesh; + } + + if (gaussianSplattingCompoundMesh.partCount < maxGaussianSplattingPartCount) { + const proxyMesh = gaussianSplattingCompoundMesh.addPart(mesh); + proxyMesh.baseGaussianSplattingMesh = mesh; + + return proxyMesh; + } +} diff --git a/editor/src/tools/node/clone.ts b/editor/src/tools/node/clone.ts index 3e5ddd951..fbd787ba9 100644 --- a/editor/src/tools/node/clone.ts +++ b/editor/src/tools/node/clone.ts @@ -18,6 +18,7 @@ import { isClusteredLight } from "../light/cluster"; import { parsePhysicsAggregate, serializePhysicsAggregate } from "../physics/serialization/aggregate"; import { isTexture } from "../guards/texture"; +import { isSoundNode } from "../guards/sound"; import { isSprite, isSpriteManagerNode, isSpriteMapNode } from "../guards/sprites"; import { isAnyParticleSystem, isNodeParticleSystemSetMesh } from "../guards/particles"; import { isCamera, isInstancedMesh, isLight, isMesh, isNode, isTransformNode } from "../guards/nodes"; @@ -61,6 +62,8 @@ export function cloneNode(editor: Editor, node: Node | Sprite | ParticleSystem | clone = node.clone(name, node.parent, false); } else if (isSprite(node)) { clone = cloneSprite(node); + } else if (isSoundNode(node)) { + clone = node.clone(name); } else if (isSpriteManagerNode(node)) { const serializationData = node.serialize(); diff --git a/editor/src/tools/scene/play/override.tsx b/editor/src/tools/scene/play/override.tsx index b1087bc20..2347ab6c6 100644 --- a/editor/src/tools/scene/play/override.tsx +++ b/editor/src/tools/scene/play/override.tsx @@ -3,7 +3,7 @@ import { join, dirname } from "path/posix"; import { Engine, WebRequest, Observable, Observer, ExitPointerlock, ExitFullscreen, SerializationHelper } from "babylonjs"; -import { getCurrentCallStack } from "../../tools"; +import { getCurrentCallStack, isValidUrl } from "../../tools"; import { Editor } from "../../../editor/main"; @@ -29,6 +29,7 @@ const savedWebRequestMethods: Record = { }; const savedEngineMethods: Record = { + activeRenderLoops: [], createTexture: Engine.prototype.createTexture, createCubeTexture: Engine.prototype.createCubeTexture, createRawCubeTextureFromUrl: Engine.prototype.createRawCubeTextureFromUrl, @@ -107,9 +108,15 @@ export function restorePlayOverrides(editor: Editor) { WebRequest.prototype.open = savedWebRequestMethods.open; + editor.layout.preview.engine.stopRenderLoop(); + savedEngineMethods.activeRenderLoops.forEach((loop) => { + editor.layout.preview.engine.runRenderLoop(loop); + }); + Engine.prototype.createTexture = savedEngineMethods.createTexture; Engine.prototype.createCubeTexture = savedEngineMethods.createCubeTexture; Engine.prototype.createRawCubeTextureFromUrl = savedEngineMethods.createRawCubeTextureFromUrl; + SerializationHelper._TextureParser = savedTextureMethods.textureParser; Observable.prototype.add = savedObservableMethods.add; @@ -181,6 +188,10 @@ export function applyOverrides(editor: Editor) { // Fetch window.fetch = async (input: string | URL | Request, init?: RequestInit) => { + if (isValidUrl(input.toString())) { + return savedWindowMethods.fetch.call(window, input, init); + } + if (!isAbsolute(input.toString())) { input = normalizeUrl(input.toString()); input = join(publicDir, input.toString()); @@ -273,6 +284,8 @@ export function applyOverrides(editor: Editor) { }; // Engine + savedEngineMethods.activeRenderLoops = editor.layout.preview.engine.activeRenderLoops.slice(); + Engine.prototype.createRawCubeTextureFromUrl = (url: string, ...args: any[]) => { if (url && url.includes(publicScene)) { url = url.replace(publicScene, projectDir); diff --git a/editor/src/tools/tools.ts b/editor/src/tools/tools.ts index bdc1fbe49..347ed7050 100644 --- a/editor/src/tools/tools.ts +++ b/editor/src/tools/tools.ts @@ -136,3 +136,16 @@ export function readBlobAsDataUrl(blob: Blob): Promise { export function cloneJSObject(source: T): T { return JSON.parse(JSON.stringify(source)); } + +/** + * Returns whether or not the given string is a valid URL. + * @param url defines the string to check if it is a valid URL. + */ +export function isValidUrl(url: string): boolean { + try { + new URL(url); + return true; + } catch { + return false; + } +} diff --git a/editor/src/tools/workers/thumbnail/mesh.ts b/editor/src/tools/workers/thumbnail/mesh.ts index 0c8725921..7c740e2ce 100644 --- a/editor/src/tools/workers/thumbnail/mesh.ts +++ b/editor/src/tools/workers/thumbnail/mesh.ts @@ -5,6 +5,8 @@ import { AssimpJSLoader } from "../../../loader/assimpjs"; import { readBlobAsDataUrl } from "../../tools"; +import { getLoaderPluginOptions } from "../../assets/loader"; + import { forceCompileAllSceneMaterials } from "../../scene/materials"; const assimpLoader = new AssimpJSLoader(false, false); @@ -42,7 +44,9 @@ export async function getPreview( scene.environmentTexture = environmentTexture; } - const container = await LoadAssetContainerAsync(absolutePath, scene); + const container = await LoadAssetContainerAsync(absolutePath, scene, { + pluginOptions: getLoaderPluginOptions(appPath ?? ""), + }); container.addAllToScene(); if (serializedOverrideMaterial) { @@ -52,19 +56,19 @@ export async function getPreview( }); } - return new Promise((resolve) => { - scene.executeWhenReady(async () => { - scene.createDefaultCameraOrLight(true, true, true); - scene.createDefaultEnvironment({ - createSkybox: true, - enableGroundShadow: true, - enableGroundMirror: true, - }); + return new Promise(async (resolve) => { + scene.createDefaultCameraOrLight(true, true, true); + scene.createDefaultEnvironment({ + createSkybox: true, + enableGroundShadow: true, + enableGroundMirror: true, + }); - const camera = scene.activeCamera as ArcRotateCamera; - camera.alpha = -Math.PI * 0.666; - camera.beta = Math.PI * 0.35; + const camera = scene.activeCamera as ArcRotateCamera; + camera.alpha = -Math.PI * 0.666; + camera.beta = Math.PI * 0.35; + scene.executeWhenReady(async () => { await forceCompileAllSceneMaterials(scene); scene.render(); diff --git a/package.json b/package.json index a9ef64565..430135f84 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@babylonjs/gui": "9.12.1", "@babylonjs/materials": "9.12.1", "braces": "3.0.3", - "node-abi": "4.14.0", + "node-abi": "^3.3.0", "wrap-ansi": "7.0.0", "babylonjs": "9.12.1", "babylonjs-addons": "9.12.1", diff --git a/plugins/fab/src/import/mesh.ts b/plugins/fab/src/import/mesh.ts index 2332565be..b5a145000 100644 --- a/plugins/fab/src/import/mesh.ts +++ b/plugins/fab/src/import/mesh.ts @@ -16,7 +16,7 @@ export async function importMesh(editor: Editor, parameters: IImportMeshParamete const dest = join(parameters.finalAssetsFolder, basename(parameters.json.file)); const rootNodes: Node[] = []; - const result = await loadImportedSceneFile(editor.layout.preview.scene, dest); + const result = await loadImportedSceneFile(editor.layout.preview.scene, dest, editor); result?.meshes.forEach((mesh) => { mesh.material = parameters.materialsMap.get(parameters.json.material_index) ?? mesh.material; diff --git a/templates/electron/package.json b/templates/electron/package.json index efe4bc1db..828f9c6e1 100644 --- a/templates/electron/package.json +++ b/templates/electron/package.json @@ -25,7 +25,7 @@ "@types/node": "^22", "babylonjs-editor-cli": "latest", "concurrently": "9.2.0", - "electron": "39.8.5", + "electron": "42.8.0", "electron-builder": "26.0.12", "tailwindcss": "4.1.18", "tsc-watch": "7.2.0", diff --git a/templates/nextjs/package.json b/templates/nextjs/package.json index 4677ce585..c3df49893 100644 --- a/templates/nextjs/package.json +++ b/templates/nextjs/package.json @@ -28,7 +28,7 @@ "babylonjs-editor-cli": "latest", "eslint": "9.29.0", "eslint-config-next": "16.2.6", - "postcss": "^8", + "postcss": "8.5.26", "raw-loader": "^4.0.2", "tailwindcss": "3.4.4", "typescript": "5.9.3" diff --git a/tools/package.json b/tools/package.json index 73de11a66..412743b64 100644 --- a/tools/package.json +++ b/tools/package.json @@ -1,6 +1,6 @@ { "name": "babylonjs-editor-tools", - "version": "5.4.3-alpha.4", + "version": "5.4.3-alpha.5", "description": "Babylon.js Editor Tools is a set of tools to help you create, edit and manage your Babylon.js scenes made using the Babylon.js Editor", "productName": "Babylon.js Editor Tools", "scripts": { diff --git a/tools/src/index.ts b/tools/src/index.ts index 8bbf8f566..b132dfa51 100644 --- a/tools/src/index.ts +++ b/tools/src/index.ts @@ -1,5 +1,6 @@ export * from "./loading/loader"; export * from "./loading/material"; + export * from "./loading/container/container"; export * from "./loading/container/entries"; diff --git a/tools/src/loading/gaussian-splatting.ts b/tools/src/loading/gaussian-splatting.ts new file mode 100644 index 000000000..ad6b3e0c8 --- /dev/null +++ b/tools/src/loading/gaussian-splatting.ts @@ -0,0 +1,117 @@ +import { Scene } from "@babylonjs/core/scene"; +import { AssetContainer } from "@babylonjs/core/assetContainer"; +import { AddParser } from "@babylonjs/core/Loading/Plugins/babylonFileParser.function"; +import { GaussianSplattingMesh } from "@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingMesh"; +import { GaussianSplattingCompoundMesh } from "@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingCompoundMesh"; +import { GetGaussianSplattingMaxPartCount } from "@babylonjs/core/Materials/GaussianSplatting/gaussianSplattingMaterial"; + +import { loadFile } from "../tools/request"; + +let registered = false; + +export function registerGaussianSplattingParser() { + if (registered) { + return; + } + + registered = true; + + let compountMesh: GaussianSplattingCompoundMesh | null = null; + + AddParser("GaussianSplattingMeshEditorPlugin", (parsedData: any, scene: Scene, container: AssetContainer, rootUrl: string) => { + const maxGaussianSplattingPartCount = GetGaussianSplattingMaxPartCount(scene.getEngine()); + + parsedData.meshes?.forEach((mesh) => { + if (mesh.type !== "GaussianSplattingMesh" || !mesh.splatDataPath) { + return; + } + + const instantiatedMesh = container.meshes.find((m) => m.id === mesh.id) as GaussianSplattingMesh; + if (!instantiatedMesh) { + return; + } + + instantiatedMesh.dispose(true); + + const splatDataUrl = rootUrl + mesh.splatDataPath; + const shDataUrls = mesh.shDataPaths?.map((shData) => rootUrl + shData); + + scene.addPendingData(splatDataUrl); + shDataUrls?.forEach((shDataUrl) => { + scene.addPendingData(shDataUrl); + }); + + const promises = [loadFile(splatDataUrl, "arraybuffer")]; + + shDataUrls?.forEach((shDataUrl) => { + promises.push(loadFile(shDataUrl, "arraybuffer")); + }); + + Promise.all(promises).then(([splatData, ...shDataArray]) => { + mesh.splatsData = splatData; + mesh.shData = shDataArray?.map((buffer) => new Uint8Array(buffer)); + + const parsedMesh = GaussianSplattingMesh.Parse(mesh, scene); + parsedMesh.id = mesh.id; + parsedMesh.uniqueId = mesh.uniqueId; + + scene.removeMesh(parsedMesh); + + mesh.proxies.forEach((proxy) => { + compountMesh ??= new GaussianSplattingCompoundMesh("GaussianSplattingCompoundMesh", undefined, scene); + + if (compountMesh.partCount >= maxGaussianSplattingPartCount) { + return; + } + + const proxyMesh = compountMesh.addPart(parsedMesh, false); + + proxyMesh.name = proxy.name; + proxyMesh.id = proxy.id; + proxyMesh.uniqueId = proxy.uniqueId; + + proxyMesh.metadata = proxy.metadata ?? {}; + proxyMesh.metadata._waitingParentId = proxy.metadata?.parentId; + + proxyMesh.setEnabled(proxy.isEnabled ?? true); + + if (proxy.position) { + proxyMesh.position.copyFromFloats(proxy.position[0], proxy.position[1], proxy.position[2]); + } + + if (proxy.rotation) { + proxyMesh.rotation.copyFromFloats(proxy.rotation[0], proxy.rotation[1], proxy.rotation[2]); + } + + if (proxy.rotationQuaternion) { + proxyMesh.rotationQuaternion?.copyFromFloats( + proxy.rotationQuaternion[0], + proxy.rotationQuaternion[1], + proxy.rotationQuaternion[2], + proxy.rotationQuaternion[3] + ); + } + if (proxy.scaling) { + proxyMesh.scaling.copyFromFloats(proxy.scaling[0], proxy.scaling[1], proxy.scaling[2]); + } + + if (proxy.parentId) { + const parent = container.getNodes().find((n) => n.id === proxy.parentId); + if (parent) { + proxyMesh.parent = parent; + } else { + proxyMesh._waitingParentId = proxy.parentId; + } + } + }); + + scene.removePendingData(splatDataUrl); + shDataUrls?.forEach((shDataUrl) => { + scene.removePendingData(shDataUrl); + }); + }); + }); + }); +} + +registerGaussianSplattingParser(); diff --git a/tools/src/loading/loader.ts b/tools/src/loading/loader.ts index 6befaa27b..fd1b42073 100644 --- a/tools/src/loading/loader.ts +++ b/tools/src/loading/loader.ts @@ -22,13 +22,13 @@ import { _preloadScriptsAssets } from "./script/preload"; import { registerAudioParser } from "./sound"; import { registerTextureParser } from "./texture"; import { registerShadowGeneratorParser } from "./shadows"; +import { registerSpriteManagerParser } from "./sprite-manager"; import { registerMorphTargetManagerParser } from "./morph-target-manager"; +import { registerNodeParticleSystemSetParser } from "./node-particle-system-set"; import { configureLights } from "./light"; import { registerSpriteMapParser } from "./sprite-map"; import { configureTransformNodes } from "./transform-node"; -import { registerSpriteManagerParser } from "./sprite-manager"; -import { registerNodeParticleSystemSetParser } from "./node-particle-system-set"; /** * Defines the possible output type of a script. diff --git a/tools/src/tools/guards.ts b/tools/src/tools/guards.ts index 2beb71c0d..81bd2d95e 100644 --- a/tools/src/tools/guards.ts +++ b/tools/src/tools/guards.ts @@ -7,6 +7,7 @@ import { GroundMesh } from "@babylonjs/core/Meshes/groundMesh"; import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; import { InstancedMesh } from "@babylonjs/core/Meshes/instancedMesh"; import { TransformNode } from "@babylonjs/core/Meshes/transformNode"; +import { GaussianSplattingMesh } from "@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingMesh"; import { Texture } from "@babylonjs/core/Materials/Textures/texture"; @@ -257,3 +258,11 @@ export function isSprite(object: any): object is Sprite { export function isSoundNode(object: any): object is SoundNode { return object.getClassName?.() === "SoundNode"; } + +/** + * Returns wether or not the given object is a GaussianSplattingMesh. + * @param object defines the reference to the object to test its class name. + */ +export function isGaussianSplattingMesh(object: any): object is GaussianSplattingMesh { + return object.getClassName?.() === "GaussianSplattingMesh"; +} diff --git a/tools/src/tools/light.ts b/tools/src/tools/light.ts index ea744392c..8e912f9c5 100644 --- a/tools/src/tools/light.ts +++ b/tools/src/tools/light.ts @@ -5,6 +5,7 @@ import { RenderTargetTexture } from "@babylonjs/core/Materials/Textures/renderTa import { SceneLoaderQualitySelector } from "../loading/loader"; import { getPowerOfTwoUntil } from "./scalar"; +import { isGaussianSplattingMesh } from "./guards"; declare module "@babylonjs/core/Lights/Shadows/shadowGenerator" { export interface IShadowGenerator { @@ -20,6 +21,10 @@ export function configureShadowMapRenderListPredicate(scene: Scene) { } shadowMap.renderListPredicate = (mesh) => { + if (isGaussianSplattingMesh(mesh)) { + return false; + } + const distance = Vector3.Distance(mesh.getAbsolutePosition(), light.getAbsolutePosition()); return distance <= light.range; }; diff --git a/website/package.json b/website/package.json index 5ce4f7d7c..90d31fe75 100644 --- a/website/package.json +++ b/website/package.json @@ -43,7 +43,7 @@ "autoprefixer": "^10.0.1", "eslint": "9.29.0", "eslint-config-next": "16.2.6", - "postcss": "^8", + "postcss": "8.5.26", "tailwindcss": "3.4.4", "typescript": "5.9.3" } diff --git a/website/src/app/documentation/assets/using-gaussian-splatting/page.tsx b/website/src/app/documentation/assets/using-gaussian-splatting/page.tsx new file mode 100644 index 000000000..8341e7dd3 --- /dev/null +++ b/website/src/app/documentation/assets/using-gaussian-splatting/page.tsx @@ -0,0 +1,84 @@ +"use client"; + +import Link from "next/link"; + +import { Fade } from "react-awesome-reveal"; + +import { CodeBlock } from "../../code"; +import { loadSceneWithGaussianSplatting } from "./scripts"; + +export default function DocumentationRunningProjectPage() { + return ( +
+
+ + +
Using Sprite Manager
+
+
+ + +
+
Introduction
+ +
+ Gaussian Splatting is a volume-rendering method. It's useful for capturing real-life data. You can find more information about Gaussian Splatting + support in Babylon.js{" "} + + here + + . +
+ +
+ The Babylon.js Editor supports Gaussian Splatting assets. You can import them in your project and use them in your scene. The Editor will automatically + create a Gaussian Splatting instance. You can then manipulate them in the scene and change their properties in the Inspector. You can also add scripts + to them. +
+ +
+ Supported formats are: +
    +
  • + .splat: JavaScript typed-array serialized version of .PLY data +
  • +
  • + .spz:{" "} + + Niantic Labs + {" "} + SPZ format{" "} +
  • +
  • + .sog:{" "} + + Self-Organizing Gaussian + {" "} + format +
  • +
+
+ +
Importing Gaussian Splatting assets
+ +
Supporting Gaussian Splatting in your app
+ +
+ By default, Gaussian Splatting support is NOT included when you import the Babylon.js Editor tools. For tree-shaking purpose, you need to + explicitely import the Gaussian Splatting support in your app. You can do this by adding the following line in your code: +
+ + + +
+ And then you can load your scene(s) that contain Gaussian Splatting assets. The loader will automatically create the Gaussian Splatting instances in + your scene. Here is an example of the code to load a scene with Gaussian Splatting support: +
+ + +
+
+
+
+ ); +} diff --git a/website/src/app/documentation/assets/using-gaussian-splatting/scripts.ts b/website/src/app/documentation/assets/using-gaussian-splatting/scripts.ts new file mode 100644 index 000000000..e174d64b6 --- /dev/null +++ b/website/src/app/documentation/assets/using-gaussian-splatting/scripts.ts @@ -0,0 +1,11 @@ +export const loadSceneWithGaussianSplatting = ` +import { loadScene } from "babylonjs-editor-tools"; + +import "babylonjs-editor-tools/loading/gaussian-splatting"; + +// ... + +await loadScene("/scene/", "my-scene.babylon", scene, scriptsMap, { + quality: "high", +}); +`; diff --git a/website/src/app/documentation/sprites/using-sprite-manager/page.tsx b/website/src/app/documentation/assets/using-sprite-manager/page.tsx similarity index 100% rename from website/src/app/documentation/sprites/using-sprite-manager/page.tsx rename to website/src/app/documentation/assets/using-sprite-manager/page.tsx diff --git a/website/src/app/documentation/sprites/using-sprite-manager/scripts.ts b/website/src/app/documentation/assets/using-sprite-manager/scripts.ts similarity index 100% rename from website/src/app/documentation/sprites/using-sprite-manager/scripts.ts rename to website/src/app/documentation/assets/using-sprite-manager/scripts.ts diff --git a/website/src/app/documentation/sidebar.tsx b/website/src/app/documentation/sidebar.tsx index 58fc8364b..dcae6e236 100644 --- a/website/src/app/documentation/sidebar.tsx +++ b/website/src/app/documentation/sidebar.tsx @@ -43,9 +43,10 @@ export function DocumentationSidebar() { -
Sprites
+
Assets
- + +
Deploying
diff --git a/yarn.lock b/yarn.lock index adeca242a..ba8d9069d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adobe/spz@0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@adobe/spz/-/spz-0.2.2.tgz#f2c3547a62e63daa1e5a52a36204300fceeed012" + integrity sha512-KUCw5R52rCBnfyvOOIa9nUQGfR6TEcHjYzuWDYRGb1fpkpnkci6VXFqCx/xFJ1yEN7V5gc9YxU2zIY6sEjIbhw== + "@alloc/quick-lru@^5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" @@ -1340,6 +1345,11 @@ resolved "https://registry.yarnpkg.com/@dxup/unimport/-/unimport-0.1.2.tgz#a08e4039efe41c50d9c36fbb39d9976b88eefa68" integrity sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ== +"@electron-internal/extract-zip@^1.0.1": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz#6782c0f6066e60b7fd286fe7a5c7600f7650d420" + integrity sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA== + "@electron/asar@3.2.18": version "3.2.18" resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.18.tgz#fa607f829209bab8b9e0ce6658d3fe81b2cba517" @@ -1367,20 +1377,19 @@ fs-extra "^9.0.1" minimist "^1.2.5" -"@electron/get@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" - integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== +"@electron/get@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-5.1.0.tgz#f96ca2a0e89b27490ff8f7b5a392bd4df6942998" + integrity sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA== dependencies: debug "^4.1.1" - env-paths "^2.2.0" - fs-extra "^8.1.0" - got "^11.8.5" + env-paths "^3.0.0" + graceful-fs "^4.2.11" progress "^2.0.3" - semver "^6.2.0" + semver "^7.6.3" sumchecker "^3.0.1" optionalDependencies: - global-agent "^3.0.0" + undici "^7.24.4" "@electron/node-gyp@https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2": version "10.2.0-electron.1" @@ -6453,7 +6462,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@^22", "@types/node@^22.7.7": +"@types/node@*", "@types/node@^22": version "22.15.32" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.32.tgz#c301cc2275b535a5e54bb81d516b1d2e9afe06e5" integrity sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA== @@ -6467,6 +6476,13 @@ dependencies: undici-types "~6.21.0" +"@types/node@^24.9.0": + version "24.13.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.13.3.tgz#49f18bd3c647866dcda51a0756c145e14590ce16" + integrity sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q== + dependencies: + undici-types "~7.18.0" + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -6550,13 +6566,6 @@ resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@8.35.1": version "8.35.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz#06b1129fe26d6532abd58fb2b3fe9810bd016935" @@ -7878,9 +7887,10 @@ babylonjs-editor-tools@latest: "babylonjs-editor-tools@link:../../../Library/Caches/Yarn/v6/npm-babylonjs-editor-5.2.4-3cce3a704dc0c4572a85041a993264060376230a-integrity/node_modules/tools": version "0.0.0" + uid "" "babylonjs-editor-tools@link:tools": - version "5.4.3-alpha.4" + version "5.4.3-alpha.5" babylonjs-editor@latest: version "5.2.4" @@ -8176,11 +8186,6 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -boolean@^3.0.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" - integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== - bowser@^2.11.0: version "2.13.1" resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.13.1.tgz#5a4c652de1d002f847dd011819f5fc729f308a7e" @@ -8314,6 +8319,14 @@ builder-util-runtime@9.3.1: debug "^4.3.4" sax "^1.2.4" +builder-util-runtime@9.7.0: + version "9.7.0" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz#c86fba303684e877daee15c29eede81987166fef" + integrity sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw== + dependencies: + debug "^4.3.4" + sax "^1.2.4" + builder-util@26.0.11: version "26.0.11" resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-26.0.11.tgz#ad85b92c93f2b976b973e1d87337e0c6813fcb8f" @@ -9494,11 +9507,6 @@ detect-node-es@^1.1.0: resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - devalue@^5.8.2: version "5.9.0" resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.9.0.tgz#5d30db41a0db9171cf4ee9dbf6617e0b77cd7c2a" @@ -9746,7 +9754,21 @@ electron-to-chromium@^1.5.73: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz#8ea537b369c32527b3cc47df7973bffe5d3c2980" integrity sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ== -electron-updater@6.6.2, electron-updater@^6.6.2: +electron-updater@6.8.9: + version "6.8.9" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.8.9.tgz#21e3e2400ee3a58b7496f0a023d95d7ab1dae019" + integrity sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig== + dependencies: + builder-util-runtime "9.7.0" + fs-extra "^10.1.0" + js-yaml "^4.1.0" + lazy-val "^1.0.5" + lodash.escaperegexp "^4.1.2" + lodash.isequal "^4.5.0" + semver "~7.7.3" + tiny-typed-emitter "^2.1.0" + +electron-updater@^6.6.2: version "6.6.2" resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.6.2.tgz#3e65e044f1a99b00d61e200e24de8e709c69ce99" integrity sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw== @@ -9760,14 +9782,14 @@ electron-updater@6.6.2, electron-updater@^6.6.2: semver "^7.6.3" tiny-typed-emitter "^2.1.0" -electron@39.8.5: - version "39.8.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-39.8.5.tgz#422d42318d993a77a960ea1a9b4bfb4822221388" - integrity sha512-q6+LiQIcTadSyvtPgLDQkCtVA9jQJXQVMrQcctfOJILh6OFMN+UJJLRkuUTy8CZDYeCIBn1ZycqsL1dAXugxZA== +electron@42.8.0: + version "42.8.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-42.8.0.tgz#e3bbebc98dcc99266eaaed365c6555a022248712" + integrity sha512-lgeUDjUuUzUSBchBudmjCZ8ApeYdVneMi17nLRMdfxGw7FyLFligsLtIF+dL3UoNnOsupAwdqVNJCK5MFE82kQ== dependencies: - "@electron/get" "^2.0.0" - "@types/node" "^22.7.7" - extract-zip "^2.0.1" + "@electron-internal/extract-zip" "^1.0.1" + "@electron/get" "^5.0.0" + "@types/node" "^24.9.0" embla-carousel-react@8.6.0: version "8.6.0" @@ -9870,6 +9892,11 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== +env-paths@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da" + integrity sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A== + err-code@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" @@ -10097,11 +10124,6 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - esbuild@0.25.5: version "0.25.5" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.5.tgz#71075054993fdfae76c66586f9b9c1f8d7edd430" @@ -10637,17 +10659,6 @@ externality@^1.0.2: pathe "^1.1.1" ufo "^1.1.2" -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - extsprintf@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" @@ -10771,7 +10782,7 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" -fflate@^0.8.2: +fflate@0.8.3, fflate@^0.8.2: version "0.8.3" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.3.tgz#bc27d8eb30343d4d512abb03480202ce65d825fc" integrity sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA== @@ -10992,15 +11003,6 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -11293,18 +11295,6 @@ glob@^8.0.1, glob@^8.1.0: minimatch "^5.0.1" once "^1.3.0" -global-agent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" - integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== - dependencies: - boolean "^3.0.1" - es6-error "^4.1.1" - matcher "^3.0.0" - roarr "^2.15.3" - semver "^7.3.2" - serialize-error "^7.0.1" - global-directory@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e" @@ -11322,7 +11312,7 @@ globals@^14.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== -globalthis@^1.0.1, globalthis@^1.0.3: +globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== @@ -11361,7 +11351,7 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -got@^11.7.0, got@^11.8.5: +got@^11.7.0: version "11.8.6" resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== @@ -12419,11 +12409,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -12436,13 +12421,6 @@ json5@^2.1.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -13013,13 +12991,6 @@ markdown-to-jsx@7.6.2: resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.6.2.tgz#254cbf7d412a37073486c0a2dd52266d2191a793" integrity sha512-gEcyiJXzBxmId2Y/kydLbD6KRNccDiUy/Src1cFGn3s2X0LZZ/hUiEc2VisFyA5kUE3SXclTCczjQiAuqKZiFQ== -matcher@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" - integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== - dependencies: - escape-string-regexp "^4.0.0" - math-expression-evaluator@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-2.0.6.tgz#a33028e4d55e7dcfcca17a696738e1a25b8e2c22" @@ -13432,11 +13403,21 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.16, nanoid@^3.3.17, nanoid@^3.3.6: +nanoid@^3.3.16: + version "3.3.16" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.16.tgz#a04d8ec4b1f10009d2d533947aefe4293737816c" + integrity sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q== + +nanoid@^3.3.17: version "3.3.18" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.18.tgz#f66a2de1199ffde0fcf21c8a5f13106b1c081913" integrity sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w== +nanoid@^3.3.6: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + nanotar@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/nanotar/-/nanotar-0.3.0.tgz#0a1839731ecbdd910129244a563f73183eb80c6a" @@ -13588,12 +13569,12 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@4.14.0, node-abi@^3.45.0, node-abi@^4.2.0: - version "4.14.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-4.14.0.tgz#7846b36a732f1afa221be3ca0e6248448bc111ef" - integrity sha512-E4n91K4Nk1Rch2KzD+edU2bfZTP4W42GypAUDXU4vu1A+4u9PvUNDkGI0dXbsy8ZeF3WGj0SD/uHxnXD/sW+3w== +node-abi@^3.3.0, node-abi@^3.45.0, node-abi@^4.2.0: + version "3.94.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.94.0.tgz#007181ed0d1b56ae9670ea6c084d2bf83538405f" + integrity sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g== dependencies: - semver "^7.6.3" + semver "^7.3.5" node-addon-api@^1.6.3: version "1.7.2" @@ -13698,10 +13679,10 @@ node-pty@1.1.0-beta35: dependencies: node-addon-api "^7.1.0" -node-pty@1.2.0-beta.12: - version "1.2.0-beta.12" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.2.0-beta.12.tgz#4ea572e1b655e19c005f6e34e948e18ffd040d21" - integrity sha512-uExTCG/4VmSJa4+TjxFwPXv8BfacmfFEBL6JpxCMDghcwqzvD0yTcGmZ1fKOK6HY33tp0CelLblqTECJizc+Yw== +node-pty@1.2.0-beta.15: + version "1.2.0-beta.15" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.2.0-beta.15.tgz#cd62e0e32b69ce226c650935a35613fa41bf77d4" + integrity sha512-vORSzHXi4Ofl7HemVWpuudLqCPdaQb4LfpRCUpE5HPxhp4JYscl8zZwxh11p26v2wvW24WMwnMfLjhRLixrfxA== dependencies: node-addon-api "^7.1.0" @@ -14760,16 +14741,7 @@ postcss@8.4.31: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8, postcss@^8.4.23, postcss@^8.4.48, postcss@^8.5.10, postcss@^8.5.15, postcss@^8.5.6: - version "8.5.25" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.25.tgz#5012a598eaaa897f21bbe8553be3cb7bd2bd78cb" - integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw== - dependencies: - nanoid "^3.3.16" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -postcss@^8.5.19, postcss@^8.5.22: +postcss@8.5.26, postcss@^8.5.19, postcss@^8.5.22: version "8.5.26" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.26.tgz#6e75135780c7e10df3433bf2266c552d35c8c620" integrity sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ== @@ -14778,6 +14750,15 @@ postcss@^8.5.19, postcss@^8.5.22: picocolors "^1.1.1" source-map-js "^1.2.1" +postcss@^8.4.23, postcss@^8.4.48, postcss@^8.5.10, postcss@^8.5.15, postcss@^8.5.6: + version "8.5.25" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.25.tgz#5012a598eaaa897f21bbe8553be3cb7bd2bd78cb" + integrity sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw== + dependencies: + nanoid "^3.3.16" + picocolors "^1.1.1" + source-map-js "^1.2.1" + powershell-utils@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/powershell-utils/-/powershell-utils-0.1.0.tgz#5a42c9a824fb4f2f251ccb41aaae73314f5d6ac2" @@ -15432,18 +15413,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -roarr@^2.15.3: - version "2.15.4" - resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" - integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== - dependencies: - boolean "^3.0.1" - detect-node "^2.0.4" - globalthis "^1.0.1" - json-stringify-safe "^5.0.1" - semver-compare "^1.0.0" - sprintf-js "^1.1.2" - rolldown@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.3.tgz#db88a3008fb0e28230a00423727ce75ba32121ac" @@ -15702,26 +15671,31 @@ seek-bzip@^1.0.5: dependencies: commander "^2.8.1" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - semver@^5.5.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.2.0, semver@^6.3.1: +semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3, semver@^7.7.4, semver@^7.8.5: +semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3, semver@^7.7.2, semver@^7.7.3, semver@^7.7.4: + version "7.8.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df" + integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA== + +semver@^7.8.5: version "7.8.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== +semver@~7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + send@^1.1.0, send@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" @@ -15748,13 +15722,6 @@ sentence-case@^3.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -serialize-error@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" - integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== - dependencies: - type-fest "^0.13.1" - serialize-javascript@^7.0.3: version "7.0.5" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.0.5.tgz#c798cc0552ffbb08981914a42a8756e339d0d5b1" @@ -16240,11 +16207,6 @@ split@0.3: dependencies: through "2" -sprintf-js@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - srvx@^0.11.22: version "0.11.22" resolved "https://registry.yarnpkg.com/srvx/-/srvx-0.11.22.tgz#f83413628014eb37416ba4609e10b4bb38472295" @@ -17063,11 +17025,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" - integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -17261,6 +17218,16 @@ undici-types@~6.21.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== + +undici@^7.24.4: + version "7.29.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.29.0.tgz#ae0f6f62e06e057a9cbb7b2b5fde2bb74f791b8f" + integrity sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw== + unenv@2.0.0-rc.24, unenv@^2.0.0-rc.24: version "2.0.0-rc.24" resolved "https://registry.yarnpkg.com/unenv/-/unenv-2.0.0-rc.24.tgz#dd0035c3e93fedfa12c8454e34b7f17fe83efa2e" @@ -17371,11 +17338,6 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -18138,7 +18100,7 @@ yargs@^18.0.0: y18n "^5.0.5" yargs-parser "^22.0.0" -yauzl@^2.10.0, yauzl@^2.4.2: +yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==