Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
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
import { minMax } from "./utilities";
 
/**
 * Rotate `bboxCoords` by `rotation` degrees around `pivot`
 * `whRatio` is used to do rotation result in internal metric 100x100:
 *   scaleX = stageWidth / 100
 *   1. shift coords to rotate zero-based vectors: x - pivot | y - pivot
 *   2. convert internal to absolute: x * scaleX | y * scaleY
 *   3. rotate vector: x * cosA - y * sinA | x * sinA + y * cosA
 *   4. scale back:    x / scale | y / scale
 *   5. shift back:    x + pivot | y + pivot
 *   2+3+4 are simplified for x:
 *     ((x * scaleX) * cosA - (y * scaleY) * sinA) / scaleX
 *     x * cosA - y * sinA * scaleY / scaleX
 *   similar for y
 *   and scaleX / scaleY = whRatio
 * @typedef {{ left: number, top: number, right: number, bottom: number }} BBox
 * @param {BBox} bboxCoords
 * @param {number} rotation degrees
 * @param {{ x: number, y: number }} pivot
 * @param {number} whRatio
 * @returns {BBox}
 */
export function rotateBboxCoords(bboxCoords, rotation, pivot = { x: bboxCoords.left, y: bboxCoords.top }, whRatio = 1) {
  if (!bboxCoords) return bboxCoords;
  const a = (rotation * Math.PI) / 180;
  const cosA = Math.cos(a);
  const sinA = Math.sin(a);
 
  const points = [
    {
      x: bboxCoords.left - pivot.x,
      y: bboxCoords.top - pivot.y,
    },
    {
      x: bboxCoords.right - pivot.x,
      y: bboxCoords.top - pivot.y,
    },
    {
      x: bboxCoords.left - pivot.x,
      y: bboxCoords.bottom - pivot.y,
    },
    {
      x: bboxCoords.right - pivot.x,
      y: bboxCoords.bottom - pivot.y,
    },
  ].map((p) => ({
    x: p.x * cosA - (p.y * sinA) / whRatio,
    y: p.x * sinA * whRatio + p.y * cosA,
  }));
  const [left, right] = minMax(points.map((p) => p.x));
  const [top, bottom] = minMax(points.map((p) => p.y));
 
  return {
    left: left + pivot.x,
    right: right + pivot.x,
    top: top + pivot.y,
    bottom: bottom + pivot.y,
  };
}