feat: add axis-aligned 3d camera views
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { orbitForViewDirection } from "./orbit-view.js";
|
||||
|
||||
const MAX_PIXEL_RATIO = 3;
|
||||
const MAX_SURFACE_VIEW_SAMPLES = 20_000;
|
||||
const MAX_PITCH_RAD = Math.PI * 0.46;
|
||||
const DEFAULT_YAW_RAD = -Math.PI / 5;
|
||||
const DEFAULT_PITCH_RAD = Math.PI / 4.5;
|
||||
const MAX_PITCH_RAD = Math.PI / 2;
|
||||
|
||||
function clamp(value, minimum, maximum) {
|
||||
return Math.min(maximum, Math.max(minimum, value));
|
||||
@@ -114,11 +114,10 @@ function rotatedPoint(point, yaw, pitch) {
|
||||
|
||||
function projectedPoint(point, yaw, pitch, size) {
|
||||
const rotated = rotatedPoint(point, yaw, pitch);
|
||||
const perspective = 1 / Math.max(0.68, 1 - rotated[2] * 0.18);
|
||||
const scale = Math.min(size.width, size.height) * 0.39;
|
||||
return {
|
||||
x: size.width / 2 + rotated[0] * scale * perspective,
|
||||
y: size.height / 2 - rotated[1] * scale * perspective,
|
||||
x: size.width / 2 + rotated[0] * scale,
|
||||
y: size.height / 2 - rotated[1] * scale,
|
||||
depth: rotated[2],
|
||||
};
|
||||
}
|
||||
@@ -133,7 +132,7 @@ function polygonArea(points) {
|
||||
return Math.abs(area) / 2;
|
||||
}
|
||||
|
||||
export function createReceiveBeamPatternSurfaceView(canvas) {
|
||||
export function createReceiveBeamPatternSurfaceView(canvas, callbacks = {}) {
|
||||
if (!canvas?.getContext) {
|
||||
throw new TypeError("3D 빔 패턴 canvas가 필요합니다.");
|
||||
}
|
||||
@@ -146,8 +145,8 @@ export function createReceiveBeamPatternSurfaceView(canvas) {
|
||||
let surface = null;
|
||||
let validation = null;
|
||||
let errorMessage = "";
|
||||
let yaw = DEFAULT_YAW_RAD;
|
||||
let pitch = DEFAULT_PITCH_RAD;
|
||||
let viewDirection = "iso";
|
||||
let { yaw, pitch } = orbitForViewDirection(viewDirection);
|
||||
let orbit = null;
|
||||
let frameId = 0;
|
||||
let destroyed = false;
|
||||
@@ -324,6 +323,26 @@ export function createReceiveBeamPatternSurfaceView(canvas) {
|
||||
schedule();
|
||||
}
|
||||
|
||||
function updateViewDirection(nextDirection) {
|
||||
if (viewDirection === nextDirection) return;
|
||||
viewDirection = nextDirection;
|
||||
canvas.dataset.viewAxis = viewDirection;
|
||||
callbacks.onViewDirectionChange?.(viewDirection);
|
||||
}
|
||||
|
||||
function setViewDirection(nextDirection) {
|
||||
const nextOrbit = orbitForViewDirection(nextDirection);
|
||||
yaw = nextOrbit.yaw;
|
||||
pitch = nextOrbit.pitch;
|
||||
updateViewDirection(nextDirection);
|
||||
schedule();
|
||||
return viewDirection;
|
||||
}
|
||||
|
||||
function markFreeOrbit() {
|
||||
updateViewDirection("custom");
|
||||
}
|
||||
|
||||
function finishOrbit(event) {
|
||||
if (!orbit || (event?.pointerId !== undefined && event.pointerId !== orbit.pointerId)) return;
|
||||
const pointerId = orbit.pointerId;
|
||||
@@ -356,12 +375,17 @@ export function createReceiveBeamPatternSurfaceView(canvas) {
|
||||
function onPointerMove(event) {
|
||||
if (!orbit || orbit.pointerId !== event.pointerId) return;
|
||||
event.preventDefault();
|
||||
yaw += (event.clientX - orbit.x) * 0.011;
|
||||
pitch = clamp(
|
||||
pitch - (event.clientY - orbit.y) * 0.011,
|
||||
const deltaX = event.clientX - orbit.x;
|
||||
const deltaY = event.clientY - orbit.y;
|
||||
const nextYaw = yaw + deltaX * 0.011;
|
||||
const nextPitch = clamp(
|
||||
pitch - deltaY * 0.011,
|
||||
-MAX_PITCH_RAD,
|
||||
MAX_PITCH_RAD,
|
||||
);
|
||||
if (nextYaw !== yaw || nextPitch !== pitch) markFreeOrbit();
|
||||
yaw = nextYaw;
|
||||
pitch = nextPitch;
|
||||
orbit.x = event.clientX;
|
||||
orbit.y = event.clientY;
|
||||
schedule();
|
||||
@@ -369,16 +393,33 @@ export function createReceiveBeamPatternSurfaceView(canvas) {
|
||||
|
||||
function onKeyDown(event) {
|
||||
const step = event.shiftKey ? 15 : 5;
|
||||
if (event.key === "ArrowLeft") yaw -= step * Math.PI / 180;
|
||||
else if (event.key === "ArrowRight") yaw += step * Math.PI / 180;
|
||||
if (event.key === "ArrowLeft") {
|
||||
markFreeOrbit();
|
||||
yaw -= step * Math.PI / 180;
|
||||
}
|
||||
else if (event.key === "ArrowRight") {
|
||||
markFreeOrbit();
|
||||
yaw += step * Math.PI / 180;
|
||||
}
|
||||
else if (event.key === "ArrowUp") {
|
||||
pitch = clamp(pitch + step * Math.PI / 180, -MAX_PITCH_RAD, MAX_PITCH_RAD);
|
||||
const nextPitch = clamp(
|
||||
pitch + step * Math.PI / 180,
|
||||
-MAX_PITCH_RAD,
|
||||
MAX_PITCH_RAD,
|
||||
);
|
||||
if (nextPitch !== pitch) markFreeOrbit();
|
||||
pitch = nextPitch;
|
||||
} else if (event.key === "ArrowDown") {
|
||||
pitch = clamp(pitch - step * Math.PI / 180, -MAX_PITCH_RAD, MAX_PITCH_RAD);
|
||||
const nextPitch = clamp(
|
||||
pitch - step * Math.PI / 180,
|
||||
-MAX_PITCH_RAD,
|
||||
MAX_PITCH_RAD,
|
||||
);
|
||||
if (nextPitch !== pitch) markFreeOrbit();
|
||||
pitch = nextPitch;
|
||||
}
|
||||
else if (event.key.toLowerCase() === "r") {
|
||||
yaw = DEFAULT_YAW_RAD;
|
||||
pitch = DEFAULT_PITCH_RAD;
|
||||
setViewDirection("iso");
|
||||
} else return;
|
||||
event.preventDefault();
|
||||
schedule();
|
||||
@@ -399,15 +440,15 @@ export function createReceiveBeamPatternSurfaceView(canvas) {
|
||||
} else {
|
||||
view.addEventListener?.("resize", onResize);
|
||||
}
|
||||
canvas.dataset.viewAxis = viewDirection;
|
||||
schedule();
|
||||
|
||||
return {
|
||||
render,
|
||||
renderError,
|
||||
setViewDirection,
|
||||
resetOrbit() {
|
||||
yaw = DEFAULT_YAW_RAD;
|
||||
pitch = DEFAULT_PITCH_RAD;
|
||||
schedule();
|
||||
return setViewDirection("iso");
|
||||
},
|
||||
destroy() {
|
||||
if (destroyed) return;
|
||||
|
||||
Reference in New Issue
Block a user