1import { Box3, Vector3 } from "three";
2
3export function createDeviceFraming() {
4 const center = new Vector3();
5 const target = new Vector3();
6 const velocity = new Vector3();
7 const bounds = new Box3();
8 let distance = 1;
9 let targetDistance = 1;
10 let distanceVelocity = 0;
11 let tanX = 1;
12 let tanY = 1;
13 let at = 0;
14 let initialized = false;
15 let moving = false;
16 let held = false;
17 const advance = (now: number, immediate = false) => {
18 if (held) return false;
19 const dt = Math.max(0, (now - at) / 1000);
20 at = now;
21 if (!moving) return false;
22 const omega = 14;
23 const decay = Math.exp(-omega * dt);
24 const error = center.clone().sub(target);
25 const coefficient = velocity.clone().addScaledVector(error, omega);
26 center.copy(error).addScaledVector(coefficient, dt).multiplyScalar(decay).add(target);
27 velocity.addScaledVector(coefficient, -omega * dt).multiplyScalar(decay);
28 const distanceError = distance - targetDistance;
29 const distanceCoefficient = distanceVelocity + omega * distanceError;
30 distance = targetDistance + (distanceError + distanceCoefficient * dt) * decay;
31 distanceVelocity = (distanceVelocity - omega * distanceCoefficient * dt) * decay;
32 moving =
33 center.distanceTo(target) > 0.0005 ||
34 velocity.length() > 0.005 ||
35 Math.abs(distance - targetDistance) > 0.0005 ||
36 Math.abs(distanceVelocity) > 0.005;
37 if (immediate || !moving) {
38 center.copy(target);
39 distance = targetDistance;
40 velocity.set(0, 0, 0);
41 distanceVelocity = 0;
42 moving = false;
43 }
44 return true;
45 };
46 return {
47 center,
48 setBounds(next: Box3, vertical: number, aspect: number, now: number, immediate = false) {
49 if (next.isEmpty()) return;
50 advance(now);
51 bounds.copy(next);
52 tanY = Math.tan(vertical);
53 tanX = tanY * aspect;
54 bounds.getCenter(target);
55 target.z = 0;
56 const size = bounds.getSize(new Vector3());
57 targetDistance = Math.max(1, Math.max(size.x / tanX, size.y / tanY) * 0.565 + bounds.max.z);
58 moving = true;
59 advance(now, immediate || !initialized);
60 initialized = true;
61 },
62 advance,
63 distance() {
64 const clearance = Math.max(
65 Math.abs(bounds.min.x - center.x) / tanX,
66 Math.abs(bounds.max.x - center.x) / tanX,
67 Math.abs(bounds.min.y - center.y) / tanY,
68 Math.abs(bounds.max.y - center.y) / tanY,
69 );
70 return Math.max(distance, clearance * 1.02 + bounds.max.z, 1);
71 },
72 hold(active: boolean, now: number) {
73 held = active;
74 at = now;
75 },
76 needsFrame() {
77 return moving && !held;
78 },
79 };
80}