Improves transforms
This commit is contained in:
parent
10b0c50294
commit
f57507a882
4 changed files with 154 additions and 26 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
150
utils/utils.ts
150
utils/utils.ts
|
@ -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]
|
||||||
|
|
Loading…
Reference in a new issue