Improves transforms

canvas-rendering
Steve Ruiz 2021-05-19 10:35:00 +01:00
rodzic da8f812090
commit c3740cacdd
29 zmienionych plików z 700 dodań i 72 usunięć

Wyświetl plik

@ -6,7 +6,7 @@ import styled from "styles"
export default function BoundsBg() {
const rBounds = useRef<SVGRectElement>(null)
const bounds = useSelector((state) => state.values.selectedBounds)
const isSelecting = useSelector((s) => s.isIn("selecting"))
const rotation = useSelector((s) => {
if (s.data.selectedIds.size === 1) {
const { shapes } = s.data.document.pages[s.data.currentPageId]
@ -18,6 +18,7 @@ export default function BoundsBg() {
})
if (!bounds) return null
if (!isSelecting) return null
const { minX, minY, width, height } = bounds

Wyświetl plik

@ -7,9 +7,9 @@ import { lerp } from "utils/utils"
export default function Bounds() {
const isBrushing = useSelector((s) => s.isIn("brushSelecting"))
const isSelecting = useSelector((s) => s.isIn("selecting"))
const zoom = useSelector((s) => s.data.camera.zoom)
const bounds = useSelector((s) => s.values.selectedBounds)
const rotation = useSelector((s) => {
if (s.data.selectedIds.size === 1) {
const { shapes } = s.data.document.pages[s.data.currentPageId]
@ -21,6 +21,7 @@ export default function Bounds() {
})
if (!bounds) return null
if (!isSelecting) return null
let { minX, minY, maxX, maxY, width, height } = bounds

Wyświetl plik

@ -18,7 +18,7 @@ export default class Circle extends CodeShape<CircleShape> {
rotation: 0,
radius: 20,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -17,7 +17,7 @@ export default class Dot extends CodeShape<DotShape> {
point: [0, 0],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -19,7 +19,7 @@ export default class Ellipse extends CodeShape<EllipseShape> {
radiusY: 20,
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -19,7 +19,7 @@ export default class Line extends CodeShape<LineShape> {
direction: [-0.5, 0.5],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -19,7 +19,7 @@ export default class Ray extends CodeShape<RayShape> {
direction: [0, 1],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -19,7 +19,7 @@ export default class Rectangle extends CodeShape<RectangleShape> {
size: [100, 100],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -22,7 +22,7 @@ const circle = createShape<CircleShape>({
rotation: 0,
radius: 20,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
},
...props,
@ -157,6 +157,10 @@ const circle = createShape<CircleShape>({
return shape
},
transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info)
},
canTransform: true,
})

Wyświetl plik

@ -22,7 +22,7 @@ const dot = createShape<DotShape>({
point: [0, 0],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
},
...props,
@ -89,6 +89,10 @@ const dot = createShape<DotShape>({
return shape
},
transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info)
},
canTransform: false,
})

Wyświetl plik

@ -5,7 +5,12 @@ import { createShape } from "./index"
import { boundsContained } from "utils/bounds"
import { intersectEllipseBounds } from "utils/intersections"
import { pointInEllipse } from "utils/hitTests"
import { translateBounds } from "utils/utils"
import {
getBoundsFromPoints,
getRotatedCorners,
rotateBounds,
translateBounds,
} from "utils/utils"
const ellipse = createShape<EllipseShape>({
boundsCache: new WeakMap([]),
@ -23,7 +28,7 @@ const ellipse = createShape<EllipseShape>({
radiusY: 20,
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
},
...props,
@ -56,7 +61,7 @@ const ellipse = createShape<EllipseShape>({
},
getRotatedBounds(shape) {
return this.getBounds(shape)
return getBoundsFromPoints(getRotatedCorners(shape))
},
getCenter(shape) {
@ -68,7 +73,8 @@ const ellipse = createShape<EllipseShape>({
point,
vec.add(shape.point, [shape.radiusX, shape.radiusY]),
shape.radiusX,
shape.radiusY
shape.radiusY,
shape.rotation
)
},
@ -83,7 +89,8 @@ const ellipse = createShape<EllipseShape>({
vec.add(shape.point, [shape.radiusX, shape.radiusY]),
shape.radiusX,
shape.radiusY,
brushBounds
brushBounds,
shape.rotation
).length > 0
)
},
@ -109,6 +116,10 @@ const ellipse = createShape<EllipseShape>({
return shape
},
transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info)
},
canTransform: true,
})

Wyświetl plik

@ -72,6 +72,23 @@ export interface ShapeUtility<K extends Shape> {
}
): K
transformSingle(
this: ShapeUtility<K>,
shape: K,
bounds: Bounds,
info: {
type: TransformEdge | TransformCorner
boundsRotation: number
initialShape: K
initialShapeBounds: BoundsSnapshot
initialBounds: Bounds
isFlippedX: boolean
isFlippedY: boolean
isSingle: boolean
anchor: TransformEdge | TransformCorner
}
): K
// Apply a scale to a shape.
scale(this: ShapeUtility<K>, shape: K, scale: number): K

Wyświetl plik

@ -22,7 +22,7 @@ const line = createShape<LineShape>({
direction: [0, 0],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
},
...props,
@ -97,6 +97,10 @@ const line = createShape<LineShape>({
return shape
},
transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info)
},
canTransform: false,
})

Wyświetl plik

@ -123,6 +123,10 @@ const polyline = createShape<PolylineShape>({
return shape
},
transformSingle(shape, bounds, info) {
return this.transform(shape, bounds, info)
},
canTransform: true,
})

Wyświetl plik

@ -22,7 +22,7 @@ const ray = createShape<RayShape>({
direction: [0, 1],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
strokeWidth: 1,
},

Wyświetl plik

@ -1,9 +1,19 @@
import { v4 as uuid } from "uuid"
import * as vec from "utils/vec"
import { RectangleShape, ShapeType } from "types"
import {
RectangleShape,
ShapeType,
TransformCorner,
TransformEdge,
} from "types"
import { createShape } from "./index"
import { boundsCollidePolygon, boundsContainPolygon } from "utils/bounds"
import { getBoundsFromPoints, rotateBounds, translateBounds } from "utils/utils"
import {
getBoundsFromPoints,
getRotatedCorners,
rotateBounds,
translateBounds,
} from "utils/utils"
const rectangle = createShape<RectangleShape>({
boundsCache: new WeakMap([]),
@ -20,7 +30,7 @@ const rectangle = createShape<RectangleShape>({
size: [1, 1],
rotation: 0,
style: {
fill: "rgba(142, 143, 142, 1.000)",
fill: "#c6cacb",
stroke: "#000",
},
...props,
@ -50,18 +60,9 @@ const rectangle = createShape<RectangleShape>({
},
getRotatedBounds(shape) {
const b = this.getBounds(shape)
const center = [b.minX + b.width / 2, b.minY + b.height / 2]
// Rotate corners of the shape, then find the minimum among those points.
const rotatedCorners = [
[b.minX, b.minY],
[b.maxX, b.minY],
[b.maxX, b.maxY],
[b.minX, b.maxY],
].map((point) => vec.rotWith(point, center, shape.rotation))
return getBoundsFromPoints(rotatedCorners)
return getBoundsFromPoints(
getRotatedCorners(this.getBounds(shape), shape.rotation)
)
},
getCenter(shape) {
@ -74,15 +75,10 @@ const rectangle = createShape<RectangleShape>({
},
hitTestBounds(shape, brushBounds) {
const b = this.getBounds(shape)
const center = [b.minX + b.width / 2, b.minY + b.height / 2]
const rotatedCorners = [
[b.minX, b.minY],
[b.maxX, b.minY],
[b.maxX, b.maxY],
[b.minX, b.maxY],
].map((point) => vec.rotWith(point, center, shape.rotation))
const rotatedCorners = getRotatedCorners(
this.getBounds(shape),
shape.rotation
)
return (
boundsContainPolygon(brushBounds, rotatedCorners) ||
@ -108,8 +104,6 @@ const rectangle = createShape<RectangleShape>({
shapeBounds,
{ initialShape, isSingle, initialShapeBounds, isFlippedX, isFlippedY }
) {
// TODO: Apply rotation to single-selection items
if (shape.rotation === 0 || isSingle) {
shape.size = [shapeBounds.width, shapeBounds.height]
shape.point = [shapeBounds.minX, shapeBounds.minY]
@ -145,6 +139,105 @@ const rectangle = createShape<RectangleShape>({
return shape
},
transformSingle(
shape,
bounds,
{ initialShape, initialShapeBounds, anchor, isFlippedY, isFlippedX }
) {
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
},
canTransform: true,
})

Wyświetl plik

@ -2,7 +2,7 @@ import Command from "./command"
import history from "../history"
import { Data, Shape } from "types"
export default function createShape(data: Data, shape: Shape) {
export default function createShapeCommand(data: Data, shape: Shape) {
const { currentPageId } = data
history.execute(

Wyświetl plik

@ -3,7 +3,7 @@ import history from "../history"
import { DirectionSnapshot } from "state/sessions/direction-session"
import { Data, LineShape, RayShape } from "types"
export default function translateCommand(
export default function directCommand(
data: Data,
before: DirectionSnapshot,
after: DirectionSnapshot

Wyświetl plik

@ -3,7 +3,7 @@ import history from "../history"
import { CodeControl, Data, Shape } from "types"
import { current } from "immer"
export default function setGeneratedShapes(
export default function generateCommand(
data: Data,
currentPageId: string,
generatedShapes: Shape[]

Wyświetl plik

@ -1,5 +1,6 @@
import translate from "./translate"
import transform from "./transform"
import transformSingle from "./transform-single"
import generate from "./generate"
import createShape from "./create-shape"
import direct from "./direct"
@ -8,6 +9,7 @@ import rotate from "./rotate"
const commands = {
translate,
transform,
transformSingle,
generate,
createShape,
direct,

Wyświetl plik

@ -3,7 +3,7 @@ import history from "../history"
import { Data } from "types"
import { RotateSnapshot } from "state/sessions/rotate-session"
export default function translateCommand(
export default function rotateCommand(
data: Data,
before: RotateSnapshot,
after: RotateSnapshot

Wyświetl plik

@ -0,0 +1,72 @@
import Command from "./command"
import history from "../history"
import { Data, TransformCorner, TransformEdge } from "types"
import { getShapeUtils } from "lib/shapes"
import { TransformSingleSnapshot } from "state/sessions/transform-single-session"
export default function transformSingleCommand(
data: Data,
before: TransformSingleSnapshot,
after: TransformSingleSnapshot,
anchor: TransformCorner | TransformEdge
) {
history.execute(
data,
new Command({
name: "translate_shapes",
category: "canvas",
do(data) {
const {
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,
})
},
undo(data) {
const {
type,
initialShape,
initialShapeBounds,
currentPageId,
id,
boundsRotation,
} = before
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,
})
},
})
)
}

Wyświetl plik

@ -4,7 +4,7 @@ import { Data, TransformCorner, TransformEdge } from "types"
import { TransformSnapshot } from "state/sessions/transform-session"
import { getShapeUtils } from "lib/shapes"
export default function translateCommand(
export default function transformCommand(
data: Data,
before: TransformSnapshot,
after: TransformSnapshot,
@ -22,7 +22,6 @@ export default function translateCommand(
initialBounds,
currentPageId,
selectedIds,
isSingle,
boundsRotation,
} = after
@ -40,7 +39,7 @@ export default function translateCommand(
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle,
isSingle: false,
anchor,
})
})
@ -52,7 +51,6 @@ export default function translateCommand(
initialBounds,
currentPageId,
selectedIds,
isSingle,
boundsRotation,
} = before
@ -70,7 +68,7 @@ export default function translateCommand(
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle,
isSingle: false,
anchor: type,
})
})

Wyświetl plik

@ -17,7 +17,7 @@ export const defaultDocument: Data["document"] = {
direction: [0.5, 0.5],
style: {
fill: "#AAA",
stroke: "rgba(142, 143, 142, 1.000)",
stroke: "#c6cacb",
strokeWidth: 1,
},
}),
@ -28,7 +28,7 @@ export const defaultDocument: Data["document"] = {
// point: [400, 500],
// style: {
// fill: "#AAA",
// stroke: "rgba(142, 143, 142, 1.000)",
// stroke: "#c6cacb",
// strokeWidth: 1,
// },
// }),
@ -40,7 +40,7 @@ export const defaultDocument: Data["document"] = {
radius: 50,
style: {
fill: "#AAA",
stroke: "rgba(142, 143, 142, 1.000)",
stroke: "#c6cacb",
strokeWidth: 1,
},
}),
@ -53,7 +53,7 @@ export const defaultDocument: Data["document"] = {
radiusY: 30,
style: {
fill: "#AAA",
stroke: "rgba(142, 143, 142, 1.000)",
stroke: "#c6cacb",
strokeWidth: 1,
},
}),
@ -66,7 +66,7 @@ export const defaultDocument: Data["document"] = {
// radiusY: 30,
// style: {
// fill: "#AAA",
// stroke: "rgba(142, 143, 142, 1.000)",
// stroke: "#c6cacb",
// strokeWidth: 1,
// },
// }),
@ -82,7 +82,7 @@ export const defaultDocument: Data["document"] = {
// ],
// style: {
// fill: "none",
// stroke: "rgba(142, 143, 142, 1.000)",
// stroke: "#c6cacb",
// strokeWidth: 2,
// strokeLinecap: "round",
// strokeLinejoin: "round",
@ -96,7 +96,7 @@ export const defaultDocument: Data["document"] = {
size: [200, 200],
style: {
fill: "#AAA",
stroke: "rgba(142, 143, 142, 1.000)",
stroke: "#c6cacb",
strokeWidth: 1,
},
}),
@ -108,7 +108,7 @@ export const defaultDocument: Data["document"] = {
// direction: [0.2, 0.2],
// style: {
// fill: "#AAA",
// stroke: "rgba(142, 143, 142, 1.000)",
// stroke: "#c6cacb",
// strokeWidth: 1,
// },
// }),

Wyświetl plik

@ -2,6 +2,7 @@ import BaseSession from "./base-session"
import BrushSession from "./brush-session"
import TranslateSession from "./translate-session"
import TransformSession from "./transform-session"
import TransformSingleSession from "./transform-single-session"
import DirectionSession from "./direction-session"
import RotateSession from "./rotate-session"
@ -10,6 +11,7 @@ export {
BaseSession,
TranslateSession,
TransformSession,
TransformSingleSession,
DirectionSession,
RotateSession,
}

Wyświetl plik

@ -66,12 +66,25 @@ export default class TransformSession extends BaseSession {
initialBounds,
currentPageId,
selectedIds,
isSingle,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId]
const delta = vec.vec(this.origin, point)
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
@ -173,7 +186,7 @@ export default class TransformSession extends BaseSession {
boundsRotation,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle,
isSingle: false,
anchor: getTransformAnchor(
this.transformType,
this.isFlippedX,
@ -190,7 +203,6 @@ export default class TransformSession extends BaseSession {
initialBounds,
currentPageId,
selectedIds,
isSingle,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId]
@ -208,7 +220,7 @@ export default class TransformSession extends BaseSession {
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle,
isSingle: false,
anchor: getTransformAnchor(this.transformType, false, false),
})
})
@ -255,7 +267,6 @@ export function getTransformSnapshot(
type: transformType,
initialBounds: bounds,
boundsRotation,
isSingle: selectedIds.size === 1,
selectedIds: new Set(selectedIds),
shapeBounds: Object.fromEntries(
Array.from(selectedIds.values()).map((id) => {

Wyświetl plik

@ -0,0 +1,247 @@
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 {
getTransformedBoundingBox,
getCommonBounds,
getRotatedCorners,
getTransformAnchor,
} from "utils/utils"
export default class TransformSingleSession extends BaseSession {
delta = [0, 0]
isFlippedX = false
isFlippedY = false
transformType: TransformEdge | TransformCorner
origin: number[]
center: number[]
snapshot: TransformSingleSnapshot
corners: {
a: number[]
b: number[]
}
rotatedCorners: number[][]
constructor(
data: Data,
transformType: TransformCorner | TransformEdge,
point: number[]
) {
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 {
boundsRotation,
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 newBoundingBox = getTransformedBoundingBox(
initialShapeBounds,
transformType,
delta,
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.
getShapeUtils(shape).transformSingle(shape, newBoundingBox, {
type: this.transformType,
initialShape,
initialShapeBounds,
initialBounds: initialShapeBounds,
boundsRotation,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle: true,
anchor,
})
}
cancel(data: Data) {
const {
id,
boundsRotation,
initialShape,
initialShapeBounds,
currentPageId,
isSingle,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId]
// selectedIds.forEach((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),
// })
// })
}
complete(data: Data) {
commands.transformSingle(
data,
this.snapshot,
getTransformSingleSnapshot(data, this.transformType),
getTransformAnchor(this.transformType, false, false)
)
}
}
export function getTransformSingleSnapshot(
data: Data,
transformType: TransformEdge | TransformCorner
) {
const {
document: { pages },
selectedIds,
currentPageId,
} = current(data)
const pageShapes = pages[currentPageId].shapes
const id = Array.from(selectedIds)[0]
const shape = pageShapes[id]
const bounds = getShapeUtils(shape).getBounds(shape)
return {
id,
currentPageId,
type: transformType,
initialShape: shape,
initialShapeBounds: {
...bounds,
nx: 0,
ny: 0,
nmx: 1,
nmy: 1,
nw: 1,
nh: 1,
},
boundsRotation: shape.rotation,
isSingle: true,
}
}
export type TransformSingleSnapshot = ReturnType<
typeof getTransformSingleSnapshot
>

Wyświetl plik

@ -527,11 +527,18 @@ const state = createState({
data,
payload: PointerInfo & { target: TransformCorner | TransformEdge }
) {
session = new Sessions.TransformSession(
data,
payload.target,
screenToWorld(payload.point, data)
)
session =
data.selectedIds.size === 1
? new Sessions.TransformSingleSession(
data,
payload.target,
screenToWorld(payload.point, data)
)
: new Sessions.TransformSession(
data,
payload.target,
screenToWorld(payload.point, data)
)
},
startDrawTransformSession(data, payload: PointerInfo) {
session = new Sessions.TransformSession(

Wyświetl plik

@ -1,6 +1,7 @@
import Vector from "lib/code/vector"
import { getShapeUtils } from "lib/shapes"
import React from "react"
import { Data, Bounds, TransformEdge, TransformCorner } from "types"
import { Data, Bounds, TransformEdge, TransformCorner, Shape } from "types"
import * as svg from "./svg"
import * as vec from "./vec"
@ -1020,3 +1021,152 @@ export function rotateBounds(
height: bounds.height,
}
}
export function getRotatedCorners(b: Bounds, rotation: number) {
const center = [b.minX + b.width / 2, b.minY + b.height / 2]
return [
[b.minX, b.minY],
[b.maxX, b.minY],
[b.maxX, b.maxY],
[b.minX, b.maxY],
].map((point) => vec.rotWith(point, center, rotation))
}
export function getTransformedBoundingBox(
bounds: Bounds,
handle: TransformCorner | TransformEdge,
delta: number[],
rotation = 0
) {
// 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] = 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
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 = 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: {
cv = vec.sub(
vec.rotWith([bx1, by1], c1, rotation),
vec.rotWith([ax1, ay1], c0, rotation)
)
break
}
case TransformCorner.BottomLeft: {
cv = vec.sub(
vec.rotWith([bx0, by1], c1, rotation),
vec.rotWith([ax0, ay1], c0, rotation)
)
break
}
}
;[bx0, by0] = vec.sub([bx0, by0], 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.
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,
}
}