feat: [fire-shutter] 3D 뷰 프리셋 버튼 추가 (정면/평면/우측/좌측/배면/투시)
- 우상단에 뷰 전환 버튼 패널 배치 - easeOutCubic 애니메이션으로 부드러운 카메라 전환 - 현재 타겟/거리 유지하며 카메라 위치만 변경
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user