Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
7a71968
feat: implement FX Editor with toolbar, layout, and preview components
Dec 5, 2025
9d18718
feat: enhance FX Editor Graph with particle management features inclu…
Dec 5, 2025
83dc93b
feat: enhance FX Editor with particle properties management, includin…
Dec 6, 2025
d5ba951
feat: enhance FX Editor with new behavior properties and function edi…
Dec 7, 2025
35d29d9
refactor: streamline FX Editor components by consolidating JSX struct…
Dec 7, 2025
a60d661
feat: enhance FX Editor behaviors and emission properties with improv…
Dec 8, 2025
d816d61
feat: implement Three.js JSON conversion for FX Editor, enabling part…
Dec 8, 2025
118607e
feat: enhance FX Editor layout and properties management with state u…
Dec 8, 2025
cacb77b
feat: integrate VFX system into FX Editor with enhanced particle beha…
Dec 11, 2025
867e150
refactor: remove unused loader file and streamline imports in FX Edit…
Dec 11, 2025
a7b90fc
refactor: clean up imports and formatting in FX Editor components for…
Dec 12, 2025
38fa0d3
refactor: update Babylon.js imports across FX Editor components for c…
Dec 12, 2025
766aa1c
refactor: remove unused particle JSON file and enhance VFX component …
Dec 12, 2025
86a9a2f
refactor: enhance VFXEffect structure and hierarchy management in FX …
Dec 12, 2025
7970d5a
refactor: streamline VFX hierarchy processing by consolidating node c…
Dec 12, 2025
f6e74b7
refactor: update FX Editor properties to utilize VFXEffectNode for im…
Dec 12, 2025
cbf1feb
refactor: update FX Editor properties to utilize VFXEffectNode for im…
Dec 12, 2025
b6a406d
refactor: enhance VFX component structure by removing unused parsers …
Dec 12, 2025
c9499b5
refactor: remove VFXEmitterFactory and streamline VFX system creation…
Dec 12, 2025
32621e6
refactor: enhance VFX system functionality by introducing capacity an…
Dec 13, 2025
9776ed2
refactor: update VFX factories to utilize VFXData for improved resour…
Dec 13, 2025
1f97121
refactor: streamline VFX behavior implementations by removing unused …
Dec 14, 2025
86e2b38
refactor: enhance VFX parsing and system integration by implementing …
Dec 14, 2025
1447e15
refactor: clean up whitespace in VFXSolidParticleSystem for improved …
Dec 14, 2025
957baab
refactor: remove trailing whitespace in VFX files for improved code c…
Dec 14, 2025
d384259
refactor: enhance ColorOverLife behavior by improving key interpolati…
Dec 14, 2025
8349b6a
refactor: enhance VFX behavior and properties by implementing compreh…
Dec 14, 2025
d590364
refactor: enhance VFX system by introducing a new VFXEmitterFactory f…
Dec 14, 2025
84154f0
feat: introduce EditorInspectorColorGradientField and GradientPicker …
Dec 14, 2025
7dda021
refactor: enhance VFX editor properties by adding emission burst mana…
Dec 15, 2025
b0ae624
feat: implement prewarm functionality in VFX systems, enhancing perfo…
Dec 15, 2025
760cf3b
feat: enhance FX editor functionality by implementing effect manageme…
Dec 15, 2025
87c78d3
feat: add geometry field component to FX editor for improved mesh han…
Dec 15, 2025
7c67d01
refactor: rename FX editor components and interfaces for consistency,…
Dec 16, 2025
955ffea
feat: implement comprehensive effect editor features including animat…
Dec 16, 2025
22ee3ca
refactor: update FX editor components by renaming properties for cons…
Dec 16, 2025
0014b3e
refactor: consolidate imports in geometry and renderer components, en…
Dec 16, 2025
77616dd
refactor: update type definitions across effect components to improve…
Dec 16, 2025
0745cb1
refactor: standardize type definitions across effect behaviors and sy…
Dec 16, 2025
d0172b5
refactor: update type definitions in effect behaviors and systems to …
Dec 16, 2025
b0f9207
refactor: update type definitions across effect components to use pre…
Dec 16, 2025
8b4c29a
refactor: transition emitter configuration to prefixed interfaces, en…
Dec 18, 2025
5d54c91
refactor: remove deprecated particle system files to streamline the e…
Dec 18, 2025
5de104e
refactor: rename particle system creation methods for consistency and…
Dec 18, 2025
25ed26b
refactor: standardize property assignment syntax in effect systems fo…
Dec 18, 2025
b93d7d2
refactor: enhance effect system configuration by standardizing proper…
Dec 18, 2025
286c380
refactor: update effect editor to improve node handling and enhance e…
Dec 18, 2025
50ff8b6
refactor: enhance effect editor and solid particle system by introduc…
Dec 18, 2025
3462b03
refactor: enhance data conversion and solid particle system behavior …
Dec 19, 2025
bdc1a51
refactor: unify color function handling across behaviors by introduci…
Dec 19, 2025
ea10e4e
refactor: enhance effect editor and solid particle system by implemen…
Dec 19, 2025
d0c2c8a
refactor: enhance effect editor and particle system functionality by …
Dec 24, 2025
b12c092
refactor: streamline effect class by consolidating system and group m…
Dec 31, 2025
4991e7f
fix: imports
Jan 5, 2026
75b09fb
refactor: update effect editor to use QuarksConverter and improve dat…
Jan 5, 2026
517ca61
feat: enhance effect editor with Quarks file import functionality
Jan 5, 2026
cdc77fc
refactor: update imports to use specific Babylon.js core modules
Jan 6, 2026
dba81d3
refactor: standardize Babylon.js imports across effect editor components
Jan 6, 2026
804a308
feat: extend EffectSolidParticleSystem options with new properties
Jan 6, 2026
e5a3804
feat: enhance NodeFactory with new node creation methods
Jan 6, 2026
cfe4ec7
fix: add shader imports for particle systems in effect editor preview
Jan 24, 2026
f933997
refactor: update rotation handling in converters and properties
Jan 24, 2026
159e65a
refactor: clean up imports in EffectEditorObjectProperties
Feb 2, 2026
81776f6
refactor: enhance transform conversion in QuarksConverter and update …
Feb 2, 2026
fe2a419
refactor: restructure Quarks converters and introduce new utility fun…
Feb 9, 2026
caad57c
refactor: streamline imports and enhance code readability in effect e…
Feb 9, 2026
03f5749
refactor: update geometry field layout for improved readability
Feb 9, 2026
eb788ea
feat: enhance FX editor functionality and file handling
Feb 13, 2026
b2237c6
refactor: update effect data handling and serialization in effect editor
Feb 13, 2026
3bc536a
feat: implement deserialization utilities for effect data handling
Feb 13, 2026
0838819
refactor: update geometry and material factory methods for improved c…
Feb 15, 2026
f54a4e7
feat: enhance behavior system with new constants and refactor behavio…
Feb 18, 2026
320ac03
refactor: enhance Unity converters with utility functions for propert…
Feb 20, 2026
abd087f
refactor: clean up comments and improve clarity in effect type defini…
Feb 27, 2026
9d3cd94
refact: lint
julien-moreau Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.2.0",
Expand All @@ -73,6 +74,7 @@
"@recast-navigation/generators": "0.43.0",
"@xterm/addon-fit": "0.11.0",
"@xterm/xterm": "6.1.0-beta.22",
"adm-zip": "^0.5.16",
"assimpjs": "0.0.10",
"axios": "1.13.5",
"babylonjs": "9.0.0",
Expand Down Expand Up @@ -103,6 +105,7 @@
"framer-motion": "12.23.24",
"fs-extra": "11.2.0",
"glob": "11.1.0",
"js-yaml": "^4.1.1",
"markdown-to-jsx": "7.6.2",
"math-expression-evaluator": "^2.0.6",
"md5": "2.3.0",
Expand Down
30 changes: 28 additions & 2 deletions editor/src/editor/layout/assets-browser.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clipboard, webUtils } from "electron";
import { clipboard, webUtils, ipcRenderer } from "electron";
import { dirname, join, extname, basename } from "path/posix";
import { copyFile, copy, mkdir, move, pathExists, readdir, stat, writeFile, writeJSON } from "fs-extra";

Expand Down Expand Up @@ -832,6 +832,8 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE

<ContextMenuSeparator />
<ContextMenuItem onClick={() => this._handleAddFullScreenGUI()}>Fullscreen GUI</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem onClick={() => this._handleAddFX()}>FX</ContextMenuItem>
</>
);
}
Expand Down Expand Up @@ -1296,6 +1298,25 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
return this._refreshItems(this.state.browsedPath);
}

private async _handleAddFX(): Promise<void> {
if (!this.state.browsedPath) {
return;
}

const name = await findAvailableFilename(this.state.browsedPath, "New Effect", ".fx");
const defaultEffectFile = {
version: "1.0.0",
effects: [],
};

await writeJSON(join(this.state.browsedPath, name), defaultEffectFile, {
spaces: "\t",
encoding: "utf-8",
});

return this._refreshItems(this.state.browsedPath);
}

private async _handleAddFullScreenGUI(): Promise<void> {
if (!this.state.browsedPath) {
return;
Expand Down Expand Up @@ -1458,9 +1479,14 @@ export class EditorAssetsBrowser extends Component<IEditorAssetsBrowserProps, IE
case ".tsx":
case ".js":
case ".jsx":
case ".fx":
case ".json":
return execNodePty(`code "${item.props.absolutePath}"`);

case ".fx":
return ipcRenderer.send("window:open", "build/src/editor/windows/effect-editor", {
filePath: item.props.absolutePath,
projectConfiguration: { ...projectConfiguration },
});
}
}

Expand Down
234 changes: 234 additions & 0 deletions editor/src/editor/layout/inspector/fields/geometry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { DragEvent, Component, PropsWithChildren, ReactNode } from "react";
import { extname } from "path/posix";

import { toast } from "sonner";

import { XMarkIcon } from "@heroicons/react/20/solid";
import { MdOutlineQuestionMark } from "react-icons/md";

import { Scene, Mesh } from "babylonjs";

import { isScene } from "../../../../tools/guards/scene";
import { registerUndoRedo } from "../../../../tools/undoredo";

import { configureImportedNodeIds, loadImportedSceneFile } from "../../preview/import/import";

export interface IEditorInspectorGeometryFieldProps extends PropsWithChildren {
title: string;
property: string;
object: any;

noUndoRedo?: boolean;

scene?: Scene;
onChange?: (mesh: Mesh | null) => void;
}

export interface IEditorInspectorGeometryFieldState {
dragOver: boolean;
loading: boolean;
}

export class EditorInspectorGeometryField extends Component<IEditorInspectorGeometryFieldProps, IEditorInspectorGeometryFieldState> {
public constructor(props: IEditorInspectorGeometryFieldProps) {
super(props);

this.state = {
dragOver: false,
loading: false,
};
}

public render(): ReactNode {
const mesh = this.props.object[this.props.property] as Mesh | null | undefined;

return (
<div
onDrop={(ev) => this._handleDrop(ev)}
onDragOver={(ev) => this._handleDragOver(ev)}
onDragLeave={(ev) => this._handleDragLeave(ev)}
className={`flex flex-col w-full p-5 rounded-lg ${this.state.dragOver ? "bg-muted-foreground/75 dark:bg-muted-foreground/20" : "bg-muted-foreground/10 dark:bg-muted-foreground/5"} transition-all duration-300 ease-in-out`}
>
<div className="flex gap-4 w-full">
{this._getPreviewComponent(mesh)}

<div className="flex flex-col w-full">
<div className="flex flex-col px-2">
<div>{this.props.title}</div>
{mesh && <div className="text-sm text-muted-foreground">{mesh.name}</div>}
</div>

{mesh && (
<div className="flex flex-col gap-1 mt-1 w-full">
<div className="flex justify-between items-center px-2 py-2">
<div className="w-1/2">Vertices</div>
<div className="flex justify-between items-center w-full">
<div className="text-white/50">{mesh.getTotalVertices()}</div>
</div>
</div>
<div className="flex justify-between items-center px-2 py-2">
<div className="w-1/2">Faces</div>
<div className="flex justify-between items-center w-full">
<div className="text-white/50">{mesh.getTotalIndices() ? mesh.getTotalIndices() / 3 : 0}</div>
</div>
</div>
</div>
)}
</div>
<div
onClick={() => {
const oldMesh = this.props.object[this.props.property];

this.props.object[this.props.property] = null;
this.props.onChange?.(null);

if (!this.props.noUndoRedo) {
registerUndoRedo({
executeRedo: true,
undo: () => {
this.props.object[this.props.property] = oldMesh;
},
redo: () => {
this.props.object[this.props.property] = null;
},
});
}

this.forceUpdate();
}}
className="flex justify-center items-center w-24 h-full hover:bg-muted-foreground rounded-lg transition-all duration-300"
>
{mesh && <XMarkIcon className="w-6 h-6" />}
</div>
</div>

{mesh && this.props.children}
</div>
);
}

private _getPreviewComponent(mesh: Mesh | null | undefined): ReactNode {
return (
<div className={`flex justify-center items-center ${mesh ? "w-24 h-24" : "w-8 h-8"} aspect-square`}>
{mesh ? (
<div className="w-24 h-24 flex items-center justify-center bg-background rounded-lg">
<div className="text-xs text-center text-muted-foreground">{mesh.name}</div>
</div>
) : (
<MdOutlineQuestionMark className="w-8 h-8" />
)}
</div>
);
}

private _handleDragOver(ev: DragEvent<HTMLDivElement>): void {
ev.preventDefault();
this.setState({ dragOver: true });
}

private _handleDragLeave(ev: DragEvent<HTMLDivElement>): void {
ev.preventDefault();
this.setState({ dragOver: false });
}

private async _handleDrop(ev: DragEvent<HTMLDivElement>): Promise<void> {
ev.preventDefault();
this.setState({ dragOver: false, loading: true });

try {
const absolutePath = JSON.parse(ev.dataTransfer.getData("assets"))[0];
const extension = extname(absolutePath).toLowerCase();

const supportedExtensions = [".x", ".b3d", ".dae", ".glb", ".gltf", ".fbx", ".stl", ".lwo", ".dxf", ".obj", ".3ds", ".ms3d", ".blend", ".babylon"];

if (!supportedExtensions.includes(extension)) {
toast.error(`Unsupported geometry format: ${extension}`);
this.setState({ loading: false });
return;
}

const scene = this.props.scene ?? (isScene(this.props.object) ? this.props.object : this.props.object.getScene?.());

if (!scene) {
toast.error("Scene is not available");
this.setState({ loading: false });
return;
}

const result = await loadImportedSceneFile(scene, absolutePath);

if (!result || !result.meshes || result.meshes.length === 0) {
toast.error("Failed to load geometry file");
this.setState({ loading: false });
return;
}

// Use the first mesh or find a mesh without parent
let importedMesh: Mesh | null = null;
for (const m of result.meshes) {
if (m instanceof Mesh && !m.parent) {
importedMesh = m;
break;
}
}

if (!importedMesh && result.meshes.length > 0 && result.meshes[0] instanceof Mesh) {
importedMesh = result.meshes[0];
}

if (!importedMesh) {
toast.error("No valid mesh found in geometry file");
this.setState({ loading: false });
return;
}

// Configure imported mesh
configureImportedNodeIds(importedMesh);
importedMesh.setEnabled(false); // Hide the source mesh

const oldMesh = this.props.object[this.props.property];

this.props.object[this.props.property] = importedMesh;
this.props.onChange?.(importedMesh);

if (!this.props.noUndoRedo) {
registerUndoRedo({
executeRedo: true,
undo: () => {
this.props.object[this.props.property] = oldMesh;
if (importedMesh && importedMesh !== oldMesh) {
importedMesh.dispose();
}
},
redo: () => {
this.props.object[this.props.property] = importedMesh;
},
onLost: () => {
if (importedMesh && importedMesh !== oldMesh) {
importedMesh.dispose();
}
},
});
}

// Dispose other meshes from the imported file
for (const m of result.meshes) {
if (m !== importedMesh) {
m.dispose();
}
}

// Dispose transform nodes
for (const tn of result.transformNodes) {
tn.dispose();
}

this.forceUpdate();
} catch (error) {
console.error("Failed to load geometry:", error);
toast.error(`Failed to load geometry: ${error instanceof Error ? error.message : String(error)}`);
} finally {
this.setState({ loading: false });
}
}
}
Loading