import { defineStore } from 'pinia' import { shallowRef, computed } from 'vue' import * as THREE from 'three' export interface SceneObject { id: string name: string mesh: THREE.Mesh visible: boolean locked: boolean } export const useSceneStore = defineStore('scene', () => { // Use shallowRef to avoid Vue making THREE.Mesh objects deeply reactive const objects = shallowRef([]) const selectedIds = shallowRef>(new Set()) let threeScene: THREE.Scene | null = null let nextId = 1 function generateId(): string { return `obj_${nextId++}` } function generateName(baseName: string): string { const existingNames = objects.value.map((o) => o.name) if (!existingNames.includes(baseName)) { return baseName } let counter = 1 while (existingNames.includes(`${baseName} ${counter}`)) { counter++ } return `${baseName} ${counter}` } function setScene(scene: THREE.Scene) { threeScene = scene } function addObject(mesh: THREE.Mesh, name?: string): SceneObject { if (!threeScene) { throw new Error('Scene not initialized. Call setScene() first.') } const baseName = name ?? 'Object' const objectName = generateName(baseName) const sceneObject: SceneObject = { id: generateId(), name: objectName, mesh, visible: true, locked: false, } threeScene.add(mesh) objects.value = [...objects.value, sceneObject] return sceneObject } function removeObject(id: string): boolean { const obj = objects.value.find((o) => o.id === id) if (!obj) return false if (threeScene) { threeScene.remove(obj.mesh) } obj.mesh.geometry.dispose() if (obj.mesh.material instanceof THREE.Material) { obj.mesh.material.dispose() } objects.value = objects.value.filter((o) => o.id !== id) return true } function clearScene(): void { const ids = objects.value.map((o) => o.id) for (const id of ids) { removeObject(id) } } function getObject(id: string): SceneObject | undefined { return objects.value.find((o) => o.id === id) } function setObjectVisible(id: string, visible: boolean): void { objects.value = objects.value.map((obj) => { if (obj.id === id) { obj.mesh.visible = visible return { ...obj, visible } } return obj }) } function setObjectLocked(id: string, locked: boolean): void { objects.value = objects.value.map((obj) => (obj.id === id ? { ...obj, locked } : obj)) } function renameObject(id: string, name: string): void { objects.value = objects.value.map((obj) => (obj.id === id ? { ...obj, name } : obj)) } function select(id: string): void { const obj = objects.value.find((o) => o.id === id) if (!obj || obj.locked) return selectedIds.value = new Set([id]) } function addToSelection(id: string): void { const obj = objects.value.find((o) => o.id === id) if (!obj || obj.locked) return selectedIds.value = new Set([...selectedIds.value, id]) } function toggleSelection(id: string): void { const obj = objects.value.find((o) => o.id === id) if (!obj || obj.locked) return const newSet = new Set(selectedIds.value) if (newSet.has(id)) { newSet.delete(id) } else { newSet.add(id) } selectedIds.value = newSet } function selectAll(): void { const unlocked = objects.value.filter((o) => !o.locked).map((o) => o.id) selectedIds.value = new Set(unlocked) } function clearSelection(): void { selectedIds.value = new Set() } const objectList = computed(() => objects.value) const objectCount = computed(() => objects.value.length) const selectedObjects = computed(() => objects.value.filter((o) => selectedIds.value.has(o.id))) return { // State objects, selectedIds, // Getters objectList, objectCount, selectedObjects, // Actions setScene, addObject, removeObject, clearScene, getObject, setObjectVisible, setObjectLocked, renameObject, select, addToSelection, toggleSelection, selectAll, clearSelection, } })