Files
fabrikanabytok/apps/fabrikanabytok/components/planner/glb-placement-preview.tsx
2025-11-28 20:48:15 +01:00

85 lines
2.6 KiB
TypeScript

"use client"
/**
* GLB Placement Preview
* Shows ghost preview of model before placement
*/
import { useRef, useState } from "react"
import { useFrame, useThree } from "@react-three/fiber"
import { useGLTF } from "@react-three/drei"
import * as THREE from "three"
import { usePlannerStore } from "@/lib/store/planner-store"
export function GLBPlacementPreview() {
const { draggedProduct, snapSettings } = usePlannerStore()
const { camera, raycaster, pointer } = useThree()
const meshRef = useRef<THREE.Group>(null)
const [position, setPosition] = useState<THREE.Vector3>(new THREE.Vector3(0, 0, 0))
// Load model if dragging
const modelUrl = draggedProduct?.glbFile?.url || draggedProduct?.glbFile
const gltf = modelUrl ? useGLTF(modelUrl) : null
// Update position based on mouse
useFrame(() => {
if (!draggedProduct || !meshRef.current) return
// Raycast to find position on ground
raycaster.setFromCamera(pointer, camera)
// Create ground plane for raycasting
const groundPlane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
const intersectionPoint = new THREE.Vector3()
raycaster.ray.intersectPlane(groundPlane, intersectionPoint)
// Apply snapping if enabled
if (snapSettings.enabled) {
const gridSize = snapSettings.gridSize
intersectionPoint.x = Math.round(intersectionPoint.x / gridSize) * gridSize
intersectionPoint.z = Math.round(intersectionPoint.z / gridSize) * gridSize
}
setPosition(intersectionPoint)
meshRef.current.position.copy(intersectionPoint)
// Animate bobbing effect
const time = Date.now() * 0.001
meshRef.current.position.y = Math.sin(time * 2) * 0.05 + 0.1
})
if (!draggedProduct || !gltf) return null
return (
<group ref={meshRef} position={position}>
<primitive
object={gltf.scene.clone()}
scale={[1, 1, 1]}
/>
{/* Ghost material overlay */}
<mesh position={[0, 0, 0]}>
<boxGeometry args={[
(draggedProduct.dimensions?.width || 60) / 100,
(draggedProduct.dimensions?.height || 80) / 100,
(draggedProduct.dimensions?.depth || 60) / 100
]} />
<meshStandardMaterial
color="#00ff00"
transparent
opacity={0.3}
wireframe
/>
</mesh>
{/* Placement indicator */}
<mesh position={[0, -0.01, 0]} rotation={[-Math.PI / 2, 0, 0]}>
<ringGeometry args={[0.3, 0.35, 32]} />
<meshBasicMaterial color="#00ff00" transparent opacity={0.8} />
</mesh>
</group>
)
}