"use client";
import { Suspense } from "react";
import { RoundedBox, useTexture } from "@react-three/drei";
import * as THREE from "three";
// ============================================
// PHYSICAL CONSTANTS (mm converted to meters)
// ============================================
const PROFILE_DEPTH_M = 0.04; // 40mm profile depth
const DOOR_FACE_Z = PROFILE_DEPTH_M / 2; // 20mm - front face of door
const MOUNT_RADIUS = 0.006; // 6mm radius standoff cylinders
const MOUNT_LENGTH = 0.04; // 40mm standoff length
const MOUNT_CENTER_Z = DOOR_FACE_Z + MOUNT_LENGTH / 2; // Center of mount
const GRIP_CENTER_Z = DOOR_FACE_Z + MOUNT_LENGTH; // Front face of mount = grip center
const GRIP_RADIUS = 0.01; // 10mm radius for round grips
const GRIP_BAR_SIZE = 0.02; // 20mm for square grip cross-section
export interface HandleProps {
finish: string;
doorWidth: number;
doorHeight: number;
railDepth: number;
stileWidth: number;
}
// ============================================
// MATERIALS
// ============================================
/**
* Powder-coated steel material matching door frame finish.
* Loaded with texture for visual continuity with the frame.
*/
function HandleMaterialTextured({ color, finish }: { color: string; finish: string }) {
try {
const texturePath = {
zwart: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-zwart.jpg",
brons: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-brons.jpg",
grijs: "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-antraciet.jpg",
}[finish] || "/textures/aluwdoors/aluwdoors-configurator-metaalkleur-zwart.jpg";
const texture = useTexture(texturePath);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(0.2, 1);
texture.colorSpace = THREE.SRGBColorSpace;
return (
);
} catch {
return ;
}
}
function HandleMaterialFallback({ color }: { color: string }) {
return (
);
}
/** Wrap textured material in Suspense */
function PowderCoatMaterial({ color, finish }: { color: string; finish: string }) {
return (
}>
);
}
function getColor(finish: string): string {
return { zwart: "#1a1a1a", brons: "#8B6F47", grijs: "#525252" }[finish] || "#1a1a1a";
}
// ============================================
// SHARED MOUNT COMPONENT
// ============================================
/**
* A single cylindrical standoff (pootje) connecting handle to door face.
* Rotated 90° on X to point outward from the door surface.
*/
function MountStandoff({
position,
color,
finish,
}: {
position: [number, number, number];
color: string;
finish: string;
}) {
return (
);
}
// ============================================
// HANDLE COMPONENTS
// ============================================
/**
* U-Greep: Proper U-shaped bar handle with two standoff mounts.
* The grip sits 40mm off the door face, connected by two cylindrical pootjes.
*/
export function UGreep({ finish, doorHeight }: HandleProps) {
const color = getColor(finish);
const gripLength = Math.min(doorHeight * 0.25, 0.6); // Max 60cm, proportional
const mountSpacing = gripLength - GRIP_BAR_SIZE; // Distance between mount centers
return (
{/* Top mount standoff */}
{/* Bottom mount standoff */}
{/* Vertical grip bar */}
);
}
/**
* Beugelgreep: Vertical bar handle (round) with mounting blocks.
* Two rectangular mounting blocks press against the door face,
* with a round bar connecting them.
*/
export function Beugelgreep({ finish, doorHeight }: HandleProps) {
const color = getColor(finish);
const gripLength = Math.min(doorHeight * 0.35, 0.8); // Max 80cm
const barDiameter = 0.025; // 25mm
const mountBlockSize: [number, number, number] = [0.04, 0.05, MOUNT_LENGTH];
const mountSpacing = gripLength * 0.85;
return (
{/* Top mounting block (sits on door face, extends outward) */}
{/* Bottom mounting block */}
{/* Main vertical round bar */}
{/* Mounting screw details */}
{[mountSpacing / 2, -mountSpacing / 2].map((y, i) => (
))}
);
}
/**
* Hoekgreep: L-shaped corner handle with standoff mounts.
* Horizontal bar + vertical bar meeting at a rounded corner.
*/
export function Hoekgreep({ finish, doorWidth, stileWidth }: HandleProps) {
const color = getColor(finish);
const horizontalLength = 0.15;
const verticalLength = 0.12;
const barThickness = 0.02;
const barWidth = 0.03;
// Position near right stile
const xPos = doorWidth / 2 - stileWidth - 0.12;
return (
{/* Top mount standoff */}
{/* Bottom mount standoff */}
{/* Horizontal bar */}
{/* Vertical bar */}
{/* Corner radius */}
);
}
/**
* Maangreep: Crescent/moon shaped handle with standoff mounts.
* Curved torus section mounted on two pootjes.
*/
export function Maangreep({ finish, doorWidth, stileWidth }: HandleProps) {
const color = getColor(finish);
const curveRadius = 0.08;
const xPos = doorWidth / 2 - stileWidth - 0.12;
return (
{/* Left mount standoff */}
{/* Right mount standoff */}
{/* Main curved handle body */}
{/* Left end cap */}
{/* Right end cap */}
);
}
/**
* Ovaalgreep: Oval/elliptical pull handle with standoff mounts.
* Extruded ellipse shape mounted on two pootjes.
*/
export function Ovaalgreep({ finish, doorWidth, stileWidth }: HandleProps) {
const color = getColor(finish);
const xPos = doorWidth / 2 - stileWidth - 0.12;
const shape = new THREE.Shape();
const rx = 0.06;
const ry = 0.03;
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);
}
return (
{/* Top mount standoff */}
{/* Bottom mount standoff */}
{/* Oval handle */}
);
}
/**
* Klink: Traditional lever handle with rosette, standoff-mounted.
* Rosette plate against door, lever extending horizontally.
*/
export function Klink({ finish, doorWidth, stileWidth }: HandleProps) {
const color = getColor(finish);
const leverLength = 0.12;
const xPos = doorWidth / 2 - stileWidth - 0.1;
return (
{/* Mounting rosette (flat against door face) */}
{/* Square spindle standoff (connects rosette to lever) */}
{/* Lever handle */}
{/* Lever end grip */}
{/* Lock cylinder below */}
);
}