Add InteractionManager with mode system, selection outline, keyboard shortcuts

- Mode system: view | select | move | rotate | place
- Click furniture to select with cyan wireframe outline
- Keyboard: R/Shift+R rotate, Delete remove, Escape deselect,
  Ctrl+Z undo, Ctrl+Shift+Z/Ctrl+Y redo
- Listens to DesignState changes to sync scene on undo/redo
- Wired into index.html with DesignState integration
- Added furnitureIndex to mesh userData for state lookups
This commit is contained in:
m
2026-02-07 12:22:06 +01:00
parent 36bc0aedd7
commit d0d9deb03a
3 changed files with 358 additions and 16 deletions

View File

@@ -76,7 +76,7 @@
<div id="room-list"></div>
</div>
<div id="info">Click a room to select it. Scroll to zoom, drag to orbit.</div>
<div id="info">Click a room to select it. Click furniture to edit. Scroll to zoom, drag to orbit.</div>
<script type="importmap">
{
@@ -88,16 +88,35 @@
</script>
<script type="module">
import { HouseRenderer } from './renderer.js';
import { DesignState } from './state.js';
import { InteractionManager } from './interaction.js';
const viewer = document.getElementById('viewer');
const renderer = new HouseRenderer(viewer);
const houseRenderer = new HouseRenderer(viewer);
let selectedRoom = null;
let designState = null;
let interaction = null;
renderer.loadHouse('../data/sample-house.json').then(async (house) => {
houseRenderer.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');
await houseRenderer.loadCatalog('../data/furniture-catalog.json');
const design = await houseRenderer.loadDesign('../designs/sample-house-design.json');
// Initialize state and interaction manager
designState = new DesignState(design);
interaction = new InteractionManager(houseRenderer, designState);
interaction.onChange((type, detail) => {
if (type === 'select') {
document.getElementById('info').textContent =
`Selected: ${detail.itemName} — R to rotate, Delete to remove, Escape to deselect`;
} else if (type === 'deselect') {
document.getElementById('info').textContent =
'Click a room to select it. Click furniture to edit. Scroll to zoom, drag to orbit.';
}
});
buildFloorButtons();
buildRoomList();
}).catch(err => {
@@ -108,12 +127,12 @@
function buildFloorButtons() {
const container = document.getElementById('floor-buttons');
container.innerHTML = '';
for (const floor of renderer.getFloors()) {
for (const floor of houseRenderer.getFloors()) {
const btn = document.createElement('button');
btn.className = 'floor-btn' + (floor.index === renderer.currentFloor ? ' active' : '');
btn.className = 'floor-btn' + (floor.index === houseRenderer.currentFloor ? ' active' : '');
btn.textContent = floor.name;
btn.addEventListener('click', () => {
renderer.showFloor(floor.index);
houseRenderer.showFloor(floor.index);
document.querySelectorAll('.floor-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
buildRoomList();
@@ -126,7 +145,7 @@
function buildRoomList() {
const container = document.getElementById('room-list');
container.innerHTML = '';
for (const room of renderer.getRooms()) {
for (const room of houseRenderer.getRooms()) {
const item = document.createElement('div');
item.className = 'room-item';
item.dataset.roomId = room.id;
@@ -138,11 +157,11 @@
function selectRoom(roomId) {
selectedRoom = roomId;
renderer.focusRoom(roomId);
houseRenderer.focusRoom(roomId);
document.querySelectorAll('.room-item').forEach(el => {
el.classList.toggle('active', el.dataset.roomId === roomId);
});
const room = renderer.getRooms().find(r => r.id === roomId);
const room = houseRenderer.getRooms().find(r => r.id === roomId);
if (room) {
document.getElementById('info').textContent = `${room.name} (${room.nameEN}) — ${room.area}`;
}
@@ -151,11 +170,6 @@
viewer.addEventListener('roomclick', (e) => {
selectRoom(e.detail.roomId);
});
viewer.addEventListener('furnitureclick', (e) => {
const d = e.detail;
document.getElementById('info').textContent = `${d.itemName} — in ${renderer.getRooms().find(r => r.id === d.roomId)?.name || d.roomId}`;
});
</script>
</body>
</html>