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