File size: 4,697 Bytes
ec3121c
 
 
 
49c931b
ec3121c
 
 
 
 
 
 
4dbe7ae
ec3121c
49c931b
 
 
ec3121c
49c931b
 
 
 
ec3121c
49c931b
 
 
 
ec3121c
49c931b
 
 
 
ec3121c
49c931b
 
 
ec3121c
49c931b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec3121c
49c931b
ec3121c
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import type { Camera } from "../cameras/Camera";
import { Matrix3 } from "../math/Matrix3";
import { Vector3 } from "../math/Vector3";

class OrbitControls {
    minBeta: number = (5 * Math.PI) / 180;
    maxBeta: number = (85 * Math.PI) / 180;
    minZoom: number = 0.1;
    maxZoom: number = 30;
    orbitSpeed: number = 1;
    panSpeed: number = 1;
    zoomSpeed: number = 1;
    dampening: number = 0.3;

    update: () => void;
    dispose: () => void;

    constructor(camera: Camera, domElement: HTMLElement) {
        let target = new Vector3();
        let alpha = 0;
        let beta = 0;
        let radius = 5;

        let desiredTarget = target.clone();
        let desiredAlpha = alpha;
        let desiredBeta = beta;
        let desiredRadius = radius;

        let dragging = false;
        let panning = false;
        let lastX = 0;
        let lastY = 0;

        const computeZoomNorm = () => {
            return 0.1 + (0.9 * (desiredRadius - this.minZoom)) / (this.maxZoom - this.minZoom);
        };

        const onPointerDown = (e: PointerEvent) => {
            e.preventDefault();
            e.stopPropagation();

            dragging = true;
            if (e.pointerType === "touch") {
                panning = e.pointerId === 2;
            } else {
                panning = e.button === 2;
            }
            lastX = e.clientX;
            lastY = e.clientY;
            window.addEventListener("pointerup", onPointerUp);
            window.addEventListener("pointercancel", onPointerUp);
        };

        const onPointerUp = (e: PointerEvent) => {
            e.preventDefault();
            e.stopPropagation();

            dragging = false;
            panning = false;
            window.removeEventListener("pointerup", onPointerUp);
            window.removeEventListener("pointercancel", onPointerUp);
        };

        const onPointerMove = (e: PointerEvent) => {
            e.preventDefault();
            e.stopPropagation();

            if (!dragging) return;

            const dx = e.clientX - lastX;
            const dy = e.clientY - lastY;
            const zoomNorm = computeZoomNorm();

            if (panning) {
                const panX = -dx * this.panSpeed * 0.01 * zoomNorm;
                const panY = -dy * this.panSpeed * 0.01 * zoomNorm;
                const R = camera.rotation.buffer;
                const right = new Vector3(R[0], R[3], R[6]);
                const up = new Vector3(R[1], R[4], R[7]);
                desiredTarget.add(right.multiply(panX));
                desiredTarget.add(up.multiply(panY));
            } else {
                desiredAlpha -= dx * this.orbitSpeed * 0.005;
                desiredBeta += dy * this.orbitSpeed * 0.005;
                desiredBeta = Math.min(Math.max(desiredBeta, this.minBeta), this.maxBeta);
            }

            lastX = e.clientX;
            lastY = e.clientY;
        };

        const onWheel = (e: WheelEvent) => {
            e.preventDefault();
            e.stopPropagation();

            const zoomNorm = computeZoomNorm();
            desiredRadius += e.deltaY * this.zoomSpeed * 0.02 * zoomNorm;
            desiredRadius = Math.min(Math.max(desiredRadius, this.minZoom), this.maxZoom);
        };

        const lerp = (a: number, b: number, t: number) => {
            return (1 - t) * a + t * b;
        };

        this.update = () => {
            alpha = lerp(alpha, desiredAlpha, this.dampening);
            beta = lerp(beta, desiredBeta, this.dampening);
            radius = lerp(radius, desiredRadius, this.dampening);
            target = target.lerp(desiredTarget, this.dampening);

            const x = target.x + radius * Math.sin(alpha) * Math.cos(beta);
            const y = target.y - radius * Math.sin(beta);
            const z = target.z - radius * Math.cos(alpha) * Math.cos(beta);
            camera.position.set(x, y, z);

            const direction = target.clone().subtract(camera.position).normalize();
            const rx = Math.asin(-direction.y);
            const ry = Math.atan2(direction.x, direction.z);
            camera.rotation = Matrix3.RotationFromEuler(new Vector3(rx, ry, 0));
        };

        this.dispose = () => {
            domElement.removeEventListener("pointerdown", onPointerDown);
            domElement.removeEventListener("pointermove", onPointerMove);
            domElement.removeEventListener("wheel", onWheel);
        };

        domElement.addEventListener("pointerdown", onPointerDown);
        domElement.addEventListener("pointermove", onPointerMove);
        domElement.addEventListener("wheel", onWheel);

        this.update();
    }
}

export { OrbitControls };