Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f102d48
feat: implement FX Editor with toolbar, layout, and preview components
Dec 5, 2025
b3e0158
feat: enhance FX Editor Graph with particle management features inclu…
Dec 5, 2025
4d9f043
feat: enhance FX Editor with particle properties management, includin…
Dec 6, 2025
0b50618
feat: enhance FX Editor with new behavior properties and function edi…
Dec 7, 2025
327d10c
refactor: streamline FX Editor components by consolidating JSX struct…
Dec 7, 2025
00038a5
feat: enhance FX Editor behaviors and emission properties with improv…
Dec 8, 2025
20731b1
feat: implement Three.js JSON conversion for FX Editor, enabling part…
Dec 8, 2025
f85ea1a
feat: enhance FX Editor layout and properties management with state u…
Dec 8, 2025
3d22559
feat: integrate VFX system into FX Editor with enhanced particle beha…
Dec 11, 2025
4b8a613
Merge remote-tracking branch 'upstream/master' into feat/fx-editor
Dec 11, 2025
9c4b31c
refactor: remove unused loader file and streamline imports in FX Edit…
Dec 11, 2025
1c53452
refactor: clean up imports and formatting in FX Editor components for…
Dec 12, 2025
0cb7938
refactor: update Babylon.js imports across FX Editor components for c…
Dec 12, 2025
8ae0288
refactor: remove unused particle JSON file and enhance VFX component …
Dec 12, 2025
7c16a38
refactor: enhance VFXEffect structure and hierarchy management in FX …
Dec 12, 2025
0a337ed
refactor: streamline VFX hierarchy processing by consolidating node c…
Dec 12, 2025
173379a
refactor: update FX Editor properties to utilize VFXEffectNode for im…
Dec 12, 2025
8202dfe
refactor: update FX Editor properties to utilize VFXEffectNode for im…
Dec 12, 2025
97ae339
refactor: enhance VFX component structure by removing unused parsers …
Dec 12, 2025
e6b643b
refactor: remove VFXEmitterFactory and streamline VFX system creation…
Dec 12, 2025
4d56c70
refactor: enhance VFX system functionality by introducing capacity an…
Dec 13, 2025
f7f0f56
refactor: update VFX factories to utilize VFXData for improved resour…
Dec 13, 2025
5aaa41a
refactor: streamline VFX behavior implementations by removing unused …
Dec 14, 2025
fb2b1a2
refactor: enhance VFX parsing and system integration by implementing …
Dec 14, 2025
12d0b70
refactor: clean up whitespace in VFXSolidParticleSystem for improved …
Dec 14, 2025
64415bc
refactor: remove trailing whitespace in VFX files for improved code c…
Dec 14, 2025
a5e93a1
refactor: enhance ColorOverLife behavior by improving key interpolati…
Dec 14, 2025
33f9ab2
refactor: enhance VFX behavior and properties by implementing compreh…
Dec 14, 2025
23d54db
refactor: enhance VFX system by introducing a new VFXEmitterFactory f…
Dec 14, 2025
75496d3
feat: introduce EditorInspectorColorGradientField and GradientPicker …
Dec 14, 2025
fdf08ca
refactor: enhance VFX editor properties by adding emission burst mana…
Dec 15, 2025
305f388
feat: implement prewarm functionality in VFX systems, enhancing perfo…
Dec 15, 2025
85f29e8
feat: enhance FX editor functionality by implementing effect manageme…
Dec 15, 2025
86495cd
feat: add geometry field component to FX editor for improved mesh han…
Dec 15, 2025
ad69f12
refactor: rename FX editor components and interfaces for consistency,…
Dec 16, 2025
742ed1c
feat: implement comprehensive effect editor features including animat…
Dec 16, 2025
e8fa850
refactor: update FX editor components by renaming properties for cons…
Dec 16, 2025
4a31162
refactor: consolidate imports in geometry and renderer components, en…
Dec 16, 2025
cd0c0b9
refactor: update type definitions across effect components to improve…
Dec 16, 2025
8bf8710
refactor: standardize type definitions across effect behaviors and sy…
Dec 16, 2025
d95b589
refactor: update type definitions in effect behaviors and systems to …
Dec 16, 2025
8ca8513
refactor: update type definitions across effect components to use pre…
Dec 16, 2025
86799c3
refactor: transition emitter configuration to prefixed interfaces, en…
Dec 18, 2025
75075ed
refactor: remove deprecated particle system files to streamline the e…
Dec 18, 2025
7a3d382
refactor: rename particle system creation methods for consistency and…
Dec 18, 2025
e0d17f7
refactor: standardize property assignment syntax in effect systems fo…
Dec 18, 2025
a3109ff
refactor: enhance effect system configuration by standardizing proper…
Dec 18, 2025
80928c4
refactor: update effect editor to improve node handling and enhance e…
Dec 18, 2025
fc3a872
refactor: enhance effect editor and solid particle system by introduc…
Dec 18, 2025
3170af8
refactor: enhance data conversion and solid particle system behavior …
Dec 19, 2025
f4a1bc2
refactor: unify color function handling across behaviors by introduci…
Dec 19, 2025
d3ce226
refactor: enhance effect editor and solid particle system by implemen…
Dec 19, 2025
9c29ce4
refactor: enhance effect editor and particle system functionality by …
Dec 24, 2025
4318570
refactor: streamline effect class by consolidating system and group m…
Dec 31, 2025
d364ee4
Merge branch 'master' into feat/fx-editor
Jan 5, 2026
4a17a45
fix: imports
Jan 5, 2026
769920a
refactor: update effect editor to use QuarksConverter and improve dat…
Jan 5, 2026
e86402b
feat: enhance effect editor with Quarks file import functionality
Jan 5, 2026
52fcc54
refactor: update imports to use specific Babylon.js core modules
Jan 6, 2026
1fd2600
refactor: standardize Babylon.js imports across effect editor components
Jan 6, 2026
b53b8bf
feat: extend EffectSolidParticleSystem options with new properties
Jan 6, 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
6 changes: 5 additions & 1 deletion editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"license": "(Apache-2.0)",
"devDependencies": {
"@electron/rebuild": "4.0.2",
"@types/adm-zip": "^0.5.7",
"@types/decompress": "4.2.7",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/node": "^22",
Expand Down Expand Up @@ -59,6 +60,8 @@
"@radix-ui/react-menubar": "^1.0.4",
"@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 @@ -68,11 +71,11 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "1.1.11",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
"@recast-navigation/core": "0.43.0",
"@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.12.0",
"babylonjs": "8.41.0",
Expand Down Expand Up @@ -101,6 +104,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
230 changes: 230 additions & 0 deletions editor/src/editor/layout/inspector/fields/geometry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
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";
import { EditorInspectorNumberField } from "./number";

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">
<EditorInspectorNumberField noUndoRedo={this.props.noUndoRedo} label="Vertices" object={{ count: mesh.getTotalVertices() }} property="count" />
<EditorInspectorNumberField
noUndoRedo={this.props.noUndoRedo}
label="Faces"
object={{ count: mesh.getTotalIndices() ? mesh.getTotalIndices()! / 3 : 0 }}
property="count"
/>
</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