diff --git a/components/configurator/door-3d-enhanced.tsx b/components/configurator/door-3d-enhanced.tsx index 8d5ceba..f6f38da 100644 --- a/components/configurator/door-3d-enhanced.tsx +++ b/components/configurator/door-3d-enhanced.tsx @@ -1,9 +1,23 @@ "use client"; -import { useRef } from "react"; +import { useRef, useMemo } from "react"; import { useConfiguratorStore } from "@/lib/store"; import { RoundedBox } from "@react-three/drei"; import * as THREE from "three"; +import { + Beugelgreep, + Hoekgreep, + Maangreep, + Ovaalgreep, + Klink, + UGreep, +} from "./handles-3d"; +import { + createStandardGlass, + createRoundedCornerGlass, + createInvertedUGlass, + createNormalUGlass, +} from "@/lib/glass-patterns"; // Steel material - fallback to solid color for now const SteelMaterial = ({ color }: { color: string }) => ( @@ -43,7 +57,7 @@ function DimensionLabel({ } export function Door3DEnhanced() { - const { doorType, gridType, finish, handle, doorLeafWidth, height } = + const { doorType, gridType, finish, handle, glassPattern, doorLeafWidth, height } = useConfiguratorStore(); const doorRef = useRef(null); @@ -151,47 +165,134 @@ export function Door3DEnhanced() { )} - {/* GLASS PANEL - Sits inside the frame */} - - - - - - {/* HANDLE - U-Greep for Taats */} - {doorType === "taats" && handle === "u-greep" && ( - - - + {/* GLASS PANELS - Pattern-based decorative glass */} + {glassPattern === "standard" && ( + + + + )} - {/* HANDLE - Klink for Scharnier */} - {doorType === "scharnier" && handle === "klink" && ( - - - - - - - + + + + )} + + {glassPattern === "dt10-ushape" && dividerPositions.length > 0 && ( + <> + {/* Top section - Inverted U */} + + + - + + {/* Bottom section - Normal U */} + + + + + + )} + + {/* HANDLES - Professional 3D handle components */} + {handle === "beugelgreep" && ( + + )} + {handle === "hoekgreep" && ( + + )} + {handle === "maangreep" && ( + + )} + {handle === "ovaalgreep" && ( + + )} + {handle === "klink" && ( + + )} + {handle === "u-greep" && ( + )} {/* 3D DIMENSION LABELS */} diff --git a/components/configurator/handles-3d.tsx b/components/configurator/handles-3d.tsx new file mode 100644 index 0000000..2c5b567 --- /dev/null +++ b/components/configurator/handles-3d.tsx @@ -0,0 +1,305 @@ +"use client"; + +import { RoundedBox } from "@react-three/drei"; +import * as THREE from "three"; + +interface HandleProps { + finish: string; + doorWidth: number; + doorHeight: number; + railDepth: number; + stileWidth: number; +} + +// Steel material for handles +const HandleMaterial = ({ color }: { color: string }) => ( + +); + +/** + * Beugelgreep - Vertical bar handle with mounting blocks + * Classic industrial style, common on steel pivot doors + */ +export function Beugelgreep({ finish, doorHeight, railDepth }: HandleProps) { + const color = { + zwart: "#1a1a1a", + brons: "#8B6F47", + grijs: "#525252", + }[finish]; + + const handleLength = Math.min(doorHeight * 0.35, 0.8); // Max 80cm + const barDiameter = 0.025; // 25mm diameter bar + const mountBlockSize = [0.04, 0.06, 0.03]; // Mount block dimensions + + return ( + + {/* Main vertical bar */} + + + + + + {/* Top mounting block */} + + + + + {/* Bottom mounting block */} + + + + + {/* Mounting screws (detail) */} + {[ + [0, handleLength / 2 + mountBlockSize[1] / 2, 0.005], + [0, -handleLength / 2 - mountBlockSize[1] / 2, 0.005], + ].map((pos, i) => ( + + + + + ))} + + ); +} + +/** + * Hoekgreep - L-shaped corner handle + * Minimalist flush-mount design + */ +export function Hoekgreep({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { + const color = { + zwart: "#1a1a1a", + brons: "#8B6F47", + grijs: "#525252", + }[finish]; + + const horizontalLength = 0.15; // 15cm horizontal + const verticalLength = 0.12; // 12cm vertical + const barThickness = 0.02; // 20mm thick + const barWidth = 0.03; // 30mm wide + + return ( + + {/* Horizontal bar */} + + + + + {/* Vertical bar */} + + + + + {/* Corner radius (decorative) */} + + + + + + ); +} + +/** + * Maangreep - Crescent/moon shaped recessed handle + * Elegant curved design for flush doors + */ +export function Maangreep({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { + const color = { + zwart: "#1a1a1a", + brons: "#8B6F47", + grijs: "#525252", + }[finish]; + + const curveRadius = 0.08; // 8cm radius + const handleDepth = 0.025; // 25mm deep recess + + return ( + + {/* Main curved handle body */} + + + + + + {/* Left end cap */} + + + + + + {/* Right end cap */} + + + + + + {/* Recessed mounting plate */} + + + + + ); +} + +/** + * Ovaalgreep - Oval/elliptical pull handle + * Modern minimalist design + */ +export function Ovaalgreep({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { + const color = { + zwart: "#1a1a1a", + brons: "#8B6F47", + grijs: "#525252", + }[finish]; + + // Create oval shape using THREE.Shape + const shape = new THREE.Shape(); + const rx = 0.06; // 6cm horizontal radius + const ry = 0.03; // 3cm vertical radius + + // Draw ellipse + for (let i = 0; i <= 64; i++) { + const angle = (i / 64) * Math.PI * 2; + const x = Math.cos(angle) * rx; + const y = Math.sin(angle) * ry; + if (i === 0) { + shape.moveTo(x, y); + } else { + shape.lineTo(x, y); + } + } + + const extrudeSettings = { + depth: 0.02, + bevelEnabled: true, + bevelThickness: 0.003, + bevelSize: 0.003, + bevelSegments: 8, + }; + + return ( + + {/* Oval handle ring */} + + + + + + {/* Inner void (make it a ring, not solid) */} + + + + + + ); +} + +/** + * Klink - Traditional door handle with lever + * Classic hinged door handle + */ +export function Klink({ finish, doorWidth, railDepth, stileWidth }: HandleProps) { + const color = { + zwart: "#1a1a1a", + brons: "#8B6F47", + grijs: "#525252", + }[finish]; + + const leverLength = 0.12; // 12cm lever + const leverThickness = 0.015; // 15mm thick + + return ( + + {/* Mounting rosette (round plate) */} + + + + + + {/* Lever handle */} + + + + + {/* Lever end (ergonomic grip) */} + + + + + + {/* Lock cylinder (detail) */} + + + + + + ); +} + +/** + * U-Greep - U-shaped bar handle (current simple version) + * Straight vertical bar for pivot doors + */ +export function UGreep({ finish, railDepth }: HandleProps) { + const color = { + zwart: "#1a1a1a", + brons: "#8B6F47", + grijs: "#525252", + }[finish]; + + return ( + + + + ); +} diff --git a/components/configurator/scene.tsx b/components/configurator/scene.tsx index c89db75..c25e095 100644 --- a/components/configurator/scene.tsx +++ b/components/configurator/scene.tsx @@ -3,88 +3,180 @@ import { Canvas } from "@react-three/fiber"; import { OrbitControls, PerspectiveCamera, Environment, ContactShadows } from "@react-three/drei"; import { Door3DEnhanced } from "./door-3d-enhanced"; +import { useConfiguratorStore } from "@/lib/store"; import * as THREE from "three"; -function Room() { +function LivingRoom({ doorWidth, doorHeight }: { doorWidth: number; doorHeight: number }) { const wallThickness = 0.15; - const doorWidth = 1.3; - const doorHeight = 2.5; + const roomWidth = 8; + const roomDepth = 6; + const roomHeight = 3; + + // Calculate dynamic doorway dimensions + const doorwayWidth = doorWidth + wallThickness * 2 + 0.1; // Extra margin + const doorwayHeight = doorHeight + wallThickness + 0.1; return ( - {/* Floor - Clean shadow catcher */} - - - + {/* Floor - Modern light wood */} + + + - {/* Proper Doorway with Reveal */} + {/* Back Wall with Dynamic Doorway */} - {/* Left Pillar */} - - - + {/* Left Pillar - Dynamic height */} + + + - {/* Right Pillar */} - - - + {/* Right Pillar - Dynamic height */} + + + - {/* Top Lintel */} - - - + {/* Doorway Frame - Left */} + + + + + + {/* Doorway Frame - Right */} + + + + + + {/* Doorway Frame - Top (Lintel) */} + + + {/* Main Wall - Left Section */} - - - + + + {/* Main Wall - Right Section */} - - - + + + - {/* Main Wall - Top Section */} - - - + {/* Main Wall - Top Section (above doorway) */} + + + - {/* Side Walls for depth */} - - - + {/* Left Wall */} + + + - - - + {/* Right Wall */} + + + + + + {/* Ceiling */} + + + + + + {/* Decorative Elements - Baseboard Left */} + + + + + + {/* Decorative Elements - Baseboard Right */} + + + ); } +function DoorWithRoom() { + const { doorLeafWidth, height } = useConfiguratorStore(); + + // Convert mm to meters for 3D scene + const doorWidth = doorLeafWidth / 1000; + const doorHeight = height / 1000; + + return ( + <> + + + + ); +} + function Lighting() { return ( <> - {/* Strong ambient for flat, technical drawing look */} - + {/* Ambient for overall illumination */} + - {/* Front key light - straight on */} + {/* Main directional light (sunlight from window) */} - {/* Subtle side light for depth */} - - + {/* Fill light from opposite side */} + + + {/* Subtle top light */} + ); } @@ -110,24 +204,24 @@ export function Scene3D() { gl={{ antialias: true, toneMapping: THREE.ACESFilmicToneMapping, - toneMappingExposure: 1.3, + toneMappingExposure: 1.2, outputColorSpace: THREE.SRGBColorSpace, }} style={{ background: "#fafafa" }} > - {/* Camera - More frontal view for technical drawing aesthetic */} - + {/* Camera - Zoomed out for room context */} + - {/* Camera Controls - Very limited for flat view */} + {/* Camera Controls - More freedom for room viewing */} {/* City/Apartment Environment for realistic steel reflections */} - + {/* High-Resolution Contact Shadows for grounding */} - {/* The Room */} - - {/* The Door - Enhanced with textures and dimensions */} - + ); } diff --git a/components/configurator/texture-loader.tsx b/components/configurator/texture-loader.tsx new file mode 100644 index 0000000..623860d --- /dev/null +++ b/components/configurator/texture-loader.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useTexture } from "@react-three/drei"; +import { useEffect, useState } from "react"; +import * as THREE from "three"; + +/** + * Preload textures to prevent loading freezes + * Uses Suspense boundary for progressive loading + */ +export function useMetalTexture(finish: string) { + const [textureUrl, setTextureUrl] = useState(null); + + useEffect(() => { + const mapping: Record = { + zwart: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-zwart.jpg", + brons: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-brons.jpg", + grijs: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-antraciet.jpg", + }; + + setTextureUrl(mapping[finish] || mapping.zwart); + }, [finish]); + + try { + if (!textureUrl) return null; + + // Load texture with useTexture + const texture = useTexture(textureUrl); + + // Configure texture for optimal rendering + if (texture) { + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + texture.repeat.set(2, 2); + texture.colorSpace = THREE.SRGBColorSpace; + } + + return texture; + } catch (error) { + // Fallback: return null if texture fails to load + console.warn("Texture loading failed, using solid color fallback", error); + return null; + } +} + +/** + * Enhanced Steel Material with texture support + */ +export function SteelMaterialWithTexture({ + color, + finish, +}: { + color: string; + finish: string; +}) { + const texture = useMetalTexture(finish); + + return ( + + ); +} + +/** + * Fallback Steel Material (solid color) + */ +export function SteelMaterialSolid({ color }: { color: string }) { + return ( + + ); +} diff --git a/components/offerte/step-options.tsx b/components/offerte/step-options.tsx index afc37b2..109842c 100644 --- a/components/offerte/step-options.tsx +++ b/components/offerte/step-options.tsx @@ -1,6 +1,7 @@ "use client"; import { useConfiguratorStore, type Finish, type Handle } from "@/lib/store"; +import { glassPatternOptions, type GlassPattern } from "@/lib/glass-patterns"; import { Check } from "lucide-react"; const finishOptions: Array<{ @@ -22,17 +23,46 @@ const handleOptions: Array<{ label: string; description: string; }> = [ + { + value: "beugelgreep", + label: "Beugelgreep", + description: "Verticale staaf met montageblokken", + }, + { + value: "hoekgreep", + label: "Hoekgreep", + description: "L-vormige minimalistisch design", + }, + { + value: "maangreep", + label: "Maangreep", + description: "Gebogen half-maanvormige greep", + }, + { + value: "ovaalgreep", + label: "Ovaalgreep", + description: "Moderne ovale trekgreep", + }, + { + value: "klink", + label: "Deurklink", + description: "Klassieke deurklink met hendel", + }, { value: "u-greep", label: "U-Greep", - description: "Verticale greep voor taatsdeur", + description: "Eenvoudige rechte staaf", + }, + { + value: "geen", + label: "Geen greep", + description: "Voor vaste panelen", }, - { value: "klink", label: "Klink", description: "Klassieke deurklink" }, - { value: "geen", label: "Geen greep", description: "Voor vaste panelen" }, ]; export function StepOptions() { - const { finish, handle, setFinish, setHandle } = useConfiguratorStore(); + const { finish, handle, glassPattern, setFinish, setHandle, setGlassPattern } = + useConfiguratorStore(); return (
@@ -95,6 +125,51 @@ export function StepOptions() {
+ {/* Glass Pattern Selection */} +
+

Glaspatroon

+

+ Kies het decoratieve patroon voor de glaspanelen. +

+ +
+ {glassPatternOptions.map((option) => { + const selected = glassPattern === option.value; + + return ( + + ); + })} +
+
+ {/* Handle Selection */}

Greep

diff --git a/lib/glass-patterns.ts b/lib/glass-patterns.ts new file mode 100644 index 0000000..21cba4b --- /dev/null +++ b/lib/glass-patterns.ts @@ -0,0 +1,198 @@ +/** + * Glass Pattern Generators + * Creates custom THREE.Shape objects for decorative glass panels + * Based on reference drawings: dt9 (rounded corners), dt10 (U-shapes) + */ + +import * as THREE from "three"; + +export type GlassPattern = "standard" | "dt9-rounded" | "dt10-ushape"; + +/** + * Standard rectangular glass panel + */ +export function createStandardGlass(width: number, height: number): THREE.Shape { + const shape = new THREE.Shape(); + const hw = width / 2; + const hh = height / 2; + + shape.moveTo(-hw, -hh); + shape.lineTo(hw, -hh); + shape.lineTo(hw, hh); + shape.lineTo(-hw, hh); + shape.lineTo(-hw, -hh); + + return shape; +} + +/** + * DT9: Rounded corners glass panel + * Creates elegant rounded corners on glass sections + */ +export function createRoundedCornerGlass( + width: number, + height: number, + cornerRadius: number = 0.08 +): THREE.Shape { + const shape = new THREE.Shape(); + const hw = width / 2; + const hh = height / 2; + const r = Math.min(cornerRadius, width / 4, height / 4); + + // Start from bottom-left corner (after radius) + shape.moveTo(-hw + r, -hh); + + // Bottom edge + shape.lineTo(hw - r, -hh); + + // Bottom-right rounded corner + shape.quadraticCurveTo(hw, -hh, hw, -hh + r); + + // Right edge + shape.lineTo(hw, hh - r); + + // Top-right rounded corner + shape.quadraticCurveTo(hw, hh, hw - r, hh); + + // Top edge + shape.lineTo(-hw + r, hh); + + // Top-left rounded corner + shape.quadraticCurveTo(-hw, hh, -hw, hh - r); + + // Left edge + shape.lineTo(-hw, -hh + r); + + // Bottom-left rounded corner + shape.quadraticCurveTo(-hw, -hh, -hw + r, -hh); + + return shape; +} + +/** + * DT10: U-shaped glass panel (top section - inverted U) + * Creates an upside-down U shape for the upper glass section + */ +export function createInvertedUGlass(width: number, height: number): THREE.Shape { + const shape = new THREE.Shape(); + const hw = width / 2; + const hh = height / 2; + const uRadius = hw * 0.85; // U curve radius + + // Start at top-left + shape.moveTo(-hw, hh); + + // Top edge + shape.lineTo(hw, hh); + + // Right edge down + shape.lineTo(hw, -hh + uRadius); + + // Bottom U curve (inverted) + shape.absarc(0, -hh + uRadius, uRadius, 0, Math.PI, false); + + // Left edge up + shape.lineTo(-hw, hh); + + return shape; +} + +/** + * DT10: U-shaped glass panel (bottom section - normal U) + * Creates a normal U shape for the lower glass section + */ +export function createNormalUGlass(width: number, height: number): THREE.Shape { + const shape = new THREE.Shape(); + const hw = width / 2; + const hh = height / 2; + const uRadius = hw * 0.85; // U curve radius + + // Start at bottom-left + shape.moveTo(-hw, -hh); + + // Bottom edge + shape.lineTo(hw, -hh); + + // Right edge up + shape.lineTo(hw, hh - uRadius); + + // Top U curve + shape.absarc(0, hh - uRadius, uRadius, 0, Math.PI, true); + + // Left edge down + shape.lineTo(-hw, -hh); + + return shape; +} + +/** + * DT9 Asymmetric: Create multiple panels with different rounded corners + * For complex DT9 layouts with side panels + */ +export function createDT9Panels( + mainWidth: number, + mainHeight: number, + sideWidth: number, + position: "top-right" | "bottom-left" | "top-left" | "bottom-right" +): { + mainPanel: THREE.Shape; + roundedPanel: THREE.Shape; + dividerHeight: number; +} { + const cornerRadius = 0.12; + + // Main large panel with one rounded corner + const mainPanel = new THREE.Shape(); + const mw = mainWidth / 2; + const mh = mainHeight / 2; + + if (position === "top-right") { + // Main panel with rounded top-right corner + mainPanel.moveTo(-mw, -mh); + mainPanel.lineTo(mw - cornerRadius, -mh); + mainPanel.quadraticCurveTo(mw, -mh, mw, -mh + cornerRadius); + mainPanel.lineTo(mw, mh - cornerRadius); + mainPanel.quadraticCurveTo(mw, mh, mw - cornerRadius, mh); + mainPanel.lineTo(-mw, mh); + mainPanel.lineTo(-mw, -mh); + } else if (position === "bottom-left") { + // Main panel with rounded bottom-left corner + mainPanel.moveTo(-mw + cornerRadius, -mh); + mainPanel.lineTo(mw, -mh); + mainPanel.lineTo(mw, mh); + mainPanel.lineTo(-mw + cornerRadius, mh); + mainPanel.quadraticCurveTo(-mw, mh, -mw, mh - cornerRadius); + mainPanel.lineTo(-mw, -mh + cornerRadius); + mainPanel.quadraticCurveTo(-mw, -mh, -mw + cornerRadius, -mh); + } + + // Small rounded panel + const roundedPanel = createRoundedCornerGlass(sideWidth, mainHeight / 3, cornerRadius * 0.6); + + return { + mainPanel, + roundedPanel, + dividerHeight: mainHeight / 3, + }; +} + +/** + * Pattern metadata for UI selection + */ +export const glassPatternOptions = [ + { + value: "standard" as GlassPattern, + label: "Standaard", + description: "Rechthoekige glaspanelen", + }, + { + value: "dt9-rounded" as GlassPattern, + label: "DT9 - Afgeronde hoeken", + description: "Elegante ronde hoeken", + }, + { + value: "dt10-ushape" as GlassPattern, + label: "DT10 - U-vormen", + description: "Decoratieve U-vormige panelen", + }, +] as const; diff --git a/lib/store.ts b/lib/store.ts index 471e091..2eb603c 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -8,11 +8,12 @@ import { type DoorConfig, type SidePanel, } from './calculations'; +import type { GlassPattern } from './glass-patterns'; export type DoorType = 'taats' | 'scharnier' | 'paneel'; export type GridType = '3-vlak' | '4-vlak' | 'geen'; export type Finish = 'zwart' | 'brons' | 'grijs'; -export type Handle = 'u-greep' | 'klink' | 'geen'; +export type Handle = 'beugelgreep' | 'hoekgreep' | 'maangreep' | 'ovaalgreep' | 'klink' | 'u-greep' | 'geen'; interface ConfiguratorState { // Door configuration @@ -24,6 +25,7 @@ interface ConfiguratorState { gridType: GridType; finish: Finish; handle: Handle; + glassPattern: GlassPattern; // Dimensions (in mm) width: number; @@ -43,6 +45,7 @@ interface ConfiguratorState { setGridType: (type: GridType) => void; setFinish: (finish: Finish) => void; setHandle: (handle: Handle) => void; + setGlassPattern: (pattern: GlassPattern) => void; setWidth: (width: number) => void; setHeight: (height: number) => void; setDimensions: (width: number, height: number) => void; @@ -68,7 +71,8 @@ export const useConfiguratorStore = create((set, get) => ({ sidePanel: 'geen', gridType: '3-vlak', finish: 'zwart', - handle: 'u-greep', + handle: 'beugelgreep', + glassPattern: 'standard', width: 1000, height: 2400, @@ -101,6 +105,8 @@ export const useConfiguratorStore = create((set, get) => ({ setHandle: (handle) => set({ handle }), + setGlassPattern: (glassPattern) => set({ glassPattern }), + setWidth: (width) => { const { doorConfig, sidePanel } = get(); const minWidth = calculateHoleMinWidth(doorConfig, sidePanel);