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;
|
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;
|
document.getElementById('house-name').textContent = house.name;
|
||||||
|
await renderer.loadCatalog('../data/furniture-catalog.json');
|
||||||
|
await renderer.loadDesign('../designs/sample-house-design.json');
|
||||||
buildFloorButtons();
|
buildFloorButtons();
|
||||||
buildRoomList();
|
buildRoomList();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ export class HouseRenderer {
|
|||||||
constructor(container) {
|
constructor(container) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.houseData = null;
|
this.houseData = null;
|
||||||
|
this.catalogData = null;
|
||||||
|
this.designData = null;
|
||||||
this.currentFloor = 0;
|
this.currentFloor = 0;
|
||||||
this.roomMeshes = new Map();
|
this.roomMeshes = new Map();
|
||||||
this.roomLabels = new Map();
|
this.roomLabels = new Map();
|
||||||
|
this.furnitureMeshes = new Map();
|
||||||
|
|
||||||
this.scene = new THREE.Scene();
|
this.scene = new THREE.Scene();
|
||||||
this.scene.background = new THREE.Color(0xf0f0f0);
|
this.scene.background = new THREE.Color(0xf0f0f0);
|
||||||
@@ -87,6 +90,23 @@ export class HouseRenderer {
|
|||||||
return this.houseData;
|
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) {
|
showFloor(index) {
|
||||||
this.currentFloor = index;
|
this.currentFloor = index;
|
||||||
// Clear existing room meshes
|
// Clear existing room meshes
|
||||||
@@ -95,6 +115,11 @@ export class HouseRenderer {
|
|||||||
}
|
}
|
||||||
this.roomMeshes.clear();
|
this.roomMeshes.clear();
|
||||||
this.roomLabels.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];
|
const floor = this.houseData.floors[index];
|
||||||
if (!floor) return;
|
if (!floor) return;
|
||||||
@@ -102,6 +127,8 @@ export class HouseRenderer {
|
|||||||
for (const room of floor.rooms) {
|
for (const room of floor.rooms) {
|
||||||
this._renderRoom(room, floor.ceilingHeight);
|
this._renderRoom(room, floor.ceilingHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._placeFurnitureForFloor();
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderRoom(room, ceilingHeight) {
|
_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() {
|
_onResize() {
|
||||||
const w = this.container.clientWidth;
|
const w = this.container.clientWidth;
|
||||||
const h = this.container.clientHeight;
|
const h = this.container.clientHeight;
|
||||||
|
|||||||
Reference in New Issue
Block a user