feat: scene traps, material legends, and scene editor window

Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-24 16:29:43 +08:00
parent d3b1c4660d
commit 02d73ddf81
39 changed files with 2563 additions and 36 deletions
+35
View File
@@ -5,6 +5,7 @@ import type {
EffectsState,
GraphNodeId,
MaterialId,
MaterialLegend,
MaterialsOverlayEvent,
MaterialsOverlayState,
MediaAsset,
@@ -20,6 +21,9 @@ import type {
SceneDarknessEvent,
SceneDarknessState,
SceneId,
SceneTrap,
SceneTrapsEvent,
SceneTrapsState,
SceneViewEvent,
SceneViewState,
VideoPlaybackEvent,
@@ -60,6 +64,7 @@ export const ipcChannels = {
updateCampaignAudios: 'project.updateCampaignAudios',
upsertMaterial: 'project.upsertMaterial',
setMaterialRotation: 'project.setMaterialRotation',
setMaterialLegend: 'project.setMaterialLegend',
deleteMaterial: 'project.deleteMaterial',
setMaterialsOrder: 'project.setMaterialsOrder',
pickMaterialImage: 'project.pickMaterialImage',
@@ -121,6 +126,8 @@ export const ipcChannels = {
closeNpcsEditor: 'windows.closeNpcsEditor',
openNpcs: 'windows.openNpcs',
closeNpcs: 'windows.closeNpcs',
openSceneEditor: 'windows.openSceneEditor',
closeSceneEditor: 'windows.closeSceneEditor',
},
session: {
stateChanged: 'session.stateChanged',
@@ -145,6 +152,11 @@ export const ipcChannels = {
dispatch: 'sceneDarkness.dispatch',
stateChanged: 'sceneDarkness.stateChanged',
},
sceneTraps: {
getState: 'sceneTraps.getState',
dispatch: 'sceneTraps.dispatch',
stateChanged: 'sceneTraps.stateChanged',
},
sceneView: {
getState: 'sceneView.getState',
dispatch: 'sceneView.dispatch',
@@ -210,6 +222,7 @@ export type IpcEventMap = {
[ipcChannels.materialsOverlay.stateChanged]: { state: MaterialsOverlayState };
[ipcChannels.npcsOverlay.stateChanged]: { state: NpcsOverlayState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.sceneTraps.stateChanged]: { state: SceneTrapsState };
[ipcChannels.sceneView.stateChanged]: { state: SceneViewState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: { snapshot: LicenseSnapshot };
@@ -298,6 +311,10 @@ export type IpcInvokeMap = {
req: { materialId: MaterialId; rotationDeg: 0 | 90 | 180 | 270 };
res: { project: Project };
};
[ipcChannels.project.setMaterialLegend]: {
req: { materialId: MaterialId; legend: MaterialLegend | null };
res: { project: Project };
};
[ipcChannels.project.deleteMaterial]: {
req: { materialId: MaterialId };
res: { project: Project };
@@ -569,6 +586,14 @@ export type IpcInvokeMap = {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.openSceneEditor]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.closeSceneEditor]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.materialsOverlay.getState]: {
req: Record<string, never>;
res: { state: MaterialsOverlayState };
@@ -601,6 +626,14 @@ export type IpcInvokeMap = {
req: { event: SceneDarknessEvent };
res: { ok: true };
};
[ipcChannels.sceneTraps.getState]: {
req: Record<string, never>;
res: { state: SceneTrapsState };
};
[ipcChannels.sceneTraps.dispatch]: {
req: { event: SceneTrapsEvent };
res: { ok: true };
};
[ipcChannels.sceneView.getState]: {
req: Record<string, never>;
res: { state: SceneViewState };
@@ -646,6 +679,7 @@ export type LegacyIpcEventMap = {
[ipcChannels.materialsOverlay.stateChanged]: { state: MaterialsOverlayState };
[ipcChannels.npcsOverlay.stateChanged]: { state: NpcsOverlayState };
[ipcChannels.sceneDarkness.stateChanged]: { state: SceneDarknessState };
[ipcChannels.sceneTraps.stateChanged]: { state: SceneTrapsState };
[ipcChannels.sceneView.stateChanged]: { state: SceneViewState };
[ipcChannels.video.stateChanged]: { state: VideoPlaybackState };
[ipcChannels.license.statusChanged]: Record<string, never>;
@@ -660,6 +694,7 @@ export type ScenePatch = {
previewVideoAutostart?: boolean;
previewRotationDeg?: 0 | 90 | 180 | 270;
darkenScene?: boolean;
traps?: SceneTrap[];
settings?: Partial<Scene['settings']>;
media?: Partial<Scene['media']>;
layout?: Partial<Scene['layout']>;