From f4125db6a85f4d034a193bf094f55dd78697b403 Mon Sep 17 00:00:00 2001 From: wass08 Date: Thu, 29 Jan 2026 10:14:40 +0900 Subject: [PATCH] control bar --- apps/editor/components/editor/index.tsx | 26 ---- .../tools/item/use-placement-coordinator.tsx | 4 +- .../components/ui/action-menu/index.tsx | 6 +- .../ui/action-menu/view-toggles.tsx | 139 ++++++++++++++++++ .../viewer/src/components/viewer/index.tsx | 2 + .../src/components/viewer/viewer-camera.tsx | 11 ++ packages/viewer/src/store/use-viewer.ts | 12 ++ .../viewer/src/systems/level/level-system.tsx | 5 +- 8 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 apps/editor/components/ui/action-menu/view-toggles.tsx create mode 100644 packages/viewer/src/components/viewer/viewer-camera.tsx diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index 7d36e1ab..f2d15187 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -178,29 +178,3 @@ const Grid = ({ ) } - -const LevelModeSwitcher = () => { - const setLevelMode = useViewer((state) => state.setLevelMode) - const levelMode = useViewer((state) => state.levelMode) - - return ( -
- - -
- ) -} diff --git a/apps/editor/components/tools/item/use-placement-coordinator.tsx b/apps/editor/components/tools/item/use-placement-coordinator.tsx index 94a4285a..7e163096 100644 --- a/apps/editor/components/tools/item/use-placement-coordinator.tsx +++ b/apps/editor/components/tools/item/use-placement-coordinator.tsx @@ -418,8 +418,10 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea const newRotationY = (currentRotation[1] ?? 0) + rotationDelta draft.rotation = [currentRotation[0], newRotationY, currentRotation[2]] - // Ref + cursor mesh only — no store update during drag + // Ref + cursor mesh + item mesh — no store update during drag cursorRef.current.rotation.y = newRotationY + const mesh = sceneRegistry.nodes.get(draft.id) + if (mesh) mesh.rotation.y = newRotationY revalidate() } } diff --git a/apps/editor/components/ui/action-menu/index.tsx b/apps/editor/components/ui/action-menu/index.tsx index bc3ceec9..6c5b16c8 100644 --- a/apps/editor/components/ui/action-menu/index.tsx +++ b/apps/editor/components/ui/action-menu/index.tsx @@ -10,7 +10,7 @@ import useEditor from "@/store/use-editor"; import { AnimatePresence, motion } from "motion/react"; import { ItemCatalog } from "../item-catalog/item-catalog"; import { FurnishTools } from "./furnish-tools"; -// import { ViewToggles } from "./view-toggles"; +import { ViewToggles } from "./view-toggles"; export function ActionMenu({ className }: { className?: string }) { const phase = useEditor((state) => state.phase); @@ -138,8 +138,8 @@ export function ActionMenu({ className }: { className?: string }) {
- {/*
*/} - {/* */} +
+
diff --git a/apps/editor/components/ui/action-menu/view-toggles.tsx b/apps/editor/components/ui/action-menu/view-toggles.tsx new file mode 100644 index 00000000..48bd6294 --- /dev/null +++ b/apps/editor/components/ui/action-menu/view-toggles.tsx @@ -0,0 +1,139 @@ +'use client' + +import { useViewer } from '@pascal-app/viewer' +import { Box, Camera, Diamond, Image, Layers, Layers2 } from 'lucide-react' +import { Button } from '@/components/ui/primitives/button' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/primitives/tooltip' +import { cn } from '@/lib/utils' + +const levelModeLabels: Record<'stacked' | 'exploded' | 'solo', string> = { + stacked: 'Stacked', + exploded: 'Exploded', + solo: 'Solo', +} + +const levelModeOrder: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo'] + +export function ViewToggles() { + const cameraMode = useViewer((state) => state.cameraMode) + const setCameraMode = useViewer((state) => state.setCameraMode) + const levelMode = useViewer((state) => state.levelMode) + const setLevelMode = useViewer((state) => state.setLevelMode) + const showScans = useViewer((state) => state.showScans) + const setShowScans = useViewer((state) => state.setShowScans) + const showGuides = useViewer((state) => state.showGuides) + const setShowGuides = useViewer((state) => state.setShowGuides) + + const toggleCameraMode = () => { + setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective') + } + + const cycleLevelMode = () => { + if (levelMode === 'manual') { + setLevelMode('stacked') + return + } + const currentIndex = levelModeOrder.indexOf(levelMode as 'stacked' | 'exploded' | 'solo') + const nextIndex = (currentIndex + 1) % levelModeOrder.length + const nextMode = levelModeOrder[nextIndex] + if (nextMode) setLevelMode(nextMode) + } + + return ( +
+ {/* Camera Mode */} + + + + + +

Camera: {cameraMode === 'perspective' ? 'Perspective' : 'Orthographic'}

+
+
+ + {/* Level Mode */} + + + + + +

Levels: {levelMode === 'manual' ? 'Manual' : levelModeLabels[levelMode as keyof typeof levelModeLabels]}

+
+
+ + {/* Show Scans */} + + + + + +

Scans: {showScans ? 'Visible' : 'Hidden'}

+
+
+ + {/* Show Guides */} + + + + + +

Guides: {showGuides ? 'Visible' : 'Hidden'}

+
+
+
+ ) +} diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx index fc4cfaa1..0f10ed49 100644 --- a/packages/viewer/src/components/viewer/index.tsx +++ b/packages/viewer/src/components/viewer/index.tsx @@ -7,6 +7,7 @@ import * as THREE from 'three/webgpu' import { LevelSystem } from '../../systems/level/level-system' import { SceneRenderer } from '../renderers/scene-renderer' import PostProcessing from './post-processing' +import { ViewerCamera } from './viewer-camera' declare module '@react-three/fiber' { interface ThreeElements extends ThreeToJSXElements {} @@ -31,6 +32,7 @@ const Viewer: React.FC = ({ children }) => { camera={{ position: [50, 50, 50], fov: 50 }} > + diff --git a/packages/viewer/src/components/viewer/viewer-camera.tsx b/packages/viewer/src/components/viewer/viewer-camera.tsx new file mode 100644 index 00000000..20d7ccaa --- /dev/null +++ b/packages/viewer/src/components/viewer/viewer-camera.tsx @@ -0,0 +1,11 @@ +import { OrthographicCamera, PerspectiveCamera } from "@react-three/drei" +import useViewer from "../../store/use-viewer" + +export const ViewerCamera = () => { + const cameraMode = useViewer((state) => state.cameraMode) + return cameraMode === 'perspective' ? ( + + ) : ( + + ) +} \ No newline at end of file diff --git a/packages/viewer/src/store/use-viewer.ts b/packages/viewer/src/store/use-viewer.ts index 57a6ac4f..88e1f784 100644 --- a/packages/viewer/src/store/use-viewer.ts +++ b/packages/viewer/src/store/use-viewer.ts @@ -34,6 +34,12 @@ type ViewerState = { levelMode: "stacked" | "exploded" | "solo" | "manual"; setLevelMode: (mode: "stacked" | "exploded" | "solo" | "manual") => void; + showScans: boolean; + setShowScans: (show: boolean) => void; + + showGuides: boolean; + setShowGuides: (show: boolean) => void; + // Smart selection update setSelection: (updates: Partial) => void; resetSelection: () => void; @@ -52,6 +58,12 @@ const useViewer = create()((set, get) => ({ levelMode: "stacked", setLevelMode: (mode) => set({ levelMode: mode }), + showScans: true, + setShowScans: (show) => set({ showScans: show }), + + showGuides: true, + setShowGuides: (show) => set({ showGuides: show }), + setSelection: (updates) => set((state) => { const newSelection = { ...state.selection, ...updates }; diff --git a/packages/viewer/src/systems/level/level-system.tsx b/packages/viewer/src/systems/level/level-system.tsx index 3f573f6d..3151851d 100644 --- a/packages/viewer/src/systems/level/level-system.tsx +++ b/packages/viewer/src/systems/level/level-system.tsx @@ -9,14 +9,17 @@ const EXPLODED_GAP = 5 export const LevelSystem = () => { useFrame((_, delta) => { const levelMode = useViewer.getState().levelMode + const selectedLevel = useViewer.getState().selection.levelId sceneRegistry.byType.level.forEach((levelId) => { const obj = sceneRegistry.nodes.get(levelId) if (obj) { const level = useScene.getState().nodes[levelId as LevelNode['id']] const targetY = ((level as any).level || 0) * - (LEVEL_HEIGHT + (levelMode === 'stacked' ? 0 : EXPLODED_GAP)) + (LEVEL_HEIGHT + (levelMode === 'exploded' ? EXPLODED_GAP: 0)) obj.position.y = lerp(obj.position.y, targetY, delta * 3) + + obj.visible = levelMode !== 'solo' || level?.id === selectedLevel || !selectedLevel } }) })