Improves rotated transforms, cleans up code

This commit is contained in:
Steve Ruiz 2021-05-19 13:27:01 +01:00
parent c3740cacdd
commit 0c205d1377
9 changed files with 208 additions and 625 deletions

View file

@ -5,7 +5,7 @@ import { createShape } from "./index"
import { boundsContained } from "utils/bounds"
import { intersectCircleBounds } from "utils/intersections"
import { pointInCircle } from "utils/hitTests"
import { translateBounds } from "utils/utils"
import { getTransformAnchor, translateBounds } from "utils/utils"
const circle = createShape<CircleShape>({
boundsCache: new WeakMap([]),
@ -94,7 +94,9 @@ const circle = createShape<CircleShape>({
return shape
},
transform(shape, bounds, { anchor }) {
transform(shape, bounds, { type, initialShape, scaleX, scaleY }) {
const anchor = getTransformAnchor(type, scaleX < 0, scaleY < 0)
// Set the new corner or position depending on the anchor
switch (anchor) {
case TransformCorner.TopLeft: {
@ -112,7 +114,11 @@ const circle = createShape<CircleShape>({
}
case TransformCorner.BottomRight: {
shape.radius = Math.min(bounds.width, bounds.height) / 2
shape.point = [bounds.minX, bounds.minY]
shape.point = [
bounds.maxX - shape.radius * 2,
bounds.maxY - shape.radius * 2,
]
break
break
}
case TransformCorner.BottomLeft: {

View file

@ -61,14 +61,9 @@ export interface ShapeUtility<K extends Shape> {
bounds: Bounds,
info: {
type: TransformEdge | TransformCorner
boundsRotation: number
initialShape: K
initialShapeBounds: BoundsSnapshot
initialBounds: Bounds
isFlippedX: boolean
isFlippedY: boolean
isSingle: boolean
anchor: TransformEdge | TransformCorner
scaleX: number
scaleY: number
}
): K
@ -78,14 +73,9 @@ export interface ShapeUtility<K extends Shape> {
bounds: Bounds,
info: {
type: TransformEdge | TransformCorner
boundsRotation: number
initialShape: K
initialShapeBounds: BoundsSnapshot
initialBounds: Bounds
isFlippedX: boolean
isFlippedY: boolean
isSingle: boolean
anchor: TransformEdge | TransformCorner
scaleX: number
scaleY: number
}
): K

View file

@ -99,142 +99,34 @@ const rectangle = createShape<RectangleShape>({
return shape
},
transform(
shape,
shapeBounds,
{ initialShape, isSingle, initialShapeBounds, isFlippedX, isFlippedY }
) {
if (shape.rotation === 0 || isSingle) {
shape.size = [shapeBounds.width, shapeBounds.height]
shape.point = [shapeBounds.minX, shapeBounds.minY]
transform(shape, bounds, { initialShape, scaleX, scaleY }) {
if (shape.rotation === 0) {
shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY]
} else {
// Center shape in resized bounds
shape.size = vec.mul(
initialShape.size,
Math.min(
shapeBounds.width / initialShapeBounds.width,
shapeBounds.height / initialShapeBounds.height
)
Math.min(Math.abs(scaleX), Math.abs(scaleY))
)
const newCenter = [
shapeBounds.minX + shapeBounds.width / 2,
shapeBounds.minY + shapeBounds.height / 2,
]
shape.point = vec.sub(newCenter, vec.div(shape.size, 2))
shape.point = vec.sub(
vec.med([bounds.minX, bounds.minY], [bounds.maxX, bounds.maxY]),
vec.div(shape.size, 2)
)
}
// Rotation for flipped shapes
// Set rotation for flipped shapes
shape.rotation = initialShape.rotation
if (isFlippedX) {
shape.rotation *= -1
}
if (isFlippedY) {
shape.rotation *= -1
}
if (scaleX < 0) shape.rotation *= -1
if (scaleY < 0) shape.rotation *= -1
return shape
},
transformSingle(
shape,
bounds,
{ initialShape, initialShapeBounds, anchor, isFlippedY, isFlippedX }
) {
transformSingle(shape, bounds) {
shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY]
// const prevCorners = getRotatedCorners(
// initialShapeBounds,
// initialShape.rotation
// )
// let currCorners = getRotatedCorners(this.getBounds(shape), shape.rotation)
// if (isFlippedX) {
// let t = currCorners[3]
// currCorners[3] = currCorners[2]
// currCorners[2] = t
// t = currCorners[0]
// currCorners[0] = currCorners[1]
// currCorners[1] = t
// }
// if (isFlippedY) {
// let t = currCorners[3]
// currCorners[3] = currCorners[0]
// currCorners[0] = t
// t = currCorners[2]
// currCorners[2] = currCorners[1]
// currCorners[1] = t
// }
// switch (anchor) {
// case TransformCorner.TopLeft: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[2], prevCorners[2])
// )
// break
// }
// case TransformCorner.TopRight: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[3], prevCorners[3])
// )
// break
// }
// case TransformCorner.BottomRight: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[0], prevCorners[0])
// )
// break
// }
// case TransformCorner.BottomLeft: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[1], prevCorners[1])
// )
// break
// }
// case TransformEdge.Top: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[3], prevCorners[3])
// )
// break
// }
// case TransformEdge.Right: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[3], prevCorners[3])
// )
// break
// }
// case TransformEdge.Bottom: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[0], prevCorners[0])
// )
// break
// }
// case TransformEdge.Left: {
// shape.point = vec.sub(
// shape.point,
// vec.sub(currCorners[2], prevCorners[2])
// )
// break
// }
// }
// console.log(shape.point, shape.size)
return shape
},

View file

@ -8,63 +8,38 @@ export default function transformSingleCommand(
data: Data,
before: TransformSingleSnapshot,
after: TransformSingleSnapshot,
anchor: TransformCorner | TransformEdge
scaleX: number,
scaleY: number
) {
history.execute(
data,
new Command({
name: "translate_shapes",
name: "transform_single_shape",
category: "canvas",
do(data) {
const {
const { id, currentPageId, type, initialShape, initialShapeBounds } =
after
const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transformSingle(shape, initialShapeBounds, {
type,
initialShape,
initialShapeBounds,
currentPageId,
id,
boundsRotation,
} = after
const { shapes } = data.document.pages[currentPageId]
const shape = shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, {
type,
initialShape,
initialShapeBounds,
initialBounds: initialShapeBounds,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor,
scaleX,
scaleY,
})
},
undo(data) {
const {
type,
initialShape,
initialShapeBounds,
currentPageId,
id,
boundsRotation,
} = before
const { id, currentPageId, type, initialShape, initialShapeBounds } =
before
const { shapes } = data.document.pages[currentPageId]
const shape = shapes[id]
const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, {
type,
initialShape,
initialShapeBounds,
initialBounds: initialShapeBounds,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor,
initialShape: after.initialShape,
scaleX: 1,
scaleY: 1,
})
},
})

View file

@ -8,7 +8,8 @@ export default function transformCommand(
data: Data,
before: TransformSnapshot,
after: TransformSnapshot,
anchor: TransformCorner | TransformEdge
scaleX: number,
scaleY: number
) {
history.execute(
data,
@ -16,60 +17,32 @@ export default function transformCommand(
name: "translate_shapes",
category: "canvas",
do(data) {
const {
type,
shapeBounds,
initialBounds,
currentPageId,
selectedIds,
boundsRotation,
} = after
const { shapes } = data.document.pages[currentPageId]
const { type, currentPageId, selectedIds } = after
selectedIds.forEach((id) => {
const { initialShape, initialShapeBounds } = shapeBounds[id]
const shape = shapes[id]
const { initialShape, initialShapeBounds } = after.shapeBounds[id]
const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, {
type,
initialShape,
initialShapeBounds,
initialBounds,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor,
scaleX: 1,
scaleY: 1,
})
})
},
undo(data) {
const {
type,
shapeBounds,
initialBounds,
currentPageId,
selectedIds,
boundsRotation,
} = before
const { shapes } = data.document.pages[currentPageId]
const { type, currentPageId, selectedIds } = before
selectedIds.forEach((id) => {
const { initialShape, initialShapeBounds } = shapeBounds[id]
const shape = shapes[id]
const { initialShape, initialShapeBounds } = before.shapeBounds[id]
const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, {
type,
initialShape,
initialShapeBounds,
initialBounds,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor: type,
scaleX: 1,
scaleY: 1,
})
})
},

View file

@ -1,28 +1,21 @@
import {
Data,
TransformEdge,
TransformCorner,
Bounds,
BoundsSnapshot,
} from "types"
import { Data, TransformEdge, TransformCorner } from "types"
import * as vec from "utils/vec"
import BaseSession from "./base-session"
import commands from "state/commands"
import { current } from "immer"
import { getShapeUtils } from "lib/shapes"
import { getCommonBounds, getTransformAnchor } from "utils/utils"
import {
getCommonBounds,
getRelativeTransformedBoundingBox,
getTransformedBoundingBox,
} from "utils/utils"
export default class TransformSession extends BaseSession {
delta = [0, 0]
isFlippedX = false
isFlippedY = false
scaleX = 1
scaleY = 1
transformType: TransformEdge | TransformCorner
origin: number[]
snapshot: TransformSnapshot
corners: {
a: number[]
b: number[]
}
constructor(
data: Data,
@ -32,196 +25,62 @@ export default class TransformSession extends BaseSession {
super(data)
this.origin = point
this.transformType = transformType
// if (data.selectedIds.size === 1) {
// const shape =
// data.document.pages[data.currentPageId].shapes[
// Array.from(data.selectedIds.values())[0]
// ]
// if (shape.rotation > 0) {
// }
// }
this.snapshot = getTransformSnapshot(data, transformType)
const { minX, minY, maxX, maxY } = this.snapshot.initialBounds
this.corners = {
a: [minX, minY],
b: [maxX, maxY],
}
}
update(data: Data, point: number[]) {
const {
corners: { a, b },
transformType,
} = this
const { transformType } = this
const {
boundsRotation,
shapeBounds,
const { currentPageId, selectedIds, shapeBounds, initialBounds } =
this.snapshot
const newBoundingBox = getTransformedBoundingBox(
initialBounds,
currentPageId,
selectedIds,
} = this.snapshot
transformType,
vec.vec(this.origin, point),
data.boundsRotation
)
const { shapes } = data.document.pages[currentPageId]
let delta = vec.vec(this.origin, point)
// if (isSingle) {
// const center = [
// initialBounds.minX + initialBounds.width / 2,
// initialBounds.minY + initialBounds.height / 2,
// ]
// const rotation = shapes[Array.from(selectedIds.values())[0]].rotation
// const rotatedOrigin = vec.rotWith(this.origin, center, -rotation)
// const rotatedPoint = vec.rotWith(point, center, -rotation)
// delta = vec.vec(rotatedOrigin, rotatedPoint)
// }
/*
Transforms
Corners a and b are the original top-left and bottom-right corners of the
bounding box. Depending on what the user is dragging, change one or both
points. To keep things smooth, calculate based by adding the delta (the
vector between the current point and its original point) to the original
bounding box values.
*/
switch (transformType) {
case TransformEdge.Top: {
a[1] = initialBounds.minY + delta[1]
break
}
case TransformEdge.Right: {
b[0] = initialBounds.maxX + delta[0]
break
}
case TransformEdge.Bottom: {
b[1] = initialBounds.maxY + delta[1]
break
}
case TransformEdge.Left: {
a[0] = initialBounds.minX + delta[0]
break
}
case TransformCorner.TopLeft: {
a[0] = initialBounds.minX + delta[0]
a[1] = initialBounds.minY + delta[1]
break
}
case TransformCorner.TopRight: {
a[1] = initialBounds.minY + delta[1]
b[0] = initialBounds.maxX + delta[0]
break
}
case TransformCorner.BottomRight: {
b[0] = initialBounds.maxX + delta[0]
b[1] = initialBounds.maxY + delta[1]
break
}
case TransformCorner.BottomLeft: {
a[0] = initialBounds.minX + delta[0]
b[1] = initialBounds.maxY + delta[1]
break
}
}
// Calculate new common (externior) bounding box
const newBounds = {
minX: Math.min(a[0], b[0]),
minY: Math.min(a[1], b[1]),
maxX: Math.max(a[0], b[0]),
maxY: Math.max(a[1], b[1]),
width: Math.abs(b[0] - a[0]),
height: Math.abs(b[1] - a[1]),
}
this.isFlippedX = b[0] < a[0]
this.isFlippedY = b[1] < a[1]
this.scaleX = newBoundingBox.scaleX
this.scaleY = newBoundingBox.scaleY
// Now work backward to calculate a new bounding box for each of the shapes.
selectedIds.forEach((id) => {
const { initialShape, initialShapeBounds } = shapeBounds[id]
const { nx, nmx, nw, ny, nmy, nh } = initialShapeBounds
const shape = shapes[id]
const minX =
newBounds.minX + (this.isFlippedX ? nmx : nx) * newBounds.width
const minY =
newBounds.minY + (this.isFlippedY ? nmy : ny) * newBounds.height
const width = nw * newBounds.width
const height = nh * newBounds.height
const newShapeBounds = getRelativeTransformedBoundingBox(
newBoundingBox,
initialBounds,
initialShapeBounds,
this.scaleX < 0,
this.scaleY < 0
)
const newShapeBounds = {
minX,
minY,
maxX: minX + width,
maxY: minY + height,
width,
height,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
}
// Pass the new data to the shape's transform utility for mutation.
// Most shapes should be able to transform using only the bounding box,
// however some shapes (e.g. those with internal points) will need more
// data here too.
const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transform(shape, newShapeBounds, {
type: this.transformType,
initialShape,
initialShapeBounds,
initialBounds,
boundsRotation,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle: false,
anchor: getTransformAnchor(
this.transformType,
this.isFlippedX,
this.isFlippedY
),
scaleX: this.scaleX,
scaleY: this.scaleY,
})
})
}
cancel(data: Data) {
const {
shapeBounds,
boundsRotation,
initialBounds,
currentPageId,
selectedIds,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId]
const { currentPageId, selectedIds, shapeBounds } = this.snapshot
selectedIds.forEach((id) => {
const shape = shapes[id]
const shape = data.document.pages[currentPageId].shapes[id]
const { initialShape, initialShapeBounds } = shapeBounds[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, {
type: this.transformType,
initialShape,
initialShapeBounds,
initialBounds,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor: getTransformAnchor(this.transformType, false, false),
scaleX: 1,
scaleY: 1,
})
})
}
@ -231,7 +90,8 @@ export default class TransformSession extends BaseSession {
data,
this.snapshot,
getTransformSnapshot(data, this.transformType),
getTransformAnchor(this.transformType, false, false)
this.scaleX,
this.scaleY
)
}
}
@ -244,7 +104,6 @@ export function getTransformSnapshot(
document: { pages },
selectedIds,
currentPageId,
boundsRotation,
} = current(data)
const pageShapes = pages[currentPageId].shapes
@ -263,27 +122,17 @@ export function getTransformSnapshot(
// Return a mapping of shapes to bounds together with the relative
// positions of the shape's bounds within the common bounds shape.
return {
currentPageId,
type: transformType,
initialBounds: bounds,
boundsRotation,
currentPageId,
selectedIds: new Set(selectedIds),
initialBounds: bounds,
shapeBounds: Object.fromEntries(
Array.from(selectedIds.values()).map((id) => {
const { minX, minY, width, height } = shapesBounds[id]
return [
id,
{
initialShape: pageShapes[id],
initialShapeBounds: {
...shapesBounds[id],
nx: (minX - bounds.minX) / bounds.width,
ny: (minY - bounds.minY) / bounds.height,
nmx: 1 - (minX + width - bounds.minX) / bounds.width,
nmy: 1 - (minY + height - bounds.minY) / bounds.height,
nw: width / bounds.width,
nh: height / bounds.height,
},
initialShapeBounds: shapesBounds[id],
},
]
})

View file

@ -13,17 +13,11 @@ import {
export default class TransformSingleSession extends BaseSession {
delta = [0, 0]
isFlippedX = false
isFlippedY = false
scaleX = 1
scaleY = 1
transformType: TransformEdge | TransformCorner
origin: number[]
center: number[]
snapshot: TransformSingleSnapshot
corners: {
a: number[]
b: number[]
}
rotatedCorners: number[][]
origin: number[]
constructor(
data: Data,
@ -33,168 +27,49 @@ export default class TransformSingleSession extends BaseSession {
super(data)
this.origin = point
this.transformType = transformType
this.snapshot = getTransformSingleSnapshot(data, transformType)
const { minX, minY, maxX, maxY } = this.snapshot.initialShapeBounds
this.center = [(minX + maxX) / 2, (minY + maxY) / 2]
this.corners = {
a: [minX, minY],
b: [maxX, maxY],
}
this.rotatedCorners = getRotatedCorners(
this.snapshot.initialShapeBounds,
this.snapshot.initialShape.rotation
)
}
update(data: Data, point: number[]) {
const {
corners: { a, b },
transformType,
} = this
const { transformType } = this
const {
boundsRotation,
initialShapeBounds,
currentPageId,
initialShape,
id,
} = this.snapshot
const { initialShapeBounds, currentPageId, initialShape, id } =
this.snapshot
const { shapes } = data.document.pages[currentPageId]
const shape = shapes[id]
const rotation = shape.rotation
// 1. Create a new bounding box.
// Counter rotate the delta and apply this to the original bounding box.
const delta = vec.vec(this.origin, point)
/*
Transforms
Corners a and b are the original top-left and bottom-right corners of the
bounding box. Depending on what the user is dragging, change one or both
points. To keep things smooth, calculate based by adding the delta (the
vector between the current point and its original point) to the original
bounding box values.
*/
const shape = data.document.pages[currentPageId].shapes[id]
const newBoundingBox = getTransformedBoundingBox(
initialShapeBounds,
transformType,
delta,
vec.vec(this.origin, point),
shape.rotation
)
// console.log(newBoundingBox)
switch (transformType) {
case TransformEdge.Top: {
a[1] = initialShapeBounds.minY + delta[1]
break
}
case TransformEdge.Right: {
b[0] = initialShapeBounds.maxX + delta[0]
break
}
case TransformEdge.Bottom: {
b[1] = initialShapeBounds.maxY + delta[1]
break
}
case TransformEdge.Left: {
a[0] = initialShapeBounds.minX + delta[0]
break
}
case TransformCorner.TopLeft: {
a[0] = initialShapeBounds.minX + delta[0]
a[1] = initialShapeBounds.minY + delta[1]
break
}
case TransformCorner.TopRight: {
a[1] = initialShapeBounds.minY + delta[1]
b[0] = initialShapeBounds.maxX + delta[0]
break
}
case TransformCorner.BottomRight: {
b[0] = initialShapeBounds.maxX + delta[0]
b[1] = initialShapeBounds.maxY + delta[1]
break
}
case TransformCorner.BottomLeft: {
a[0] = initialShapeBounds.minX + delta[0]
b[1] = initialShapeBounds.maxY + delta[1]
break
}
}
// Calculate new common (externior) bounding box
const newBounds = {
minX: Math.min(a[0], b[0]),
minY: Math.min(a[1], b[1]),
maxX: Math.max(a[0], b[0]),
maxY: Math.max(a[1], b[1]),
width: Math.abs(b[0] - a[0]),
height: Math.abs(b[1] - a[1]),
}
this.isFlippedX = b[0] < a[0]
this.isFlippedY = b[1] < a[1]
const anchor = this.transformType
// Pass the new data to the shape's transform utility for mutation.
// Most shapes should be able to transform using only the bounding box,
// however some shapes (e.g. those with internal points) will need more
// data here too.
this.scaleX = newBoundingBox.scaleX
this.scaleY = newBoundingBox.scaleY
getShapeUtils(shape).transformSingle(shape, newBoundingBox, {
type: this.transformType,
initialShape,
initialShapeBounds,
initialBounds: initialShapeBounds,
boundsRotation,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle: true,
anchor,
type: this.transformType,
scaleX: this.scaleX,
scaleY: this.scaleY,
})
}
cancel(data: Data) {
const {
id,
boundsRotation,
initialShape,
initialShapeBounds,
currentPageId,
isSingle,
} = this.snapshot
const { id, initialShape, initialShapeBounds, currentPageId } =
this.snapshot
const { shapes } = data.document.pages[currentPageId]
// selectedIds.forEach((id) => {
// const shape = shapes[id]
const shape = shapes[id]
// const { initialShape, initialShapeBounds } = shapeBounds[id]
// getShapeUtils(shape).transform(shape, initialShapeBounds, {
// type: this.transformType,
// initialShape,
// initialShapeBounds,
// initialBounds,
// boundsRotation,
// isFlippedX: false,
// isFlippedY: false,
// isSingle,
// anchor: getTransformAnchor(this.transformType, false, false),
// })
// })
getShapeUtils(shape).transform(shape, initialShapeBounds, {
initialShape,
type: this.transformType,
scaleX: this.scaleX,
scaleY: this.scaleY,
})
}
complete(data: Data) {
@ -202,7 +77,8 @@ export default class TransformSingleSession extends BaseSession {
data,
this.snapshot,
getTransformSingleSnapshot(data, this.transformType),
getTransformAnchor(this.transformType, false, false)
this.scaleX,
this.scaleY
)
}
}
@ -217,10 +93,8 @@ export function getTransformSingleSnapshot(
currentPageId,
} = current(data)
const pageShapes = pages[currentPageId].shapes
const id = Array.from(selectedIds)[0]
const shape = pageShapes[id]
const shape = pages[currentPageId].shapes[id]
const bounds = getShapeUtils(shape).getBounds(shape)
return {
@ -228,17 +102,7 @@ export function getTransformSingleSnapshot(
currentPageId,
type: transformType,
initialShape: shape,
initialShapeBounds: {
...bounds,
nx: 0,
ny: 0,
nmx: 1,
nmy: 1,
nw: 1,
nh: 1,
},
boundsRotation: shape.rotation,
isSingle: true,
initialShapeBounds: bounds,
}
}

View file

@ -541,7 +541,7 @@ const state = createState({
)
},
startDrawTransformSession(data, payload: PointerInfo) {
session = new Sessions.TransformSession(
session = new Sessions.TransformSingleSession(
data,
TransformCorner.BottomRight,
screenToWorld(payload.point, data)

View file

@ -915,6 +915,8 @@ export function getTransformAnchor(
anchor = TransformCorner.TopRight
} else if (isFlippedY) {
anchor = TransformCorner.BottomLeft
} else {
anchor = TransformCorner.BottomRight
}
break
}
@ -925,6 +927,8 @@ export function getTransformAnchor(
anchor = TransformCorner.TopLeft
} else if (isFlippedY) {
anchor = TransformCorner.BottomRight
} else {
anchor = TransformCorner.BottomLeft
}
break
}
@ -935,6 +939,8 @@ export function getTransformAnchor(
anchor = TransformCorner.BottomLeft
} else if (isFlippedY) {
anchor = TransformCorner.TopRight
} else {
anchor = TransformCorner.TopLeft
}
break
}
@ -945,6 +951,8 @@ export function getTransformAnchor(
anchor = TransformCorner.BottomRight
} else if (isFlippedY) {
anchor = TransformCorner.TopLeft
} else {
anchor = TransformCorner.TopRight
}
break
}
@ -1052,54 +1060,34 @@ export function getTransformedBoundingBox(
const [dx, dy] = vec.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 | TransformEdge
// the bounding box), use the delta to adjust the result's corners.
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 TransformEdge.Top:
case TransformCorner.TopLeft:
case TransformCorner.TopRight: {
anchor = TransformCorner.BottomLeft
bx1 += dx
by0 += dy
break
}
case TransformEdge.Bottom:
case TransformCorner.BottomLeft:
case TransformCorner.BottomRight: {
anchor = TransformCorner.TopLeft
bx1 += dx
by1 += dy
break
}
}
switch (handle) {
case TransformEdge.Left:
case TransformCorner.TopLeft:
case TransformCorner.BottomLeft: {
anchor = TransformCorner.TopRight
bx0 += dx
by1 += dy
break
}
case TransformEdge.Right:
case TransformCorner.TopRight:
case TransformCorner.BottomRight: {
bx1 += dx
break
}
}
@ -1115,35 +1103,39 @@ export function getTransformedBoundingBox(
const c0 = vec.med([ax0, ay0], [ax1, ay1])
const c1 = vec.med([bx0, by0], [bx1, by1])
switch (anchor) {
case TransformCorner.TopLeft: {
cv = vec.sub(
vec.rotWith([bx0, by0], c1, rotation),
vec.rotWith([ax0, ay0], c0, rotation)
)
break
}
case TransformCorner.TopRight: {
cv = vec.sub(
vec.rotWith([bx1, by0], c1, rotation),
vec.rotWith([ax1, ay0], c0, rotation)
)
break
}
case TransformCorner.BottomRight: {
switch (handle) {
case TransformCorner.TopLeft:
case TransformEdge.Top:
case TransformEdge.Left: {
cv = vec.sub(
vec.rotWith([bx1, by1], c1, rotation),
vec.rotWith([ax1, ay1], c0, rotation)
)
break
}
case TransformCorner.BottomLeft: {
case TransformCorner.TopRight: {
cv = vec.sub(
vec.rotWith([bx0, by1], c1, rotation),
vec.rotWith([ax0, ay1], c0, rotation)
)
break
}
case TransformCorner.BottomRight:
case TransformEdge.Bottom:
case TransformEdge.Right: {
cv = vec.sub(
vec.rotWith([bx0, by0], c1, rotation),
vec.rotWith([ax0, ay0], c0, rotation)
)
break
}
case TransformCorner.BottomLeft: {
cv = vec.sub(
vec.rotWith([bx1, by0], c1, rotation),
vec.rotWith([ax1, ay0], c0, rotation)
)
break
}
}
;[bx0, by0] = vec.sub([bx0, by0], cv)
@ -1153,6 +1145,9 @@ export function getTransformedBoundingBox(
// 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.
let scaleX = (bx1 - bx0) / (ax1 - ax0)
let scaleY = (by1 - by0) / (ay1 - ay0)
if (bx1 < bx0) {
;[bx1, bx0] = [bx0, bx1]
}
@ -1168,5 +1163,44 @@ export function getTransformedBoundingBox(
maxY: by1,
width: bx1 - bx0,
height: by1 - by0,
scaleX,
scaleY,
}
}
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,
}
}