feat: Manufacturing-grade door geometry + photorealistic materials

🏗️ Architecture (@Logic-Architect):
- Created lib/door-models.ts with exact manufacturing specs
- PROFILE_WIDTH = 40mm, PROFILE_DEPTH = 40mm (real steel tubes)
- GLASS_OFFSET = 18mm for proper centering
- Physical parts system (stiles, rails, dividers, glass)
- generateDoorAssembly() returns manufacturable parts list
- Validation for structural integrity limits

🎨 Visuals (@3D-Visual-Lead):
- Aluwdoors texture loading with vertical steel grain
- MeshStandardMaterial: roughness 0.6, metalness 0.7
- Photorealistic glass: transmission 0.98, IOR 1.5
- RoundedBox with 2mm radius for all profiles
- Suspense boundaries for progressive texture loading
- Studio environment preset + enhanced contact shadows

🔧 Technical:
- UseMemo for door assembly generation
- mmToMeters() conversion utility
- PhysicalPartComponent renderer
- Backward compatibility with glass patterns
- Fallback materials when textures fail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ubuntu
2026-02-10 19:15:51 +00:00
parent 32ed02c1f3
commit e192f19e5f
6 changed files with 1021 additions and 225 deletions

View File

@@ -1,8 +1,8 @@
"use client";
import { useRef, useMemo } from "react";
import { useRef, useMemo, Suspense } from "react";
import { useConfiguratorStore } from "@/lib/store";
import { RoundedBox } from "@react-three/drei";
import { RoundedBox, useTexture } from "@react-three/drei";
import * as THREE from "three";
import {
Beugelgreep,
@@ -18,44 +18,142 @@ import {
createInvertedUGlass,
createNormalUGlass,
} from "@/lib/glass-patterns";
import {
generateDoorAssembly,
mmToMeters,
getDividerPositions,
PROFILE_CORNER_RADIUS,
type PhysicalPart,
} from "@/lib/door-models";
// Steel material - fallback to solid color for now
const SteelMaterial = ({ color }: { color: string }) => (
<meshStandardMaterial
color={color}
roughness={0.7}
metalness={0.8}
envMapIntensity={1}
/>
);
// ============================================
// PHOTOREALISTIC MATERIALS
// ============================================
// Glass material - More opaque/white for technical drawing look
/**
* Steel Material with Aluwdoors Texture
* Vertical steel grain for industrial look
*/
function SteelMaterialTextured({ color, finish }: { color: string; finish: string }) {
try {
// Load texture based on finish
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);
// Configure texture for vertical steel grain
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(0.5, 3); // Vertical grain
texture.colorSpace = THREE.SRGBColorSpace;
return (
<meshStandardMaterial
map={texture}
color={color}
roughness={0.6}
metalness={0.7}
envMapIntensity={1.5}
/>
);
} catch (error) {
// Fallback to solid color if texture fails
return <SteelMaterialFallback color={color} />;
}
}
/**
* Fallback Steel Material (Solid Color)
* Used when textures fail to load or as initial state
*/
function SteelMaterialFallback({ color }: { color: string }) {
return (
<meshStandardMaterial
color={color}
roughness={0.6}
metalness={0.7}
envMapIntensity={1.5}
/>
);
}
/**
* Photorealistic Glass Material
* High transmission for realistic glass look
*/
const GlassMaterial = () => (
<meshPhysicalMaterial
color="#f8f9fa"
transparent
transmission={0.3}
roughness={0.1}
thickness={1}
transmission={0.98}
roughness={0.05}
thickness={0.5}
ior={1.5}
envMapIntensity={0.5}
opacity={0.95}
color="#eff6ff"
transparent
opacity={0.98}
envMapIntensity={0.8}
/>
);
// 3D Dimension Label Component - temporarily disabled
function DimensionLabel({
value,
position,
label,
// ============================================
// PHYSICAL PART RENDERER
// ============================================
/**
* Renders a single physical part with correct geometry
*/
function PhysicalPartComponent({
part,
frameColor,
finish,
}: {
value: number;
position: [number, number, number];
label: string;
part: PhysicalPart;
frameColor: string;
finish: string;
}) {
return null; // Temporarily disabled to fix loading
// Convert mm to meters
const x = mmToMeters(part.x);
const y = mmToMeters(part.y);
const z = mmToMeters(part.z);
const width = mmToMeters(part.width);
const height = mmToMeters(part.height);
const depth = mmToMeters(part.depth);
// Glass uses different material
if (part.isGlass) {
return (
<mesh position={[x, y, z]} castShadow receiveShadow>
<boxGeometry args={[width, height, depth]} />
<GlassMaterial />
</mesh>
);
}
// Steel profiles use RoundedBox for realistic edges
const cornerRadius = mmToMeters(PROFILE_CORNER_RADIUS);
return (
<RoundedBox
args={[width, height, depth]}
radius={cornerRadius}
smoothness={4}
position={[x, y, z]}
castShadow
receiveShadow
>
<Suspense fallback={<SteelMaterialFallback color={frameColor} />}>
<SteelMaterialTextured color={frameColor} finish={finish} />
</Suspense>
</RoundedBox>
);
}
// ============================================
// MAIN DOOR COMPONENT
// ============================================
export function Door3DEnhanced() {
const { doorType, gridType, finish, handle, glassPattern, doorLeafWidth, height } =
useConfiguratorStore();
@@ -66,180 +164,107 @@ export function Door3DEnhanced() {
zwart: "#1a1a1a",
brons: "#8B6F47",
grijs: "#525252",
}[finish];
}[finish] || "#1a1a1a";
// Convert mm to meters for 3D scene
const doorWidth = doorLeafWidth / 1000; // Convert mm to m
const doorHeight = height / 1000; // Convert mm to m
// Generate door assembly from manufacturing specs
const doorAssembly = useMemo(
() => generateDoorAssembly(doorType, gridType, doorLeafWidth, height),
[doorType, gridType, doorLeafWidth, height]
);
// Profile dimensions (in meters) - Thicker for visibility
const stileWidth = 0.06; // 60mm vertical profiles (more visible)
const stileDepth = 0.06; // 60mm depth
const railHeight = 0.04; // 40mm horizontal profiles (more visible)
const railDepth = 0.06; // 60mm depth
const glassThickness = 0.01; // 10mm glass
const profileRadius = 0.002; // 2mm rounded corners
// Convert dimensions to meters
const doorWidth = mmToMeters(doorLeafWidth);
const doorHeight = mmToMeters(height);
// Calculate positions for grid dividers
const getDividerPositions = () => {
if (gridType === "3-vlak") {
return [-doorHeight / 3, doorHeight / 3];
} else if (gridType === "4-vlak") {
return [-doorHeight / 2, 0, doorHeight / 2];
}
return [];
};
// Profile dimensions in meters (for handle positioning)
const stileWidth = mmToMeters(40);
const railDepth = mmToMeters(40);
const dividerPositions = getDividerPositions();
// Get divider positions for glass patterns (backward compatibility)
const dividerPositions = getDividerPositions(gridType, height);
return (
<group ref={doorRef} position={[0, doorHeight / 2, 0]}>
{/* LEFT STILE - Vertical profile */}
<RoundedBox
args={[stileWidth, doorHeight, stileDepth]}
radius={profileRadius}
smoothness={4}
position={[-doorWidth / 2 + stileWidth / 2, 0, 0]}
castShadow
>
<SteelMaterial color={frameColor} />
</RoundedBox>
{/* RIGHT STILE - Vertical profile */}
<RoundedBox
args={[stileWidth, doorHeight, stileDepth]}
radius={profileRadius}
smoothness={4}
position={[doorWidth / 2 - stileWidth / 2, 0, 0]}
castShadow
>
<SteelMaterial color={frameColor} />
</RoundedBox>
{/* TOP RAIL - Horizontal profile */}
<RoundedBox
args={[doorWidth - stileWidth * 2, railHeight, railDepth]}
radius={profileRadius}
smoothness={4}
position={[0, doorHeight / 2 - railHeight / 2, 0]}
castShadow
>
<SteelMaterial color={frameColor} />
</RoundedBox>
{/* BOTTOM RAIL - Horizontal profile */}
<RoundedBox
args={[doorWidth - stileWidth * 2, railHeight, railDepth]}
radius={profileRadius}
smoothness={4}
position={[0, -doorHeight / 2 + railHeight / 2, 0]}
castShadow
>
<SteelMaterial color={frameColor} />
</RoundedBox>
{/* INTERMEDIATE RAILS (Grid dividers) */}
{dividerPositions.map((yPos, index) => (
<RoundedBox
key={`rail-${index}`}
args={[doorWidth - stileWidth * 2, railHeight, railDepth]}
radius={profileRadius}
smoothness={4}
position={[0, yPos, 0]}
castShadow
>
<SteelMaterial color={frameColor} />
</RoundedBox>
{/* RENDER ALL PHYSICAL PARTS */}
{doorAssembly.parts.map((part, index) => (
<PhysicalPartComponent
key={`${part.type}-${index}`}
part={part}
frameColor={frameColor}
finish={finish}
/>
))}
{/* VERTICAL DIVIDER for Paneel */}
{doorType === "paneel" && (
<RoundedBox
args={[stileWidth, doorHeight - railHeight * 2, stileDepth]}
radius={profileRadius}
smoothness={4}
position={[0, 0, 0]}
castShadow
>
<SteelMaterial color={frameColor} />
</RoundedBox>
{/* GLASS PANELS WITH PATTERNS */}
{glassPattern !== "standard" && (
<group position={[0, 0, 0.005]}>
{glassPattern === "dt9-rounded" && (
<mesh castShadow receiveShadow>
<extrudeGeometry
args={[
createRoundedCornerGlass(
doorWidth - stileWidth * 2,
doorHeight - stileWidth * 2,
0.12
),
{ depth: 0.01, bevelEnabled: false },
]}
/>
<GlassMaterial />
</mesh>
)}
{glassPattern === "dt10-ushape" && dividerPositions.length > 0 && (
<>
{/* Top section - Inverted U */}
<mesh
position={[0, (doorHeight / 4 + dividerPositions[0]) / 2, 0]}
castShadow
receiveShadow
>
<extrudeGeometry
args={[
createInvertedUGlass(
doorWidth - stileWidth * 2,
Math.abs(doorHeight / 2 - stileWidth - dividerPositions[0])
),
{ depth: 0.01, bevelEnabled: false },
]}
/>
<GlassMaterial />
</mesh>
{/* Bottom section - Normal U */}
<mesh
position={[
0,
(-doorHeight / 4 + dividerPositions[dividerPositions.length - 1]) / 2,
0,
]}
castShadow
receiveShadow
>
<extrudeGeometry
args={[
createNormalUGlass(
doorWidth - stileWidth * 2,
Math.abs(
-doorHeight / 2 +
stileWidth -
dividerPositions[dividerPositions.length - 1]
)
),
{ depth: 0.01, bevelEnabled: false },
]}
/>
<GlassMaterial />
</mesh>
</>
)}
</group>
)}
{/* GLASS PANELS - Pattern-based decorative glass */}
{glassPattern === "standard" && (
<mesh position={[0, 0, 0]} castShadow receiveShadow>
<boxGeometry
args={[
doorWidth - stileWidth * 2,
doorHeight - railHeight * 2,
glassThickness,
]}
/>
<GlassMaterial />
</mesh>
)}
{glassPattern === "dt9-rounded" && (
<mesh position={[0, 0, 0]} castShadow receiveShadow>
<extrudeGeometry
args={[
createRoundedCornerGlass(
doorWidth - stileWidth * 2,
doorHeight - railHeight * 2,
0.12
),
{ depth: glassThickness, bevelEnabled: false },
]}
/>
<GlassMaterial />
</mesh>
)}
{glassPattern === "dt10-ushape" && dividerPositions.length > 0 && (
<>
{/* Top section - Inverted U */}
<mesh
position={[0, (doorHeight / 4 + dividerPositions[0]) / 2, 0]}
castShadow
receiveShadow
>
<extrudeGeometry
args={[
createInvertedUGlass(
doorWidth - stileWidth * 2,
Math.abs(doorHeight / 2 - railHeight - dividerPositions[0])
),
{ depth: glassThickness, bevelEnabled: false },
]}
/>
<GlassMaterial />
</mesh>
{/* Bottom section - Normal U */}
<mesh
position={[
0,
(-doorHeight / 4 + dividerPositions[dividerPositions.length - 1]) / 2,
0,
]}
castShadow
receiveShadow
>
<extrudeGeometry
args={[
createNormalUGlass(
doorWidth - stileWidth * 2,
Math.abs(-doorHeight / 2 + railHeight - dividerPositions[dividerPositions.length - 1])
),
{ depth: glassThickness, bevelEnabled: false },
]}
/>
<GlassMaterial />
</mesh>
</>
)}
{/* HANDLES - Professional 3D handle components */}
{/* PROFESSIONAL 3D HANDLES */}
{handle === "beugelgreep" && (
<Beugelgreep
finish={finish}
@@ -294,34 +319,6 @@ export function Door3DEnhanced() {
stileWidth={stileWidth}
/>
)}
{/* 3D DIMENSION LABELS */}
{/* Width dimension */}
<DimensionLabel
value={doorLeafWidth}
position={[0, -doorHeight / 2 - 0.15, 0.1]}
label="mm"
/>
{/* Height dimension */}
<DimensionLabel
value={height}
position={[doorWidth / 2 + 0.15, 0, 0.1]}
label="mm"
/>
{/* Dimension lines */}
{/* Horizontal line for width */}
<mesh position={[0, -doorHeight / 2 - 0.1, 0.05]}>
<boxGeometry args={[doorWidth, 0.002, 0.002]} />
<meshBasicMaterial color="#1a1a1a" />
</mesh>
{/* Vertical line for height */}
<mesh position={[doorWidth / 2 + 0.1, 0, 0.05]}>
<boxGeometry args={[0.002, doorHeight, 0.002]} />
<meshBasicMaterial color="#1a1a1a" />
</mesh>
</group>
);
}

View File

@@ -230,17 +230,17 @@ export function Scene3D() {
{/* Premium Studio Lighting */}
<Lighting />
{/* City/Apartment Environment for realistic steel reflections */}
<Environment preset="apartment" environmentIntensity={0.6} />
{/* Studio Environment for photorealistic steel reflections */}
<Environment preset="studio" environmentIntensity={1.0} />
{/* High-Resolution Contact Shadows for grounding */}
<ContactShadows
position={[0, 0.01, 0]}
opacity={0.4}
scale={12}
blur={2.5}
opacity={0.6}
scale={15}
blur={2}
far={2}
resolution={1024}
resolution={2048}
/>
{/* The Door - Enhanced with textures and dimensions */}