This commit is contained in:
lejay
2026-02-13 00:21:53 +01:00
parent 81d2412835
commit 6923c319a1
85 changed files with 17836 additions and 0 deletions
+370
View File
@@ -0,0 +1,370 @@
import { useMemo } from 'react';
import * as THREE from 'three';
/**
* Room3D component - Renders a 3D room based on AI analysis
* Used to display the wardrobe in context of the user's actual room
*/
// Floor material based on type
function FloorMaterial({ material, color }) {
const texture = useMemo(() => {
// In a real implementation, you'd load actual textures
// For now, we use colors with some roughness variation
return null;
}, [material]);
return (
<meshStandardMaterial
color={color || '#c4a574'}
roughness={material === 'wood' ? 0.7 : material === 'tile' ? 0.3 : 0.8}
metalness={0.05}
/>
);
}
// Get material properties from wall material type
function getWallMaterialProps(material, texture) {
const props = { roughness: 0.8, metalness: 0.02 };
switch (material) {
case 'paint':
props.roughness = texture === 'glossy' ? 0.3 : texture === 'smooth' ? 0.7 : 0.8;
break;
case 'wallpaper':
props.roughness = 0.85;
break;
case 'brick':
props.roughness = 0.95;
props.metalness = 0.0;
break;
case 'wood_panel':
props.roughness = 0.65;
props.metalness = 0.05;
break;
case 'concrete':
props.roughness = 0.9;
props.metalness = 0.01;
break;
case 'plaster':
props.roughness = 0.85;
break;
case 'stone':
props.roughness = 0.92;
props.metalness = 0.0;
break;
case 'tile':
props.roughness = 0.3;
props.metalness = 0.05;
break;
default:
break;
}
return props;
}
// Wall with optional window/door cutouts
function Wall({ position, rotation, size, color, material, texture, windows = [], doors = [] }) {
const [width, height] = size;
const matProps = getWallMaterialProps(material, texture);
// Create wall geometry with cutouts (simplified - just overlays for now)
return (
<group position={position} rotation={rotation}>
{/* Main wall */}
<mesh receiveShadow>
<planeGeometry args={[width, height]} />
<meshStandardMaterial
color={color || '#f5f5f0'}
side={THREE.DoubleSide}
roughness={matProps.roughness}
metalness={matProps.metalness}
/>
</mesh>
{/* Windows */}
{windows.map((win, i) => {
const winWidth = width * win.width;
const winHeight = height * win.height;
const winX = (win.x - 0.5) * width;
const winY = (win.y - 0.5) * height;
return (
<group key={`window-${i}`} position={[winX, winY, 0.01]}>
{/* Window glass */}
<mesh>
<planeGeometry args={[winWidth, winHeight]} />
<meshStandardMaterial
color="#87CEEB"
transparent
opacity={0.3}
side={THREE.DoubleSide}
/>
</mesh>
{/* Window frame */}
<lineSegments>
<edgesGeometry args={[new THREE.PlaneGeometry(winWidth, winHeight)]} />
<lineBasicMaterial color="#555" linewidth={2} />
</lineSegments>
</group>
);
})}
{/* Doors */}
{doors.map((door, i) => {
const doorWidth = width * door.width;
const doorHeight = height * door.height;
const doorX = (door.x - 0.5) * width;
const doorY = -(height / 2) + (doorHeight / 2);
return (
<group key={`door-${i}`} position={[doorX, doorY, 0.01]}>
{/* Door opening (darker rectangle) */}
<mesh>
<planeGeometry args={[doorWidth, doorHeight]} />
<meshStandardMaterial color="#2a2a2a" side={THREE.DoubleSide} />
</mesh>
{/* Door frame */}
<lineSegments>
<edgesGeometry args={[new THREE.PlaneGeometry(doorWidth, doorHeight)]} />
<lineBasicMaterial color="#8b7355" linewidth={2} />
</lineSegments>
</group>
);
})}
</group>
);
}
export default function Room3D({ roomConfig, wardrobeConfig }) {
if (!roomConfig) return null;
const { width, length, height, walls, windows, doors, floor, ceiling, wardrobePlacement } = roomConfig;
// Calculate wall positions
const halfWidth = width / 2;
const halfLength = length / 2;
const halfHeight = height / 2;
// Get windows/doors for each wall
const backWindows = windows.filter(w => w.wall === 'back').map(w => ({
x: w.x, y: w.y, width: w.width, height: w.height
}));
const leftWindows = windows.filter(w => w.wall === 'left').map(w => ({
x: w.x, y: w.y, width: w.width, height: w.height
}));
const rightWindows = windows.filter(w => w.wall === 'right').map(w => ({
x: w.x, y: w.y, width: w.width, height: w.height
}));
const backDoors = doors.filter(d => d.wall === 'back').map(d => ({
x: d.x, width: d.width, height: d.height
}));
const leftDoors = doors.filter(d => d.wall === 'left').map(d => ({
x: d.x, width: d.width, height: d.height
}));
const rightDoors = doors.filter(d => d.wall === 'right').map(d => ({
x: d.x, width: d.width, height: d.height
}));
// Calculate wardrobe position based on placement
const getWardrobePosition = () => {
if (!wardrobeConfig || !wardrobePlacement) return [0, 0, 0];
const wardrobeWidth = (wardrobeConfig.widthMm || 2400) / 1000;
const wardrobeDepth = (wardrobeConfig.depthMm || 600) / 1000;
switch (wardrobePlacement.wall) {
case 'back':
return [
((wardrobePlacement.xPercent / 100) - 0.5) * width + wardrobeWidth / 2,
0,
-halfLength + wardrobeDepth / 2 + 0.05
];
case 'left':
return [
-halfWidth + wardrobeDepth / 2 + 0.05,
0,
((wardrobePlacement.xPercent / 100) - 0.5) * length + wardrobeWidth / 2
];
case 'right':
return [
halfWidth - wardrobeDepth / 2 - 0.05,
0,
((wardrobePlacement.xPercent / 100) - 0.5) * length + wardrobeWidth / 2
];
default:
return [0, 0, -halfLength + wardrobeDepth / 2 + 0.05];
}
};
const wardrobePosition = getWardrobePosition();
const wardrobeRotation = wardrobePlacement?.wall === 'left'
? [0, Math.PI / 2, 0]
: wardrobePlacement?.wall === 'right'
? [0, -Math.PI / 2, 0]
: [0, 0, 0];
return (
<group>
{/* Floor */}
<mesh
rotation={[-Math.PI / 2, 0, 0]}
position={[0, 0, 0]}
receiveShadow
>
<planeGeometry args={[width, length]} />
<FloorMaterial material={floor?.material} color={floor?.color} />
</mesh>
{/* Back wall */}
{walls?.back?.visible !== false && (
<Wall
position={[0, halfHeight, -halfLength]}
rotation={[0, 0, 0]}
size={[width, height]}
color={walls?.back?.color}
material={walls?.back?.material}
texture={walls?.back?.texture}
windows={backWindows}
doors={backDoors}
/>
)}
{/* Left wall */}
{walls?.left?.visible !== false && (
<Wall
position={[-halfWidth, halfHeight, 0]}
rotation={[0, Math.PI / 2, 0]}
size={[length, height]}
color={walls?.left?.color}
material={walls?.left?.material}
texture={walls?.left?.texture}
windows={leftWindows}
doors={leftDoors}
/>
)}
{/* Right wall */}
{walls?.right?.visible !== false && (
<Wall
position={[halfWidth, halfHeight, 0]}
rotation={[0, -Math.PI / 2, 0]}
size={[length, height]}
color={walls?.right?.color}
material={walls?.right?.material}
texture={walls?.right?.texture}
windows={rightWindows}
doors={rightDoors}
/>
)}
{/* Ceiling (plafond) */}
{ceiling?.visible !== false && (() => {
const ceilingColor = ceiling?.color || '#ffffff';
const ceilingType = ceiling?.type || 'flat';
const slopeAngle = ceiling?.slopeAngleDeg || 0;
const ceilingMatProps = getWallMaterialProps(ceiling?.material || 'paint', 'smooth');
if (ceilingType === 'sloped' && slopeAngle > 0) {
// Sloped ceiling - render as a tilted plane
const slopeRad = (slopeAngle * Math.PI) / 180;
const slopeDir = ceiling?.slopeDirection || 'front_to_back';
const slopeRotX = slopeDir === 'front_to_back' ? Math.PI / 2 + slopeRad * 0.5 : Math.PI / 2;
const slopeRotZ = slopeDir === 'left_to_right' ? slopeRad * 0.5 : 0;
const slopeW = slopeDir === 'left_to_right' ? width / Math.cos(slopeRad * 0.5) : width;
const slopeL = slopeDir === 'front_to_back' ? length / Math.cos(slopeRad * 0.5) : length;
return (
<mesh
rotation={[slopeRotX, 0, slopeRotZ]}
position={[0, height, 0]}
receiveShadow
>
<planeGeometry args={[slopeW, slopeL]} />
<meshStandardMaterial
color={ceilingColor}
side={THREE.DoubleSide}
roughness={ceilingMatProps.roughness}
metalness={ceilingMatProps.metalness}
/>
</mesh>
);
}
// Flat ceiling
return (
<mesh
rotation={[Math.PI / 2, 0, 0]}
position={[0, height, 0]}
receiveShadow
>
<planeGeometry args={[width, length]} />
<meshStandardMaterial
color={ceilingColor}
side={THREE.DoubleSide}
roughness={ceilingMatProps.roughness}
metalness={ceilingMatProps.metalness}
/>
</mesh>
);
})()}
{/* Wardrobe position indicator (for debugging/visualization) */}
{wardrobePlacement && (
<mesh position={[wardrobePosition[0], 0.01, wardrobePosition[2]]}>
<planeGeometry args={[
(wardrobeConfig?.widthMm || 2400) / 1000,
(wardrobeConfig?.depthMm || 600) / 1000
]} />
<meshBasicMaterial color="#0066cc" transparent opacity={0.3} />
</mesh>
)}
</group>
);
}
// Export position calculator for use by parent
export function calculateWardrobeTransform(roomConfig, wardrobeConfig) {
if (!roomConfig || !roomConfig.wardrobePlacement) {
return { position: [0, 0, 0], rotation: [0, 0, 0] };
}
const { width, length, wardrobePlacement } = roomConfig;
const halfWidth = width / 2;
const halfLength = length / 2;
const wardrobeWidth = (wardrobeConfig?.widthMm || 2400) / 1000;
const wardrobeDepth = (wardrobeConfig?.depthMm || 600) / 1000;
let position, rotation;
switch (wardrobePlacement.wall) {
case 'back':
position = [
((wardrobePlacement.xPercent / 100) - 0.5) * width + wardrobeWidth / 4,
0,
-halfLength + wardrobeDepth / 2 + 0.05
];
rotation = [0, 0, 0];
break;
case 'left':
position = [
-halfWidth + wardrobeDepth / 2 + 0.05,
0,
((wardrobePlacement.xPercent / 100) - 0.5) * length
];
rotation = [0, Math.PI / 2, 0];
break;
case 'right':
position = [
halfWidth - wardrobeDepth / 2 - 0.05,
0,
((wardrobePlacement.xPercent / 100) - 0.5) * length
];
rotation = [0, -Math.PI / 2, 0];
break;
default:
position = [0, 0, -halfLength + wardrobeDepth / 2 + 0.05];
rotation = [0, 0, 0];
}
return { position, rotation };
}