diff --git a/src/index.html b/src/index.html
index a2b9430..05d793c 100644
--- a/src/index.html
+++ b/src/index.html
@@ -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();
});
diff --git a/src/renderer.js b/src/renderer.js
index bbf6099..1cc451b 100644
--- a/src/renderer.js
+++ b/src/renderer.js
@@ -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;