mattercontrol/src/stores/scene.ts

168 lines
4.1 KiB
TypeScript

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<SceneObject[]>([])
const selectedIds = shallowRef<Set<string>>(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,
}
})