Three.js에서 outline, selection, overlay 만들기
선택 테두리와 resize handle은 content가 아니라 editor control입니다. Three.js에서도 content scene과 control overlay를 분리하는 편이 좋습니다.
content mesh를 직접 바꾸지 않는다
content scene: document node mesh
overlay scene: selection bounds, handles, guides
DOM overlay: text input, accessibility controls
도형의 stroke와 선택 outline은 의미가 다릅니다. 같은 mesh material을 수정해서 selection을 표현하면 문서 스타일과 editor UI가 섞입니다.
같은 camera를 공유한다
renderer.render(contentScene, camera);
renderer.clearDepth();
renderer.render(overlayScene, camera);
overlay scene은 같은 camera를 사용하므로 zoom/pan과 자연스럽게 동기화됩니다.
world bounds를 overlay geometry로 바꾼다
selection outline은 document style이 아니므로 content mesh의 material을 바꾸지 않습니다. 선택된 node의 world bounds를 읽고 overlay scene에 line/handle mesh를 따로 둡니다.
function syncSelectionOverlay(overlay, selectedBounds) {
const { x, y, width, height } = selectedBounds;
const points = [
new THREE.Vector3(x, -y, 0),
new THREE.Vector3(x + width, -y, 0),
new THREE.Vector3(x + width, -(y + height), 0),
new THREE.Vector3(x, -(y + height), 0),
new THREE.Vector3(x, -y, 0)
];
overlay.outline.geometry.setFromPoints(points);
syncHandle(overlay.handles.nw, x, y);
syncHandle(overlay.handles.ne, x + width, y);
syncHandle(overlay.handles.sw, x, y + height);
syncHandle(overlay.handles.se, x + width, y + height);
}
handle 크기를 화면 pixel 기준으로 고정하려면 zoom에 반비례해서 scale을 조정합니다.
function syncHandle(handle, worldX, worldY, cameraZoom, cssPixels = 8) {
handle.position.set(worldX, -worldY, 10);
const worldSize = cssPixels / cameraZoom;
handle.scale.set(worldSize, worldSize, 1);
}
이 방식이면 zoom을 해도 resize handle이 너무 커지거나 작아지지 않습니다.
오늘의 핵심
selection UI는 content renderer 위에 얹는 control layer입니다. Three.js에서도 이 경계를 유지해야 editor가 커질수록 안전합니다.