Add furniture placement to 3D renderer

- loadCatalog() and loadDesign() methods on HouseRenderer
- Builds Three.js meshes from catalog part definitions (box/cylinder)
- Places furniture in room-local coordinates with rotation
- Furniture clears and re-renders on floor switch
- index.html loads catalog and design after house data
This commit is contained in:
m
2026-02-07 11:42:58 +01:00
parent 03c6eeb534
commit 4d76d9525f
2 changed files with 100 additions and 1 deletions

View File

@@ -94,8 +94,10 @@
let selectedRoom = null;
renderer.loadHouse('../data/sample-house.json').then(house => {
renderer.loadHouse('../data/sample-house.json').then(async (house) => {
document.getElementById('house-name').textContent = house.name;
await renderer.loadCatalog('../data/furniture-catalog.json');
await renderer.loadDesign('../designs/sample-house-design.json');
buildFloorButtons();
buildRoomList();
});

View File

@@ -22,9 +22,12 @@ export class HouseRenderer {
constructor(container) {
this.container = container;
this.houseData = null;
this.catalogData = null;
this.designData = null;
this.currentFloor = 0;
this.roomMeshes = new Map();
this.roomLabels = new Map();
this.furnitureMeshes = new Map();
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0xf0f0f0);
@@ -87,6 +90,23 @@ export class HouseRenderer {
return this.houseData;
}
async loadCatalog(url) {
const res = await fetch(url);
this.catalogData = await res.json();
this._catalogIndex = new Map();
for (const item of this.catalogData.items) {
this._catalogIndex.set(item.id, item);
}
return this.catalogData;
}
async loadDesign(url) {
const res = await fetch(url);
this.designData = await res.json();
this._placeFurnitureForFloor();
return this.designData;
}
showFloor(index) {
this.currentFloor = index;
// Clear existing room meshes
@@ -95,6 +115,11 @@ export class HouseRenderer {
}
this.roomMeshes.clear();
this.roomLabels.clear();
// Clear existing furniture
for (const group of this.furnitureMeshes.values()) {
this.scene.remove(group);
}
this.furnitureMeshes.clear();
const floor = this.houseData.floors[index];
if (!floor) return;
@@ -102,6 +127,8 @@ export class HouseRenderer {
for (const room of floor.rooms) {
this._renderRoom(room, floor.ceilingHeight);
}
this._placeFurnitureForFloor();
}
_renderRoom(room, ceilingHeight) {
@@ -376,6 +403,76 @@ export class HouseRenderer {
}
}
_placeFurnitureForFloor() {
if (!this.designData || !this.catalogData || !this.houseData) return;
const floor = this.houseData.floors[this.currentFloor];
if (!floor) return;
const floorRoomIds = new Set(floor.rooms.map(r => r.id));
for (const roomDesign of this.designData.rooms) {
if (!floorRoomIds.has(roomDesign.roomId)) continue;
const room = floor.rooms.find(r => r.id === roomDesign.roomId);
if (!room) continue;
for (let i = 0; i < roomDesign.furniture.length; i++) {
const placement = roomDesign.furniture[i];
const catalogItem = this._catalogIndex.get(placement.catalogId);
if (!catalogItem) continue;
const mesh = this._buildFurnitureMesh(catalogItem);
// Position in room-local coords, then offset by room position
const rx = room.position.x + placement.position.x;
const rz = room.position.y + placement.position.z;
mesh.position.set(rx, 0, rz);
mesh.rotation.y = -(placement.rotation * Math.PI) / 180;
const key = `${roomDesign.roomId}-${placement.instanceId || placement.catalogId}-${i}`;
mesh.userData = {
isFurniture: true,
catalogId: placement.catalogId,
roomId: roomDesign.roomId,
itemName: catalogItem.name
};
this.scene.add(mesh);
this.furnitureMeshes.set(key, mesh);
}
}
}
_buildFurnitureMesh(catalogItem) {
const group = new THREE.Group();
const meshDef = catalogItem.mesh;
if (!meshDef || meshDef.type !== 'group' || !meshDef.parts) return group;
for (const part of meshDef.parts) {
let geo;
if (part.geometry === 'box') {
geo = new THREE.BoxGeometry(part.size[0], part.size[1], part.size[2]);
} else if (part.geometry === 'cylinder') {
geo = new THREE.CylinderGeometry(part.radius, part.radius, part.height, 16);
} else {
continue;
}
const mat = new THREE.MeshStandardMaterial({
color: new THREE.Color(part.color),
roughness: 0.7
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(part.position[0], part.position[1], part.position[2]);
mesh.castShadow = true;
mesh.receiveShadow = true;
group.add(mesh);
}
return group;
}
_onResize() {
const w = this.container.clientWidth;
const h = this.container.clientHeight;