Last active
June 20, 2021 17:07
-
-
Save steveruizok/9aff0128cba8b767fb0b2aae74314765 to your computer and use it in GitHub Desktop.
Transform a rotated bounding box.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function getRelativeTransformedBoundingBox( | |
bounds: Bounds, | |
initialBounds: Bounds, | |
initialShapeBounds: Bounds, | |
isFlippedX: boolean, | |
isFlippedY: boolean | |
) { | |
const minX = | |
bounds.minX + | |
bounds.width * | |
((isFlippedX | |
? initialBounds.maxX - initialShapeBounds.maxX | |
: initialShapeBounds.minX - initialBounds.minX) / | |
initialBounds.width) | |
const minY = | |
bounds.minY + | |
bounds.height * | |
((isFlippedY | |
? initialBounds.maxY - initialShapeBounds.maxY | |
: initialShapeBounds.minY - initialBounds.minY) / | |
initialBounds.height) | |
const width = (initialShapeBounds.width / initialBounds.width) * bounds.width | |
const height = | |
(initialShapeBounds.height / initialBounds.height) * bounds.height | |
return { | |
minX, | |
minY, | |
maxX: minX + width, | |
maxY: minY + height, | |
width, | |
height, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Get a new bounding box from a bounding box that is being transformed by having one of its corners or edges is dragged. | |
* | |
* ```javascript | |
* getTransformedBoundingBox( | |
* { | |
* minX: 0, | |
* minY: 0, | |
* maxX: 100, | |
* maxY: 100 | |
* }, | |
* TransformEdge.BottomRight, | |
* [12, 56], | |
* .0552 | |
* ) | |
* ``` | |
* | |
* @param bounds The initial bounds, ideally from when the transform session began. | |
* @param handle The edge or corner being dragged. | |
* @param delta The delta between the initial dragging point (ideally from when the transform session began) and the current dragging point. | |
* @param rotation The rotation (in radians) of the bounding box. | |
*/ | |
export function getTransformedBoundingBox( | |
bounds: Bounds, | |
handle: TransformEdge | TransformCorner, | |
delta: number[], | |
rotation = 0 | |
): Bounds { | |
// Create top left and bottom right corners. | |
let [ax0, ay0] = [bounds.minX, bounds.minY] | |
let [ax1, ay1] = [bounds.maxX, bounds.maxY] | |
// Create a second set of corners for the result. | |
let [bx0, by0] = [bounds.minX, bounds.minY] | |
let [bx1, by1] = [bounds.maxX, bounds.maxY] | |
// Counter rotate the delta. This lets us make changes as if | |
// the (possibly rotated) boxes were axis aligned. | |
const [dx, dy] = rot(delta, -rotation) | |
// Depending on the dragging handle (an edge or corner of | |
// the bounding box), find the anchor corner and use the delta | |
// to adjust the result's corners. | |
let anchor: TransformCorner | |
switch (handle) { | |
case TransformEdge.Top: { | |
anchor = TransformCorner.BottomRight | |
by0 += dy | |
break | |
} | |
case TransformEdge.Right: { | |
anchor = TransformCorner.TopLeft | |
bx1 += dx | |
break | |
} | |
case TransformEdge.Bottom: { | |
anchor = TransformCorner.TopLeft | |
by1 += dy | |
break | |
} | |
case TransformEdge.Left: { | |
anchor = TransformCorner.BottomRight | |
bx0 += dx | |
break | |
} | |
case TransformCorner.TopLeft: { | |
anchor = TransformCorner.BottomRight | |
bx0 += dx | |
by0 += dy | |
break | |
} | |
case TransformCorner.TopRight: { | |
anchor = TransformCorner.BottomLeft | |
bx1 += dx | |
by0 += dy | |
break | |
} | |
case TransformCorner.BottomRight: { | |
anchor = TransformCorner.TopLeft | |
bx1 += dx | |
by1 += dy | |
break | |
} | |
case TransformCorner.BottomLeft: { | |
anchor = TransformCorner.TopRight | |
bx0 += dx | |
by1 += dy | |
break | |
} | |
} | |
// If the bounds are rotated, get a vector from the rotated anchor | |
// corner in the inital bounds to the rotated anchor corner in the | |
// result's bounds. Subtract this vector from the result's corners, | |
// so that the two anchor points (initial and result) will be equal. | |
if (rotation % (Math.PI * 2) !== 0) { | |
let cv = [0, 0] | |
const c0 = med([ax0, ay0], [ax1, ay1]) | |
const c1 = med([bx0, by0], [bx1, by1]) | |
switch (anchor) { | |
case TransformCorner.TopLeft: { | |
cv = sub( | |
rotWith([bx0, by0], c1, rotation), | |
rotWith([ax0, ay0], c0, rotation) | |
) | |
break | |
} | |
case TransformCorner.TopRight: { | |
cv = sub( | |
rotWith([bx1, by0], c1, rotation), | |
rotWith([ax1, ay0], c0, rotation) | |
) | |
break | |
} | |
case TransformCorner.BottomRight: { | |
cv = sub( | |
rotWith([bx1, by1], c1, rotation), | |
rotWith([ax1, ay1], c0, rotation) | |
) | |
break | |
} | |
case TransformCorner.BottomLeft: { | |
cv = sub( | |
rotWith([bx0, by1], c1, rotation), | |
rotWith([ax0, ay1], c0, rotation) | |
) | |
break | |
} | |
} | |
;[bx0, by0] = sub([bx0, by0], cv) | |
;[bx1, by1] = sub([bx1, by1], cv) | |
} | |
// If the axes are flipped (e.g. if the right edge has been dragged | |
// left past the initial left edge) then swap points on that axis. | |
if (bx1 < bx0) { | |
;[bx1, bx0] = [bx0, bx1] | |
} | |
if (by1 < by0) { | |
;[by1, by0] = [by0, by1] | |
} | |
return { | |
minX: bx0, | |
minY: by0, | |
maxX: bx1, | |
maxY: by1, | |
width: bx1 - bx0, | |
height: by1 - by0, | |
} | |
} | |
/* ---------------------- Types --------------------- */ | |
export interface Bounds { | |
minX: number | |
minY: number | |
maxX: number | |
maxY: number | |
width: number | |
height: number | |
} | |
export enum TransformEdge { | |
Top = "top_edge", | |
Right = "right_edge", | |
Bottom = "bottom_edge", | |
Left = "left_edge", | |
} | |
export enum TransformCorner { | |
TopLeft = "top_left_corner", | |
TopRight = "top_right_corner", | |
BottomRight = "bottom_right_corner", | |
BottomLeft = "bottom_left_corner", | |
} | |
/* -------------------- Utilities ------------------- */ | |
// Rotate a vector / point | |
function rot(A: number[], r: number) { | |
return [ | |
A[0] * Math.cos(r) - A[1] * Math.sin(r), | |
A[0] * Math.sin(r) + A[1] * Math.cos(r), | |
] | |
} | |
// Find the midpoint of two vectors / points | |
function med(A: number[], B: number[]) { | |
return [A[0] + B[0] / 2, A[1] + B[1] / 2] | |
} | |
// Subtract two vectors / points | |
function sub(A: number[], B: number[]) { | |
return [A[0] - B[0], A[1] - B[1]] | |
} | |
// Rotate a vector / point around a center | |
function rotWith(A: number[], C: number[], r: number) { | |
if (r === 0) return A | |
const s = Math.sin(r) | |
const c = Math.cos(r) | |
const px = A[0] - C[0] | |
const py = A[1] - C[1] | |
return [(px * c - py * s) + C[0], (px * s + py * c) + C[1]] | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment