diff --git a/src/composables/shapeHandlers/multipleSelectedHandler.spec.ts b/src/composables/shapeHandlers/multipleSelectedHandler.spec.ts index 19ab0338..8df2e960 100644 --- a/src/composables/shapeHandlers/multipleSelectedHandler.spec.ts +++ b/src/composables/shapeHandlers/multipleSelectedHandler.spec.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from "vitest"; -import { newMultipleSelectedHandler } from "./multipleSelectedHandler"; +import { getIsCoordinateAngleFn, newMultipleSelectedHandler } from "./multipleSelectedHandler"; import { newShapeComposite } from "../shapeComposite"; import { createShape, getCommonStruct } from "../../shapes"; import { RectangleShape } from "../../shapes/rectangle"; @@ -19,14 +19,14 @@ describe("hitTest", () => { p: { x: 0, y: 10 }, width: 10, height: 10, - rotation: Math.PI / 2, + rotation: Math.PI / 3, }); const c = createShape(getCommonStruct, "rectangle", { id: "c", p: { x: 0, y: 20 }, width: 10, height: 10, - rotation: Math.PI / 2, + rotation: Math.PI / 3, }); const d = createShape(getCommonStruct, "rectangle", { id: "d", @@ -63,7 +63,7 @@ describe("hitTest", () => { const target1 = newMultipleSelectedHandler({ getShapeComposite: () => shapeComposite, targetIds: shapes.map((s) => s.id), - rotation: Math.PI / 2, + rotation: Math.PI / 3, }); expect(target1.hitTest({ x: 5, y: 4 }, 1)).toEqual({ type: "rotation", @@ -105,3 +105,21 @@ describe("hitTest", () => { expect(target0.hitTest({ x: 5, y: 5 }, 1)?.info[0]).toBe("a"); }); }); + +describe("getIsCoordinateAngleFn", () => { + test("should return true when the angle is coordinate", () => { + const fn1 = getIsCoordinateAngleFn(0); + expect(fn1(Math.PI / 3)).toBe(false); + expect(fn1(Math.PI / 2)).toBe(true); + expect(fn1(Math.PI)).toBe(true); + expect(fn1(Math.PI * 1.5)).toBe(true); + expect(fn1(Math.PI * 2)).toBe(true); + + const fn2 = getIsCoordinateAngleFn(Math.PI / 3); + expect(fn2((Math.PI / 3) * 0.5)).toBe(false); + expect(fn2((Math.PI / 3) * 1.5)).toBe(false); + expect(fn2(Math.PI / 3 + Math.PI / 2)).toBe(true); + expect(fn2(Math.PI / 3 + Math.PI)).toBe(true); + expect(fn2(Math.PI / 3 + (Math.PI * 3) / 2)).toBe(true); + }); +}); diff --git a/src/composables/shapeHandlers/multipleSelectedHandler.ts b/src/composables/shapeHandlers/multipleSelectedHandler.ts index 50d9b7af..52fb597f 100644 --- a/src/composables/shapeHandlers/multipleSelectedHandler.ts +++ b/src/composables/shapeHandlers/multipleSelectedHandler.ts @@ -1,9 +1,9 @@ -import { IVec2, MINVALUE, getDistance, getRectCenter } from "okageo"; +import { IVec2, getDistance, getRectCenter } from "okageo"; import { ShapeComposite } from "../shapeComposite"; import { defineShapeHandler } from "./core"; import { applyFillStyle } from "../../utils/fillStyle"; -import { TAU } from "../../utils/geometry"; -import { StyleScheme } from "../../models"; +import { isSameValue, TAU } from "../../utils/geometry"; +import { Shape, StyleScheme } from "../../models"; import { isLineShape } from "../../shapes/line"; import { CanvasCTX } from "../../utils/types"; @@ -24,10 +24,11 @@ interface Option { export const newMultipleSelectedHandler = defineShapeHandler((option) => { const shapeComposite = option.getShapeComposite(); - const rotationAnchorInfoList: RotationAnchorInfo[] = option.targetIds - .map((id) => shapeComposite.shapeMap[id]) - .filter((s) => !isLineShape(s) && Math.abs(s.rotation - option.rotation) > MINVALUE) - .map((s) => [s.id, s.rotation, getRectCenter(shapeComposite.getWrapperRect(s))]); + + const rotationAnchorInfoList: RotationAnchorInfo[] = getIncoordinateAngledShapes( + option.targetIds.map((id) => shapeComposite.shapeMap[id]), + option.rotation, + ).map((s) => [s.id, s.rotation, getRectCenter(shapeComposite.getWrapperRect(s))]); function hitTest(p: IVec2, scale: number): HitResult | undefined { const threshold = ANCHOR_SIZE * scale; @@ -62,3 +63,16 @@ export const newMultipleSelectedHandler = defineShapeHandler( }; }); export type MultipleSelectedHandler = ReturnType; + +function getIncoordinateAngledShapes(shapes: Shape[], origin: number) { + const checkFn = getIsCoordinateAngleFn(origin); + return shapes.filter((s) => !isLineShape(s) && !checkFn(s.rotation)); +} + +export function getIsCoordinateAngleFn(origin: number): (r: number) => boolean { + return (r) => { + const cos = Math.cos(r - origin); + const sin = Math.sin(r - origin); + return isSameValue(Math.abs(cos * sin), 0); + }; +}