Skip to content

Instantly share code, notes, and snippets.

@steveruizok
Last active June 20, 2021 17:07
Show Gist options
  • Save steveruizok/9aff0128cba8b767fb0b2aae74314765 to your computer and use it in GitHub Desktop.
Save steveruizok/9aff0128cba8b767fb0b2aae74314765 to your computer and use it in GitHub Desktop.
Transform a rotated bounding box.
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,
}
}
/**
* 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