Improves transforms

This commit is contained in:
Steve Ruiz 2021-05-21 08:42:56 +01:00
parent 10b0c50294
commit f57507a882
4 changed files with 154 additions and 26 deletions

View file

@ -28,7 +28,7 @@ export default class TransformSession extends BaseSession {
this.snapshot = getTransformSnapshot(data, transformType) this.snapshot = getTransformSnapshot(data, transformType)
} }
update(data: Data, point: number[]) { update(data: Data, point: number[], isAspectRatioLocked = false) {
const { transformType } = this const { transformType } = this
const { currentPageId, selectedIds, shapeBounds, initialBounds } = const { currentPageId, selectedIds, shapeBounds, initialBounds } =
@ -38,7 +38,8 @@ export default class TransformSession extends BaseSession {
initialBounds, initialBounds,
transformType, transformType,
vec.vec(this.origin, point), vec.vec(this.origin, point),
data.boundsRotation data.boundsRotation,
isAspectRatioLocked
) )
this.scaleX = newBoundingBox.scaleX this.scaleX = newBoundingBox.scaleX

View file

@ -32,7 +32,7 @@ export default class TransformSingleSession extends BaseSession {
this.isCreating = isCreating this.isCreating = isCreating
} }
update(data: Data, point: number[]) { update(data: Data, point: number[], isAspectRatioLocked = false) {
const { transformType } = this const { transformType } = this
const { initialShapeBounds, currentPageId, initialShape, id } = const { initialShapeBounds, currentPageId, initialShape, id } =
@ -44,7 +44,8 @@ export default class TransformSingleSession extends BaseSession {
initialShapeBounds, initialShapeBounds,
transformType, transformType,
vec.vec(this.origin, point), vec.vec(this.origin, point),
shape.rotation shape.rotation,
isAspectRatioLocked
) )
this.scaleX = newBoundingBox.scaleX this.scaleX = newBoundingBox.scaleX

View file

@ -168,6 +168,8 @@ const state = createState({
on: { on: {
MOVED_POINTER: "updateTransformSession", MOVED_POINTER: "updateTransformSession",
PANNED_CAMERA: "updateTransformSession", PANNED_CAMERA: "updateTransformSession",
PRESSED_SHIFT_KEY: "keyUpdateTransformSession",
RELEASED_SHIFT_KEY: "keyUpdateTransformSession",
STOPPED_POINTING: { do: "completeSession", to: "selecting" }, STOPPED_POINTING: { do: "completeSession", to: "selecting" },
CANCELLED: { do: "cancelSession", to: "selecting" }, CANCELLED: { do: "cancelSession", to: "selecting" },
}, },
@ -569,7 +571,8 @@ const state = createState({
? new Sessions.TransformSingleSession( ? new Sessions.TransformSingleSession(
data, data,
payload.target, payload.target,
screenToWorld(payload.point, data) screenToWorld(payload.point, data),
false
) )
: new Sessions.TransformSession( : new Sessions.TransformSession(
data, data,
@ -585,8 +588,21 @@ const state = createState({
true true
) )
}, },
keyUpdateTransformSession(data, payload: PointerInfo) {
session.update(
data,
screenToWorld(inputs.pointer.point, data),
payload.shiftKey,
payload.altKey
)
},
updateTransformSession(data, payload: PointerInfo) { updateTransformSession(data, payload: PointerInfo) {
session.update(data, screenToWorld(payload.point, data)) session.update(
data,
screenToWorld(payload.point, data),
payload.shiftKey,
payload.altKey
)
}, },
// Direction // Direction

View file

@ -1043,25 +1043,44 @@ export function getRotatedCorners(b: Bounds, rotation: number) {
export function getTransformedBoundingBox( export function getTransformedBoundingBox(
bounds: Bounds, bounds: Bounds,
handle: TransformCorner | TransformEdge, handle: TransformCorner | TransformEdge | "center",
delta: number[], delta: number[],
rotation = 0 rotation = 0,
isAspectRatioLocked = false
) { ) {
// Create top left and bottom right corners. // Create top left and bottom right corners.
let [ax0, ay0] = [bounds.minX, bounds.minY] let [ax0, ay0] = [bounds.minX, bounds.minY]
let [ax1, ay1] = [bounds.maxX, bounds.maxY] let [ax1, ay1] = [bounds.maxX, bounds.maxY]
// Create a second set of corners for the result. // Create a second set of corners for the new box.
let [bx0, by0] = [bounds.minX, bounds.minY] let [bx0, by0] = [bounds.minX, bounds.minY]
let [bx1, by1] = [bounds.maxX, bounds.maxY] 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 // Counter rotate the delta. This lets us make changes as if
// the (possibly rotated) boxes were axis aligned. // the (possibly rotated) boxes were axis aligned.
const [dx, dy] = vec.rot(delta, -rotation) let [dx, dy] = vec.rot(delta, -rotation)
// Depending on the dragging handle (an edge or corner of /*
// the bounding box), use the delta to adjust the result's corners. 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) { switch (handle) {
case TransformEdge.Top: case TransformEdge.Top:
case TransformCorner.TopLeft: case TransformCorner.TopLeft:
@ -1092,10 +1111,76 @@ export function getTransformedBoundingBox(
} }
} }
// If the bounds are rotated, get a vector from the rotated anchor const aw = ax1 - ax0
// corner in the inital bounds to the rotated anchor corner in the const ah = ay1 - ay0
// result's bounds. Subtract this vector from the result's corners,
// so that the two anchor points (initial and result) will be equal. const scaleX = (bx1 - bx0) / aw
const scaleY = (by1 - by0) / ah
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 TransformCorner.TopLeft: {
if (isTall) by0 = by1 + tw
else bx0 = bx1 + th
break
}
case TransformCorner.TopRight: {
if (isTall) by0 = by1 + tw
else bx1 = bx0 - th
break
}
case TransformCorner.BottomRight: {
if (isTall) by1 = by0 - tw
else bx1 = bx0 - th
break
}
case TransformCorner.BottomLeft: {
if (isTall) by1 = by0 - tw
else bx0 = bx1 + th
break
}
case TransformEdge.Bottom:
case TransformEdge.Top: {
const m = (bx0 + bx1) / 2
const w = bh * ar
bx0 = m - w / 2
bx1 = m + w / 2
break
}
case TransformEdge.Left:
case TransformEdge.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) { if (rotation % (Math.PI * 2) !== 0) {
let cv = [0, 0] let cv = [0, 0]
@ -1104,9 +1189,7 @@ export function getTransformedBoundingBox(
const c1 = vec.med([bx0, by0], [bx1, by1]) const c1 = vec.med([bx0, by0], [bx1, by1])
switch (handle) { switch (handle) {
case TransformCorner.TopLeft: case TransformCorner.TopLeft: {
case TransformEdge.Top:
case TransformEdge.Left: {
cv = vec.sub( cv = vec.sub(
vec.rotWith([bx1, by1], c1, rotation), vec.rotWith([bx1, by1], c1, rotation),
vec.rotWith([ax1, ay1], c0, rotation) vec.rotWith([ax1, ay1], c0, rotation)
@ -1120,9 +1203,7 @@ export function getTransformedBoundingBox(
) )
break break
} }
case TransformCorner.BottomRight: case TransformCorner.BottomRight: {
case TransformEdge.Bottom:
case TransformEdge.Right: {
cv = vec.sub( cv = vec.sub(
vec.rotWith([bx0, by0], c1, rotation), vec.rotWith([bx0, by0], c1, rotation),
vec.rotWith([ax0, ay0], c0, rotation) vec.rotWith([ax0, ay0], c0, rotation)
@ -1136,17 +1217,46 @@ export function getTransformedBoundingBox(
) )
break break
} }
case TransformEdge.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 TransformEdge.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 TransformEdge.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 TransformEdge.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) ;[bx0, by0] = vec.sub([bx0, by0], cv)
;[bx1, by1] = vec.sub([bx1, by1], cv) ;[bx1, by1] = vec.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. 4. Flips
let scaleX = (bx1 - bx0) / (ax1 - ax0) If the axes are flipped (e.g. if the right edge has been dragged
let scaleY = (by1 - by0) / (ay1 - ay0) left past the initial left edge) then swap points on that axis.
*/
if (bx1 < bx0) { if (bx1 < bx0) {
;[bx1, bx0] = [bx0, bx1] ;[bx1, bx0] = [bx0, bx1]