Migrate to full 3D visualizer with React Three Fiber

Complete architectural overhaul from CSS to WebGL 3D rendering:

**Dependencies Added:**
- three, @types/three
- @react-three/fiber (R3F)
- @react-three/drei (helpers)

**New Components:**

1. components/configurator/scene.tsx
   - Canvas with shadows and tone mapping
   - PerspectiveCamera with OrbitControls
   - Lighting: Ambient + Directional + Spot + Environment
   - 3D Room: Floor (wood texture) + Walls with doorway opening
   - Limited camera rotation (minPolarAngle/maxPolarAngle)

2. components/configurator/door-3d.tsx
   - Door constructed from 3D mesh primitives (BoxGeometry)
   - Frame: Metal material (metalness 0.7, roughness 0.3)
   - Glass: PhysicalMaterial with transmission for realistic glass
   - Dynamic grid dividers based on gridType
   - Handles: 3D U-greep (vertical bar) or Klink (horizontal + sphere)
   - All meshes cast shadows

3. components/configurator/door-visualizer.tsx
   - Integrated Scene3D with Suspense
   - Loading fallback with spinner
   - Control hints for user interaction
   - Badge changed to "3D Voorbeeld"

**Features:**
- True depth and perspective
- Realistic shadows on floor and walls
- Reflective glass material
- Interactive camera (drag to rotate, scroll to zoom)
- Door appears IN the doorway, not floating
- Apartment environment preset for reflections
- Real-time updates from Zustand store

**Result:** Photorealistic 3D room with proper lighting, shadows, and materials

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Ubuntu
2026-02-10 16:30:52 +00:00
parent 8c6febea09
commit 065451988c
5 changed files with 867 additions and 97 deletions

View File

@@ -1,106 +1,37 @@
"use client";
import { Suspense } from "react";
import { useConfiguratorStore } from "@/lib/store";
import { Scene3D } from "./scene";
function LoadingFallback() {
return (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-slate-100 to-slate-200">
<div className="text-center">
<div className="mb-4 size-12 animate-spin rounded-full border-4 border-[#1A2E2E] border-t-[#C4D668]" />
<p className="text-sm font-medium text-[#1A2E2E]">3D Scene laden...</p>
</div>
</div>
);
}
export function DoorVisualizer() {
const { doorType, gridType, finish, handle } = useConfiguratorStore();
// Frame color mapping based on finish
const frameColor = {
zwart: "#1f1f1f",
brons: "#8B6F47",
grijs: "#525252",
}[finish];
// Door width varies by type
const doorWidth = doorType === "paneel" ? "w-[300px]" : "w-[240px]";
// Grid configuration
const gridConfig = {
"3-vlak": "grid-rows-3",
"4-vlak": "grid-rows-4",
geen: "",
}[gridType];
return (
<div className="relative flex h-full w-full items-center justify-center overflow-hidden rounded-[2.5rem]">
{/* Background room image with overlay */}
<div
className="absolute inset-0 bg-cover bg-center"
style={{ backgroundImage: "url(/images/hero.jpg)" }}
/>
<div className="absolute inset-0 bg-white/60" />
<div className="relative h-full w-full overflow-hidden rounded-[2.5rem]">
{/* Live Preview Badge */}
<div className="absolute left-8 top-8 z-10">
<div className="flex items-center gap-2 rounded-full bg-[#1A2E2E] px-4 py-2 text-sm font-semibold text-white shadow-lg">
<div className="size-2 animate-pulse rounded-full bg-[#C4D668]" />
Live Voorbeeld
3D Voorbeeld
</div>
</div>
{/* The Door Frame (CSS-based) */}
<div className="relative z-10 flex h-[500px] items-center justify-center">
<div
className={`relative h-[480px] ${doorWidth} rounded-sm border-[12px] shadow-2xl ring-1 ring-white/10`}
style={{ borderColor: frameColor }}
>
{/* The Glass Panels with Grid */}
{gridType !== "geen" ? (
<div
className={`grid h-full w-full gap-y-3 ${gridConfig}`}
style={{ backgroundColor: frameColor }}
>
{gridType === "3-vlak" &&
[1, 2, 3].map((i) => (
<div
key={i}
className="backdrop-blur-[1px] backdrop-brightness-110 bg-sky-100/10"
/>
))}
{gridType === "4-vlak" &&
[1, 2, 3, 4].map((i) => (
<div
key={i}
className="backdrop-blur-[1px] backdrop-brightness-110 bg-sky-100/10"
/>
))}
</div>
) : (
// No grid - single glass panel
<div className="h-full w-full backdrop-blur-[1px] backdrop-brightness-110 bg-sky-100/10" />
)}
{/* Handle Overlays */}
{doorType === "taats" && handle === "u-greep" && (
<div
className="absolute left-1/2 top-1/2 h-32 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full shadow-lg"
style={{ backgroundColor: frameColor }}
/>
)}
{doorType === "scharnier" && handle === "klink" && (
<div className="absolute right-4 top-1/2 flex -translate-y-1/2 items-center gap-1">
<div
className="h-2 w-12 rounded-full shadow-md"
style={{ backgroundColor: frameColor }}
/>
<div
className="size-3 rounded-full shadow-md ring-1 ring-white/20"
style={{ backgroundColor: finish === "brons" ? "#6B5434" : frameColor }}
/>
</div>
)}
{doorType === "paneel" && (
// Central vertical divider for paneel
<div
className="absolute left-1/2 top-0 h-full w-3 -translate-x-1/2 shadow-inner"
style={{ backgroundColor: frameColor }}
/>
)}
</div>
</div>
{/* 3D Scene */}
<Suspense fallback={<LoadingFallback />}>
<Scene3D />
</Suspense>
{/* Configuration Info Card */}
<div className="absolute bottom-8 left-8 right-8 z-10">
@@ -131,6 +62,13 @@ export function DoorVisualizer() {
</div>
</div>
</div>
{/* Controls Hint */}
<div className="absolute bottom-8 right-8 z-10 hidden lg:block">
<div className="rounded-xl bg-[#1A2E2E]/80 px-3 py-2 text-xs text-white backdrop-blur-sm">
<p className="font-medium">🖱 Drag to rotate Scroll to zoom</p>
</div>
</div>
</div>
);
}