Skip to content

Commit

Permalink
fix: Discard shape snapping when the guideline is invalid for the res…
Browse files Browse the repository at this point in the history
…izing

- It's possible that the guideline and the resizing direction have no intersection
  • Loading branch information
miyanokomiya committed Nov 13, 2024
1 parent 5bb5048 commit 7798f05
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 23 deletions.
43 changes: 35 additions & 8 deletions src/composables/boundingBox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,19 +321,46 @@ describe("newBoundingBoxResizing", () => {
],
{ keepAspect: true },
);
expect(affine0[0][0]).toBeCloseTo(1.3);
expect(affine0[0][1]).toBeCloseTo(0);
expect(affine0[0][2]).toBeCloseTo(0);
expect(affine0[0][3]).toBeCloseTo(1.3);
expect(affine0[0][4]).toBeCloseTo(0);
expect(affine0[0][5]).toBeCloseTo(0);
expect(affine0[1]).toBeCloseTo(getDistance({ x: 110, y: 50 }, applyAffine(affine0[0], { x: 100, y: 50 })));
expect(affine0[2]).toEqual([
expect(affine0?.[0][0]).toBeCloseTo(1.3);
expect(affine0?.[0][1]).toBeCloseTo(0);
expect(affine0?.[0][2]).toBeCloseTo(0);
expect(affine0?.[0][3]).toBeCloseTo(1.3);
expect(affine0?.[0][4]).toBeCloseTo(0);
expect(affine0?.[0][5]).toBeCloseTo(0);
expect(affine0?.[1]).toBeCloseTo(getDistance({ x: 110, y: 50 }, applyAffine(affine0![0], { x: 100, y: 50 })));
expect(affine0?.[2]).toEqual([
{ x: 130, y: 0 },
{ x: 130, y: 100 },
]);
});
});

describe("getAffineAfterSnapping", () => {
test("should return undefined when there's no valid snapped segment", () => {
const corner0 = newBoundingBoxResizing({
rotation: Math.PI / 4,
hitResult: { type: "segment", index: 1 },
resizingBase: {
direction: { x: 100, y: 0 },
origin: { x: 0, y: 0 },
},
});
const affine0 = corner0.getAffineAfterSnapping(
{ x: 10, y: 0 },
[
[
{ x: 100, y: 50 },
{ x: 110, y: 50 },
],
],
[
{ x: 0, y: 51 },
{ x: 130, y: 51 },
],
);
expect(affine0).toBe(undefined);
});
});
});

describe("newBoundingBoxRotating", () => {
Expand Down
13 changes: 7 additions & 6 deletions src/composables/boundingBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ export function newBoundingBoxResizing(option: BoundingBoxResizingOption) {

function getAffineAfterSnapping(
diff: IVec2,
movingPointInfoList: [src: IVec2, snapped: IVec2][],
movingPointInfoList: [src: IVec2, resizedP: IVec2][],
snappedSegment: ISegment,
modifire?: { keepAspect?: boolean; centralize?: boolean },
): [affine: AffineMatrix, d: number, exactTarget?: ISegment] {
): [affine: AffineMatrix, d: number, exactTarget?: ISegment] | undefined {
const keepAspect = keepAspectForce || modifire?.keepAspect;
const centralize = modifire?.centralize;

Expand All @@ -390,7 +390,7 @@ export function newBoundingBoxResizing(option: BoundingBoxResizingOption) {
let rate: number | undefined;
let distance: number | undefined;
let movingPointInfo: [IVec2, IVec2] | undefined;
movingPointInfoList.forEach(([p, snappedP]) => {
movingPointInfoList.forEach(([p, resizedP]) => {
const rotatedP = rotateFn(p);

// There are three kinds of guide lines depending on resizing anchor type and modifire.
Expand All @@ -404,7 +404,7 @@ export function newBoundingBoxResizing(option: BoundingBoxResizingOption) {
: sub(rotatedP, adjustedRotatedDirection);
const direction = KeepAspectSegment ? sub(rotatedP, adjustedRotatedOrigin) : adjustedRotatedDirection;
const targetSeg = [adjustedRotatedOrigin, rotatedP];
const pedalRotatedMovedP = getPedal(rotateFn(snappedP), targetSeg);
const pedalRotatedMovedP = getPedal(rotateFn(resizedP), targetSeg);

const cross = getCrossLineAndLine(rotatedSegment, targetSeg);
if (cross) {
Expand All @@ -413,12 +413,13 @@ export function newBoundingBoxResizing(option: BoundingBoxResizingOption) {
if (rate === undefined || distance === undefined || d <= distance) {
rate = r;
distance = d;
movingPointInfo = [p, snappedP];
movingPointInfo = [p, resizedP];
}
}
});
if (rate === undefined || !movingPointInfo) {
return [getAffine(diff, modifire), 0];
// this snapping is invalid for this resizing.
return;
}

const adjustedOrigin = centralize ? centralizedOrigin : option.resizingBase.origin;
Expand Down
12 changes: 3 additions & 9 deletions src/composables/shapeSnapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -750,11 +750,8 @@ export function getSnappingResultForBoundingBoxResizing(
diff: IVec2,
options?: { keepAspect?: boolean; centralize?: boolean },
): { resizingAffine: AffineMatrix; snappingResult: SnappingResult | undefined } {
const keepAspect = options?.keepAspect;
const centralize = options?.centralize;

// Apply plain resizing
let resizingAffine = boundingBoxResizing.getAffine(diff, { keepAspect, centralize });
let resizingAffine = boundingBoxResizing.getAffine(diff, options);

// Let resized bounding box snap to shapes.
const snappingResults = boundingBoxPath
Expand All @@ -772,12 +769,9 @@ export function getSnappingResultForBoundingBoxResizing(
const result = pickMinItem(
guidelines
.map((guideline) =>
boundingBoxResizing.getAffineAfterSnapping(adjustedD, movingPointInfoList, guideline, {
keepAspect,
centralize,
}),
boundingBoxResizing.getAffineAfterSnapping(adjustedD, movingPointInfoList, guideline, options),
)
.filter((r) => r[1] <= shapeSnapping.snapThreshold * 2),
.filter((r): r is Exclude<typeof r, undefined> => !!r && r[1] <= shapeSnapping.snapThreshold * 2),
(r) => r[1],
);
// No snapping result satisfies the resizing restriction or close enough to the cursor.
Expand Down

0 comments on commit 7798f05

Please sign in to comment.