Browse Source

Improves transforms

main
Steve Ruiz 4 years ago
parent
commit
f57507a882
4 changed files with 154 additions and 26 deletions
  1. 3
    2
      state/sessions/transform-session.ts
  2. 3
    2
      state/sessions/transform-single-session.ts
  3. 18
    2
      state/state.ts
  4. 130
    20
      utils/utils.ts

+ 3
- 2
state/sessions/transform-session.ts View File

@@ -28,7 +28,7 @@ export default class TransformSession extends BaseSession {
28 28
     this.snapshot = getTransformSnapshot(data, transformType)
29 29
   }
30 30
 
31
-  update(data: Data, point: number[]) {
31
+  update(data: Data, point: number[], isAspectRatioLocked = false) {
32 32
     const { transformType } = this
33 33
 
34 34
     const { currentPageId, selectedIds, shapeBounds, initialBounds } =
@@ -38,7 +38,8 @@ export default class TransformSession extends BaseSession {
38 38
       initialBounds,
39 39
       transformType,
40 40
       vec.vec(this.origin, point),
41
-      data.boundsRotation
41
+      data.boundsRotation,
42
+      isAspectRatioLocked
42 43
     )
43 44
 
44 45
     this.scaleX = newBoundingBox.scaleX

+ 3
- 2
state/sessions/transform-single-session.ts View File

@@ -32,7 +32,7 @@ export default class TransformSingleSession extends BaseSession {
32 32
     this.isCreating = isCreating
33 33
   }
34 34
 
35
-  update(data: Data, point: number[]) {
35
+  update(data: Data, point: number[], isAspectRatioLocked = false) {
36 36
     const { transformType } = this
37 37
 
38 38
     const { initialShapeBounds, currentPageId, initialShape, id } =
@@ -44,7 +44,8 @@ export default class TransformSingleSession extends BaseSession {
44 44
       initialShapeBounds,
45 45
       transformType,
46 46
       vec.vec(this.origin, point),
47
-      shape.rotation
47
+      shape.rotation,
48
+      isAspectRatioLocked
48 49
     )
49 50
 
50 51
     this.scaleX = newBoundingBox.scaleX

+ 18
- 2
state/state.ts View File

@@ -168,6 +168,8 @@ const state = createState({
168 168
               on: {
169 169
                 MOVED_POINTER: "updateTransformSession",
170 170
                 PANNED_CAMERA: "updateTransformSession",
171
+                PRESSED_SHIFT_KEY: "keyUpdateTransformSession",
172
+                RELEASED_SHIFT_KEY: "keyUpdateTransformSession",
171 173
                 STOPPED_POINTING: { do: "completeSession", to: "selecting" },
172 174
                 CANCELLED: { do: "cancelSession", to: "selecting" },
173 175
               },
@@ -569,7 +571,8 @@ const state = createState({
569 571
           ? new Sessions.TransformSingleSession(
570 572
               data,
571 573
               payload.target,
572
-              screenToWorld(payload.point, data)
574
+              screenToWorld(payload.point, data),
575
+              false
573 576
             )
574 577
           : new Sessions.TransformSession(
575 578
               data,
@@ -585,8 +588,21 @@ const state = createState({
585 588
         true
586 589
       )
587 590
     },
591
+    keyUpdateTransformSession(data, payload: PointerInfo) {
592
+      session.update(
593
+        data,
594
+        screenToWorld(inputs.pointer.point, data),
595
+        payload.shiftKey,
596
+        payload.altKey
597
+      )
598
+    },
588 599
     updateTransformSession(data, payload: PointerInfo) {
589
-      session.update(data, screenToWorld(payload.point, data))
600
+      session.update(
601
+        data,
602
+        screenToWorld(payload.point, data),
603
+        payload.shiftKey,
604
+        payload.altKey
605
+      )
590 606
     },
591 607
 
592 608
     // Direction

+ 130
- 20
utils/utils.ts View File

@@ -1043,25 +1043,44 @@ export function getRotatedCorners(b: Bounds, rotation: number) {
1043 1043
 
1044 1044
 export function getTransformedBoundingBox(
1045 1045
   bounds: Bounds,
1046
-  handle: TransformCorner | TransformEdge,
1046
+  handle: TransformCorner | TransformEdge | "center",
1047 1047
   delta: number[],
1048
-  rotation = 0
1048
+  rotation = 0,
1049
+  isAspectRatioLocked = false
1049 1050
 ) {
1050 1051
   // Create top left and bottom right corners.
1051 1052
   let [ax0, ay0] = [bounds.minX, bounds.minY]
1052 1053
   let [ax1, ay1] = [bounds.maxX, bounds.maxY]
1053 1054
 
1054
-  // Create a second set of corners for the result.
1055
+  // Create a second set of corners for the new box.
1055 1056
   let [bx0, by0] = [bounds.minX, bounds.minY]
1056 1057
   let [bx1, by1] = [bounds.maxX, bounds.maxY]
1057 1058
 
1059
+  // If the drag is on the center, just translate the bounds.
1060
+  if (handle === "center") {
1061
+    return {
1062
+      minX: bx0 + delta[0],
1063
+      minY: by0 + delta[1],
1064
+      maxX: bx1 + delta[0],
1065
+      maxY: by1 + delta[1],
1066
+      width: bx1 - bx0,
1067
+      height: by1 - by0,
1068
+      scaleX: 1,
1069
+      scaleY: 1,
1070
+    }
1071
+  }
1072
+
1058 1073
   // Counter rotate the delta. This lets us make changes as if
1059 1074
   // the (possibly rotated) boxes were axis aligned.
1060
-  const [dx, dy] = vec.rot(delta, -rotation)
1075
+  let [dx, dy] = vec.rot(delta, -rotation)
1061 1076
 
1062
-  // Depending on the dragging handle (an edge or corner of
1063
-  // the bounding box), use the delta to adjust the result's corners.
1077
+  /*
1078
+  1. Delta
1064 1079
 
1080
+  Use the delta to adjust the new box by changing its corners.
1081
+  The dragging handle (corner or edge) will determine which 
1082
+  corners should change.
1083
+  */
1065 1084
   switch (handle) {
1066 1085
     case TransformEdge.Top:
1067 1086
     case TransformCorner.TopLeft:
@@ -1092,10 +1111,76 @@ export function getTransformedBoundingBox(
1092 1111
     }
1093 1112
   }
1094 1113
 
1095
-  // If the bounds are rotated, get a vector from the rotated anchor
1096
-  // corner in the inital bounds to the rotated anchor corner in the
1097
-  // result's bounds. Subtract this vector from the result's corners,
1098
-  // so that the two anchor points (initial and result) will be equal.
1114
+  const aw = ax1 - ax0
1115
+  const ah = ay1 - ay0
1116
+
1117
+  const scaleX = (bx1 - bx0) / aw
1118
+  const scaleY = (by1 - by0) / ah
1119
+
1120
+  const bw = Math.abs(bx1 - bx0)
1121
+  const bh = Math.abs(by1 - by0)
1122
+
1123
+  /*
1124
+  2. Aspect ratio
1125
+
1126
+  If the aspect ratio is locked, adjust the corners so that the
1127
+  new box's aspect ratio matches the original aspect ratio.
1128
+  */
1129
+
1130
+  if (isAspectRatioLocked) {
1131
+    const ar = aw / ah
1132
+    const isTall = ar < bw / bh
1133
+    const tw = bw * (scaleY < 0 ? 1 : -1) * (1 / ar)
1134
+    const th = bh * (scaleX < 0 ? 1 : -1) * ar
1135
+
1136
+    switch (handle) {
1137
+      case TransformCorner.TopLeft: {
1138
+        if (isTall) by0 = by1 + tw
1139
+        else bx0 = bx1 + th
1140
+        break
1141
+      }
1142
+      case TransformCorner.TopRight: {
1143
+        if (isTall) by0 = by1 + tw
1144
+        else bx1 = bx0 - th
1145
+        break
1146
+      }
1147
+      case TransformCorner.BottomRight: {
1148
+        if (isTall) by1 = by0 - tw
1149
+        else bx1 = bx0 - th
1150
+        break
1151
+      }
1152
+      case TransformCorner.BottomLeft: {
1153
+        if (isTall) by1 = by0 - tw
1154
+        else bx0 = bx1 + th
1155
+        break
1156
+      }
1157
+      case TransformEdge.Bottom:
1158
+      case TransformEdge.Top: {
1159
+        const m = (bx0 + bx1) / 2
1160
+        const w = bh * ar
1161
+        bx0 = m - w / 2
1162
+        bx1 = m + w / 2
1163
+        break
1164
+      }
1165
+      case TransformEdge.Left:
1166
+      case TransformEdge.Right: {
1167
+        const m = (by0 + by1) / 2
1168
+        const h = bw / ar
1169
+        by0 = m - h / 2
1170
+        by1 = m + h / 2
1171
+        break
1172
+      }
1173
+    }
1174
+  }
1175
+
1176
+  /*
1177
+  3. Rotation
1178
+
1179
+  If the bounds are rotated, get a vector from the rotated anchor
1180
+  corner in the inital bounds to the rotated anchor corner in the
1181
+  result's bounds. Subtract this vector from the result's corners,
1182
+  so that the two anchor points (initial and result) will be equal.
1183
+  */
1099 1184
 
1100 1185
   if (rotation % (Math.PI * 2) !== 0) {
1101 1186
     let cv = [0, 0]
@@ -1104,9 +1189,7 @@ export function getTransformedBoundingBox(
1104 1189
     const c1 = vec.med([bx0, by0], [bx1, by1])
1105 1190
 
1106 1191
     switch (handle) {
1107
-      case TransformCorner.TopLeft:
1108
-      case TransformEdge.Top:
1109
-      case TransformEdge.Left: {
1192
+      case TransformCorner.TopLeft: {
1110 1193
         cv = vec.sub(
1111 1194
           vec.rotWith([bx1, by1], c1, rotation),
1112 1195
           vec.rotWith([ax1, ay1], c0, rotation)
@@ -1120,9 +1203,7 @@ export function getTransformedBoundingBox(
1120 1203
         )
1121 1204
         break
1122 1205
       }
1123
-      case TransformCorner.BottomRight:
1124
-      case TransformEdge.Bottom:
1125
-      case TransformEdge.Right: {
1206
+      case TransformCorner.BottomRight: {
1126 1207
         cv = vec.sub(
1127 1208
           vec.rotWith([bx0, by0], c1, rotation),
1128 1209
           vec.rotWith([ax0, ay0], c0, rotation)
@@ -1136,17 +1217,46 @@ export function getTransformedBoundingBox(
1136 1217
         )
1137 1218
         break
1138 1219
       }
1220
+      case TransformEdge.Top: {
1221
+        cv = vec.sub(
1222
+          vec.rotWith(vec.med([bx0, by1], [bx1, by1]), c1, rotation),
1223
+          vec.rotWith(vec.med([ax0, ay1], [ax1, ay1]), c0, rotation)
1224
+        )
1225
+        break
1226
+      }
1227
+      case TransformEdge.Left: {
1228
+        cv = vec.sub(
1229
+          vec.rotWith(vec.med([bx1, by0], [bx1, by1]), c1, rotation),
1230
+          vec.rotWith(vec.med([ax1, ay0], [ax1, ay1]), c0, rotation)
1231
+        )
1232
+        break
1233
+      }
1234
+      case TransformEdge.Bottom: {
1235
+        cv = vec.sub(
1236
+          vec.rotWith(vec.med([bx0, by0], [bx1, by0]), c1, rotation),
1237
+          vec.rotWith(vec.med([ax0, ay0], [ax1, ay0]), c0, rotation)
1238
+        )
1239
+        break
1240
+      }
1241
+      case TransformEdge.Right: {
1242
+        cv = vec.sub(
1243
+          vec.rotWith(vec.med([bx0, by0], [bx0, by1]), c1, rotation),
1244
+          vec.rotWith(vec.med([ax0, ay0], [ax0, ay1]), c0, rotation)
1245
+        )
1246
+        break
1247
+      }
1139 1248
     }
1140 1249
 
1141 1250
     ;[bx0, by0] = vec.sub([bx0, by0], cv)
1142 1251
     ;[bx1, by1] = vec.sub([bx1, by1], cv)
1143 1252
   }
1144 1253
 
1145
-  // If the axes are flipped (e.g. if the right edge has been dragged
1146
-  // left past the initial left edge) then swap points on that axis.
1254
+  /*
1255
+  4. Flips
1147 1256
 
1148
-  let scaleX = (bx1 - bx0) / (ax1 - ax0)
1149
-  let scaleY = (by1 - by0) / (ay1 - ay0)
1257
+  If the axes are flipped (e.g. if the right edge has been dragged
1258
+  left past the initial left edge) then swap points on that axis.
1259
+  */
1150 1260
 
1151 1261
   if (bx1 < bx0) {
1152 1262
     ;[bx1, bx0] = [bx0, bx1]

Loading…
Cancel
Save