feat: [fire-shutter] 3D 뷰 프리셋 버튼 추가 (정면/평면/우측/좌측/배면/투시)

- 우상단에 뷰 전환 버튼 패널 배치
- easeOutCubic 애니메이션으로 부드러운 카메라 전환
- 현재 타겟/거리 유지하며 카메라 위치만 변경
This commit is contained in:
김보곤
2026-03-14 08:42:25 +09:00
parent 70f35e1e7b
commit fd9f1b1bf2

View File

@@ -1617,6 +1617,28 @@ function animate() {
isoBadge.onclick = () => { fs3dShowAll(); };
container.appendChild(isoBadge);
// ── 뷰 전환 패널 (정면/평면/우측면/좌측면/투시) ──
const viewPanel = document.createElement('div');
viewPanel.style.cssText = 'position:absolute;top:8px;right:8px;display:flex;gap:4px;z-index:10;';
const viewPresets = [
{ label: '정면', icon: '⬜', key: 'front' },
{ label: '평면', icon: '⬒', key: 'top' },
{ label: '우측', icon: '▷', key: 'right' },
{ label: '좌측', icon: '◁', key: 'left' },
{ label: '배면', icon: '⬛', key: 'back' },
{ label: '투시', icon: '◈', key: 'persp' },
];
viewPresets.forEach(vp => {
const btn = document.createElement('button');
btn.style.cssText = 'padding:5px 10px;background:rgba(30,41,59,0.85);border:1px solid rgba(100,116,139,0.4);color:#94a3b8;font-size:11px;font-weight:700;border-radius:6px;cursor:pointer;font-family:Pretendard,sans-serif;transition:all 0.15s;';
btn.textContent = vp.label;
btn.onmouseenter = () => { btn.style.background = 'rgba(59,130,246,0.3)'; btn.style.color = '#fff'; btn.style.borderColor = '#3b82f6'; };
btn.onmouseleave = () => { btn.style.background = 'rgba(30,41,59,0.85)'; btn.style.color = '#94a3b8'; btn.style.borderColor = 'rgba(100,116,139,0.4)'; };
btn.onclick = () => fs3dSetView(vp.key);
viewPanel.appendChild(btn);
});
container.appendChild(viewPanel);
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
@@ -2430,6 +2452,38 @@ function fs3dShowAll() {
if (badge) badge.style.display = 'none';
}
// 뷰 프리셋 (정면/평면/우측면/좌측면/배면/투시)
function fs3dSetView(preset) {
if (!camera || !controls) return;
const t = controls.target.clone(); // 현재 타겟 유지
const dist = camera.position.distanceTo(t); // 현재 거리 유지
const d = Math.max(dist, 2000); // 최소 거리 보장
let pos;
switch (preset) {
case 'front': pos = new THREE.Vector3(t.x, t.y, t.z + d); break; // +Z: 정면 (전면에서 봄)
case 'back': pos = new THREE.Vector3(t.x, t.y, t.z - d); break; // -Z: 배면 (후면에서 봄)
case 'top': pos = new THREE.Vector3(t.x, t.y + d, t.z + 1); break; // +Y: 평면 (위에서 봄)
case 'right': pos = new THREE.Vector3(t.x + d, t.y, t.z); break; // +X: 우측면
case 'left': pos = new THREE.Vector3(t.x - d, t.y, t.z); break; // -X: 좌측면
case 'persp': pos = new THREE.Vector3(t.x + d * 0.7, t.y + d * 0.5, t.z + d * 0.7); break;
default: return;
}
// 부드러운 전환 (애니메이션)
const startPos = camera.position.clone();
const startTime = performance.now();
const duration = 400; // ms
function animateView(now) {
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3); // easeOutCubic
camera.position.lerpVectors(startPos, pos, ease);
camera.lookAt(t);
controls.update();
if (progress < 1) requestAnimationFrame(animateView);
}
requestAnimationFrame(animateView);
}
// 3D Controls
window.fs3dShutterPos = function(v) {
undoSaveState();