Last active
July 29, 2021 07:14
-
-
Save steveruizok/72ba6de2c6d535b8fdbc9d40111644fd to your computer and use it in GitHub Desktop.
getTransformedBoundingBox
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 the relative bounds (usually a child) within a transformed bounding box. | |
*/ | |
function getRelativeTransformedBoundingBox( | |
bounds: TLBounds, | |
initialBounds: TLBounds, | |
initialShapeBounds: TLBounds, | |
isFlippedX: boolean, | |
isFlippedY: boolean, | |
): TLBounds { | |
const nx = | |
(isFlippedX | |
? initialBounds.maxX - initialShapeBounds.maxX | |
: initialShapeBounds.minX - initialBounds.minX) / initialBounds.width | |
const ny = | |
(isFlippedY | |
? initialBounds.maxY - initialShapeBounds.maxY | |
: initialShapeBounds.minY - initialBounds.minY) / initialBounds.height | |
const nw = initialShapeBounds.width / initialBounds.width | |
const nh = initialShapeBounds.height / initialBounds.height | |
const minX = bounds.minX + bounds.width * nx | |
const minY = bounds.minY + bounds.height * ny | |
const width = bounds.width * nw | |
const height = bounds.height * nh | |
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
function getTransformedBoundingBox( | |
bounds: TLBounds, | |
handle: TLBoundsCorner | TLBoundsEdge | 'center', | |
delta: number[], | |
rotation = 0, | |
isAspectRatioLocked = false, | |
): TLBounds & { scaleX: number; scaleY: number } { | |
// Create top left and bottom right corners. | |
const [ax0, ay0] = [bounds.minX, bounds.minY] | |
const [ax1, ay1] = [bounds.maxX, bounds.maxY] | |
// Create a second set of corners for the new box. | |
let [bx0, by0] = [bounds.minX, bounds.minY] | |
let [bx1, by1] = [bounds.maxX, bounds.maxY] | |
// If the drag is on the center, just translate the bounds. | |
if (handle === 'center') { | |
return { | |
minX: bx0 + delta[0], | |
minY: by0 + delta[1], | |
maxX: bx1 + delta[0], | |
maxY: by1 + delta[1], | |
width: bx1 - bx0, | |
height: by1 - by0, | |
scaleX: 1, | |
scaleY: 1, | |
} | |
} | |
// Counter rotate the delta. This lets us make changes as if | |
// the (possibly rotated) boxes were axis aligned. | |
const [dx, dy] = vec.rot(delta, -rotation) | |
/* | |
1. Delta | |
Use the delta to adjust the new box by changing its corners. | |
The dragging handle (corner or edge) will determine which | |
corners should change. | |
*/ | |
switch (handle) { | |
case TLBoundsEdge.Top: | |
case TLBoundsCorner.TopLeft: | |
case TLBoundsCorner.TopRight: { | |
by0 += dy | |
break | |
} | |
case TLBoundsEdge.Bottom: | |
case TLBoundsCorner.BottomLeft: | |
case TLBoundsCorner.BottomRight: { | |
by1 += dy | |
break | |
} | |
} | |
switch (handle) { | |
case TLBoundsEdge.Left: | |
case TLBoundsCorner.TopLeft: | |
case TLBoundsCorner.BottomLeft: { | |
bx0 += dx | |
break | |
} | |
case TLBoundsEdge.Right: | |
case TLBoundsCorner.TopRight: | |
case TLBoundsCorner.BottomRight: { | |
bx1 += dx | |
break | |
} | |
} | |
const aw = ax1 - ax0 | |
const ah = ay1 - ay0 | |
const scaleX = (bx1 - bx0) / aw | |
const scaleY = (by1 - by0) / ah | |
const flipX = scaleX < 0 | |
const flipY = scaleY < 0 | |
const bw = Math.abs(bx1 - bx0) | |
const bh = Math.abs(by1 - by0) | |
/* | |
2. Aspect ratio | |
If the aspect ratio is locked, adjust the corners so that the | |
new box's aspect ratio matches the original aspect ratio. | |
*/ | |
if (isAspectRatioLocked) { | |
const ar = aw / ah | |
const isTall = ar < bw / bh | |
const tw = bw * (scaleY < 0 ? 1 : -1) * (1 / ar) | |
const th = bh * (scaleX < 0 ? 1 : -1) * ar | |
switch (handle) { | |
case TLBoundsCorner.TopLeft: { | |
if (isTall) by0 = by1 + tw | |
else bx0 = bx1 + th | |
break | |
} | |
case TLBoundsCorner.TopRight: { | |
if (isTall) by0 = by1 + tw | |
else bx1 = bx0 - th | |
break | |
} | |
case TLBoundsCorner.BottomRight: { | |
if (isTall) by1 = by0 - tw | |
else bx1 = bx0 - th | |
break | |
} | |
case TLBoundsCorner.BottomLeft: { | |
if (isTall) by1 = by0 - tw | |
else bx0 = bx1 + th | |
break | |
} | |
case TLBoundsEdge.Bottom: | |
case TLBoundsEdge.Top: { | |
const m = (bx0 + bx1) / 2 | |
const w = bh * ar | |
bx0 = m - w / 2 | |
bx1 = m + w / 2 | |
break | |
} | |
case TLBoundsEdge.Left: | |
case TLBoundsEdge.Right: { | |
const m = (by0 + by1) / 2 | |
const h = bw / ar | |
by0 = m - h / 2 | |
by1 = m + h / 2 | |
break | |
} | |
} | |
} | |
/* | |
3. Rotation | |
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 = vec.med([ax0, ay0], [ax1, ay1]) | |
const c1 = vec.med([bx0, by0], [bx1, by1]) | |
switch (handle) { | |
case TLBoundsCorner.TopLeft: { | |
cv = vec.sub(vec.rotWith([bx1, by1], c1, rotation), vec.rotWith([ax1, ay1], c0, rotation)) | |
break | |
} | |
case TLBoundsCorner.TopRight: { | |
cv = vec.sub(vec.rotWith([bx0, by1], c1, rotation), vec.rotWith([ax0, ay1], c0, rotation)) | |
break | |
} | |
case TLBoundsCorner.BottomRight: { | |
cv = vec.sub(vec.rotWith([bx0, by0], c1, rotation), vec.rotWith([ax0, ay0], c0, rotation)) | |
break | |
} | |
case TLBoundsCorner.BottomLeft: { | |
cv = vec.sub(vec.rotWith([bx1, by0], c1, rotation), vec.rotWith([ax1, ay0], c0, rotation)) | |
break | |
} | |
case TLBoundsEdge.Top: { | |
cv = vec.sub( | |
vec.rotWith(vec.med([bx0, by1], [bx1, by1]), c1, rotation), | |
vec.rotWith(vec.med([ax0, ay1], [ax1, ay1]), c0, rotation), | |
) | |
break | |
} | |
case TLBoundsEdge.Left: { | |
cv = vec.sub( | |
vec.rotWith(vec.med([bx1, by0], [bx1, by1]), c1, rotation), | |
vec.rotWith(vec.med([ax1, ay0], [ax1, ay1]), c0, rotation), | |
) | |
break | |
} | |
case TLBoundsEdge.Bottom: { | |
cv = vec.sub( | |
vec.rotWith(vec.med([bx0, by0], [bx1, by0]), c1, rotation), | |
vec.rotWith(vec.med([ax0, ay0], [ax1, ay0]), c0, rotation), | |
) | |
break | |
} | |
case TLBoundsEdge.Right: { | |
cv = vec.sub( | |
vec.rotWith(vec.med([bx0, by0], [bx0, by1]), c1, rotation), | |
vec.rotWith(vec.med([ax0, ay0], [ax0, ay1]), c0, rotation), | |
) | |
break | |
} | |
} | |
;[bx0, by0] = vec.sub([bx0, by0], cv) | |
;[bx1, by1] = vec.sub([bx1, by1], cv) | |
} | |
/* | |
4. Flips | |
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, | |
scaleX: ((bx1 - bx0) / (ax1 - ax0 || 1)) * (flipX ? -1 : 1), | |
scaleY: ((by1 - by0) / (ay1 - ay0 || 1)) * (flipY ? -1 : 1), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ahh, right. In order to translate around a specific point (center, corner, etc) you are actually doing 3 operations