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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user