cube-parameters
Version:
A sophisticated 3D model viewer built with React, TypeScript, and Three.js, featuring advanced visualization tools, measurement capabilities, and lighting controls.
176 lines (164 loc) • 6.21 kB
text/typescript
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { useShallow } from 'zustand/react/shallow';
import * as THREE from 'three';
import type {
LoadedModel,
SunlightSettings,
AmbientLightSettings,
EnvironmentSettings,
ShadowQuality
} from '../types/model';
// App State Slice
interface AppStateSlice {
scene: THREE.Scene | null;
isInitialized: boolean;
setScene: (scene: THREE.Scene | null) => void;
setInitialized: (initialized: boolean) => void;
}
// UI State Slice
interface UIStateSlice {
showControlPanel: boolean;
showMeasurePanel: boolean;
activeControlTab: string;
activeTool: 'select' | 'point' | 'measure';
isOrthographic: boolean;
gridEnabled: boolean;
groundPlaneEnabled: boolean;
setShowControlPanel: (show: boolean) => void;
setShowMeasurePanel: (show: boolean) => void;
setActiveControlTab: (tab: string) => void;
setActiveTool: (tool: 'select' | 'point' | 'measure') => void;
setIsOrthographic: (orthographic: boolean) => void;
setGridEnabled: (enabled: boolean) => void;
setGroundPlaneEnabled: (enabled: boolean) => void;
}
// Scene State Slice
interface SceneStateSlice {
loadedModels: LoadedModel[];
currentModel: LoadedModel | null;
sunlight: SunlightSettings;
ambientLight: AmbientLightSettings;
shadowQuality: ShadowQuality;
environment: EnvironmentSettings;
isUploading: boolean;
uploadError: string | null;
setLoadedModels: (models: LoadedModel[]) => void;
setCurrentModel: (model: LoadedModel | null) => void;
setSunlight: (sunlight: SunlightSettings) => void;
setAmbientLight: (ambientLight: AmbientLightSettings) => void;
setShadowQuality: (quality: ShadowQuality) => void;
setEnvironment: (environment: EnvironmentSettings) => void;
setUploading: (uploading: boolean) => void;
setUploadError: (error: string | null) => void;
}
// Combined Store Type
type AppStore = AppStateSlice & UIStateSlice & SceneStateSlice;
export const useAppStore = create<AppStore>()(
devtools(
(set) => ({
// App State
scene: null,
isInitialized: false,
setScene: (scene) => set({ scene }, false, 'setScene'),
setInitialized: (isInitialized) => set({ isInitialized }, false, 'setInitialized'),
// UI State
showControlPanel: false,
showMeasurePanel: false,
activeControlTab: 'scene',
activeTool: 'select',
isOrthographic: false,
gridEnabled: true,
groundPlaneEnabled: false,
setShowControlPanel: (showControlPanel) => set({ showControlPanel }, false, 'setShowControlPanel'),
setShowMeasurePanel: (showMeasurePanel) => set({ showMeasurePanel }, false, 'setShowMeasurePanel'),
setActiveControlTab: (activeControlTab) => set({ activeControlTab }, false, 'setActiveControlTab'),
setActiveTool: (activeTool) => set({ activeTool }, false, 'setActiveTool'),
setIsOrthographic: (isOrthographic) => set({ isOrthographic }, false, 'setIsOrthographic'),
setGridEnabled: (gridEnabled) => set({ gridEnabled }, false, 'setGridEnabled'),
setGroundPlaneEnabled: (groundPlaneEnabled) => set({ groundPlaneEnabled }, false, 'setGroundPlaneEnabled'),
// Scene State
loadedModels: [],
currentModel: null,
sunlight: {
intensity: 1,
azimuth: 45,
elevation: 45,
color: '#ffffff',
castShadow: true
},
ambientLight: {
intensity: 0.4,
color: '#ffffff'
},
shadowQuality: 'medium' as const,
environment: {
showGrid: true,
groundColor: '#ffffff',
skyColor: '#87CEEB',
showGround: true,
preset: 'default'
},
isUploading: false,
uploadError: null,
setLoadedModels: (loadedModels) => set({ loadedModels }, false, 'setLoadedModels'),
setCurrentModel: (currentModel) => set({ currentModel }, false, 'setCurrentModel'),
setSunlight: (sunlight) => set({ sunlight }, false, 'setSunlight'),
setAmbientLight: (ambientLight) => set({ ambientLight }, false, 'setAmbientLight'),
setShadowQuality: (shadowQuality) => set({ shadowQuality }, false, 'setShadowQuality'),
setEnvironment: (environment) => set({ environment }, false, 'setEnvironment'),
setUploading: (isUploading) => set({ isUploading }, false, 'setUploading'),
setUploadError: (uploadError) => set({ uploadError }, false, 'setUploadError'),
}),
{
name: 'app-store',
}
)
);
// Fixed selector hooks using useShallow to prevent infinite loops
export const useAppState = () => useAppStore(
useShallow((state) => ({
scene: state.scene,
isInitialized: state.isInitialized,
setScene: state.setScene,
setInitialized: state.setInitialized,
}))
);
export const useUIState = () => useAppStore(
useShallow((state) => ({
showControlPanel: state.showControlPanel,
showMeasurePanel: state.showMeasurePanel,
activeControlTab: state.activeControlTab,
activeTool: state.activeTool,
isOrthographic: state.isOrthographic,
gridEnabled: state.gridEnabled,
groundPlaneEnabled: state.groundPlaneEnabled,
setShowControlPanel: state.setShowControlPanel,
setShowMeasurePanel: state.setShowMeasurePanel,
setActiveControlTab: state.setActiveControlTab,
setActiveTool: state.setActiveTool,
setIsOrthographic: state.setIsOrthographic,
setGridEnabled: state.setGridEnabled,
setGroundPlaneEnabled: state.setGroundPlaneEnabled,
}))
);
export const useSceneState = () => useAppStore(
useShallow((state) => ({
loadedModels: state.loadedModels,
currentModel: state.currentModel,
sunlight: state.sunlight,
ambientLight: state.ambientLight,
shadowQuality: state.shadowQuality,
environment: state.environment,
isUploading: state.isUploading,
uploadError: state.uploadError,
setLoadedModels: state.setLoadedModels,
setCurrentModel: state.setCurrentModel,
setSunlight: state.setSunlight,
setAmbientLight: state.setAmbientLight,
setShadowQuality: state.setShadowQuality,
setEnvironment: state.setEnvironment,
setUploading: state.setUploading,
setUploadError: state.setUploadError,
}))
);