|
@@ -1,364 +1,540 @@
|
|
|
import { useStore } from "../store";
|
|
|
-import { components, DrawItem, ShapeType } from "../components";
|
|
|
-import { computed, reactive, watch } from "vue";
|
|
|
-import { calcScaleFactor, lineLen, Pos, vector } from "@/utils/math";
|
|
|
+import {
|
|
|
+ components,
|
|
|
+ DrawItem,
|
|
|
+ ShapeType,
|
|
|
+ ComponentSnapInfo,
|
|
|
+} from "../components";
|
|
|
+import { computed, reactive, watch, watchEffect } from "vue";
|
|
|
+import {
|
|
|
+ createLine,
|
|
|
+ eqNGDire,
|
|
|
+ eqPoint,
|
|
|
+ lineIntersection,
|
|
|
+ lineLen,
|
|
|
+ linePointLen,
|
|
|
+ linePointProjection,
|
|
|
+ numEq,
|
|
|
+ Pos,
|
|
|
+ vector,
|
|
|
+ vector2IncludedAngle,
|
|
|
+ verticalVector,
|
|
|
+ zeroEq,
|
|
|
+} from "@/utils/math";
|
|
|
import { installGlobalVar } from "./use-global-vars";
|
|
|
import { BaseItem } from "../components/util";
|
|
|
import {
|
|
|
- TransformerVectorType,
|
|
|
- useGetTransformerOrigin,
|
|
|
+ ScaleVectorType,
|
|
|
+ useGetTransformerOperDirection,
|
|
|
+ useGetTransformerOperType,
|
|
|
useTransformer,
|
|
|
} from "./use-transformer";
|
|
|
import { Transform } from "konva/lib/Util";
|
|
|
-import { Circle } from "konva/lib/shapes/Circle";
|
|
|
-import { useFormalLayer, useTempLayer } from "./use-layer";
|
|
|
-import { useViewerInvertTransform, useViewerTransform } from "./use-viewer";
|
|
|
+import { useUnitTransform, useViewerInvertTransform } from "./use-viewer";
|
|
|
import { MathUtils } from "three";
|
|
|
+import { arrayInsert, rangMod } from "@/utils/shared";
|
|
|
|
|
|
-type SnapPoint = Pos & Pick<DrawItem, "id">;
|
|
|
-export const useSnapPoints = installGlobalVar(() => {
|
|
|
+export type SnapInfo = ComponentSnapInfo & Pick<DrawItem, "id">;
|
|
|
+export const useGlobalSnapInfos = installGlobalVar(() => {
|
|
|
const store = useStore();
|
|
|
const types = Object.keys(components) as ShapeType[];
|
|
|
- const points = reactive(new Set<SnapPoint>());
|
|
|
+ const infos = reactive(new Set<SnapInfo>());
|
|
|
|
|
|
for (const type of types) {
|
|
|
- const api = (components as any)[type]?.getSnapPoints;
|
|
|
- if (!api) continue;
|
|
|
+ const comp = components[type];
|
|
|
+ if (!("getSnapInfos" in comp)) continue;
|
|
|
watch(
|
|
|
- (store as any)[type],
|
|
|
+ () => store[type],
|
|
|
(items) => {
|
|
|
+ if (!items) return;
|
|
|
for (const item of items) {
|
|
|
- watch(
|
|
|
- () => {
|
|
|
- const snaps = api(item) as SnapPoint[];
|
|
|
- snaps.forEach((snap) => (snap.id = item.id));
|
|
|
- return snaps;
|
|
|
- },
|
|
|
+ const snaps = computed(
|
|
|
+ () => comp.getSnapInfos!(item as any) as SnapInfo[]
|
|
|
+ );
|
|
|
+ const snapInfoWatchStop = watch(
|
|
|
+ snaps,
|
|
|
(snaps, _, onCleanup) => {
|
|
|
- snaps.forEach((snap) => points.add(snap));
|
|
|
+ snaps.forEach((snap) => {
|
|
|
+ snap.id = item.id;
|
|
|
+ infos.add(snap);
|
|
|
+ });
|
|
|
onCleanup(() => {
|
|
|
- snaps.forEach((snap) => points.delete(snap));
|
|
|
+ snaps.forEach((snap) => infos.delete(snap));
|
|
|
});
|
|
|
},
|
|
|
{ immediate: true }
|
|
|
);
|
|
|
+ const existsWatchStop = watchEffect(() => {
|
|
|
+ if (!items.includes(item as any)) {
|
|
|
+ snapInfoWatchStop();
|
|
|
+ existsWatchStop();
|
|
|
+ }
|
|
|
+ });
|
|
|
}
|
|
|
},
|
|
|
- { immediate: true, flush: "sync" }
|
|
|
+ { immediate: true }
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- return computed(() => Array.from(points.values()));
|
|
|
+ return computed(() => Array.from(infos.values()));
|
|
|
});
|
|
|
|
|
|
-export type SnapInfo = {
|
|
|
- useXSnap?: AxisSnapInfo,
|
|
|
- useYSnap?: AxisSnapInfo,
|
|
|
- xSnaps: AxisSnapInfo[];
|
|
|
- ySnaps: AxisSnapInfo[];
|
|
|
+export const useSnapConfig = () => {
|
|
|
+ const unitTransform = useUnitTransform();
|
|
|
+ return {
|
|
|
+ get snapOffset() {
|
|
|
+ return unitTransform.getPixel(5);
|
|
|
+ },
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export type SnapResultInfo = {
|
|
|
+ attractSnaps: AttractSnapInfo[];
|
|
|
clear: () => void;
|
|
|
};
|
|
|
-export const useSnapInfo = installGlobalVar(() => {
|
|
|
+export const useSnapResultInfo = installGlobalVar(() => {
|
|
|
const snapInfo = reactive({
|
|
|
-
|
|
|
- xSnaps: [],
|
|
|
- ySnaps: [],
|
|
|
+ attractSnaps: [],
|
|
|
clear() {
|
|
|
- snapInfo.xSnaps.length = 0;
|
|
|
- snapInfo.ySnaps.length = 0;
|
|
|
+ snapInfo.attractSnaps.length = 0;
|
|
|
},
|
|
|
- }) as SnapInfo;
|
|
|
+ }) as SnapResultInfo;
|
|
|
return snapInfo;
|
|
|
});
|
|
|
|
|
|
-type AxisSnapInfo = { ref: Pos; current: Pos; offset: number };
|
|
|
-
|
|
|
-export const getAxisSnapInfos = (
|
|
|
- refPoints: Pos[],
|
|
|
- selfPoints: Pos[],
|
|
|
- snapOffset: number
|
|
|
+export type AttractSnapInfo = {
|
|
|
+ ref: ComponentSnapInfo;
|
|
|
+ current: ComponentSnapInfo;
|
|
|
+ offset: number;
|
|
|
+ angle: number;
|
|
|
+ join: Pos;
|
|
|
+ refDirection: Pos;
|
|
|
+ refLinkNdx: number;
|
|
|
+ currentLinkNdx: number;
|
|
|
+};
|
|
|
+// TODO 返回结果按照self.point参照线多少排序, 子数组按照offset排序
|
|
|
+export const filterAttractSnapInfos = (
|
|
|
+ refInfos: ComponentSnapInfo[],
|
|
|
+ selfInfos: ComponentSnapInfo[],
|
|
|
+ filters: (
|
|
|
+ self: ComponentSnapInfo,
|
|
|
+ ref: ComponentSnapInfo,
|
|
|
+ result: AttractSnapInfo[]
|
|
|
+ ) =>
|
|
|
+ | { items?: AttractSnapInfo[]; stop?: boolean; stopSelf?: boolean }
|
|
|
+ | AttractSnapInfo[],
|
|
|
+ sortKey?: "offset" | "angle"
|
|
|
) => {
|
|
|
- let xSnapInfos: AxisSnapInfo[] = [];
|
|
|
- for (const snap of refPoints) {
|
|
|
- for (const current of selfPoints) {
|
|
|
- const offset = snap.x - current.x;
|
|
|
- if (Math.abs(offset) < snapOffset) {
|
|
|
- xSnapInfos.push({
|
|
|
- ref: snap,
|
|
|
- current,
|
|
|
- offset: offset,
|
|
|
- });
|
|
|
+ const attractSnapInfosGroups: AttractSnapInfo[][] = [];
|
|
|
+ for (const self of selfInfos) {
|
|
|
+ const attractSnapInfos: AttractSnapInfo[] = [];
|
|
|
+ for (const ref of refInfos) {
|
|
|
+ let infos = filters(self, ref, attractSnapInfos);
|
|
|
+ const stop = Array.isArray(infos) ? false : !!infos.stop;
|
|
|
+ const stopRef = Array.isArray(infos) ? false : !!infos.stopSelf;
|
|
|
+ infos = Array.isArray(infos) ? infos : infos.items!;
|
|
|
+ if (infos && sortKey) {
|
|
|
+ for (const info of infos) {
|
|
|
+ arrayInsert(
|
|
|
+ attractSnapInfos,
|
|
|
+ info,
|
|
|
+ (a, b) => b[sortKey] < a[sortKey]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (stop) {
|
|
|
+ return attractSnapInfosGroups;
|
|
|
+ }
|
|
|
+ if (stopRef) {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
+ arrayInsert(
|
|
|
+ attractSnapInfosGroups,
|
|
|
+ attractSnapInfos,
|
|
|
+ (a, b) => a.length < b.length
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return attractSnapInfosGroups;
|
|
|
+};
|
|
|
+
|
|
|
+type FilterAttrib = {
|
|
|
+ maxOffset?: number;
|
|
|
+ maxAngle?: number;
|
|
|
+ type?: "inter" | "projection" | "all" | "none";
|
|
|
+};
|
|
|
+const getAttractSnapInfos = (
|
|
|
+ ref: ComponentSnapInfo,
|
|
|
+ self: ComponentSnapInfo,
|
|
|
+ filter: FilterAttrib
|
|
|
+) => {
|
|
|
+ filter.type = filter.type || "all";
|
|
|
+
|
|
|
+ const limitAngle = "maxAngle" in filter;
|
|
|
+ const limitOffset = "maxOffset" in filter;
|
|
|
+ const isAll = filter.type === "all";
|
|
|
+
|
|
|
+ const attractSnapInfos: AttractSnapInfo[] = [];
|
|
|
+ for (let i = 0; i < self.linkAngle.length; i++) {
|
|
|
+ for (let j = 0; j < ref.linkAngle.length; j++) {
|
|
|
+ const angle = Math.abs(ref.linkAngle[j] - self.linkAngle[i]);
|
|
|
+ if (limitAngle && angle > filter.maxAngle!) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ let join: Pos | null = null;
|
|
|
+ let offset: number | null = null;
|
|
|
+
|
|
|
+ const checkOffset = (getJoin: () => Pos | null) => {
|
|
|
+ join = getJoin();
|
|
|
+ offset = join && lineLen(self.point, join);
|
|
|
+ const adopt = join && (!limitOffset || offset! < filter.maxOffset!);
|
|
|
+ if (!adopt) {
|
|
|
+ join = null;
|
|
|
+ offset = null;
|
|
|
+ }
|
|
|
+ return adopt;
|
|
|
+ };
|
|
|
+
|
|
|
+ if (filter.type === "none") {
|
|
|
+ const adopt = checkOffset(() => ref.point);
|
|
|
+ if (!adopt) continue;
|
|
|
+ } else {
|
|
|
+ const refLine = [
|
|
|
+ ref.point,
|
|
|
+ vector(ref.point).add(ref.linkDirections[j]),
|
|
|
+ ];
|
|
|
+ if (filter.type === "projection" || isAll) {
|
|
|
+ const adopt = checkOffset(() =>
|
|
|
+ linePointProjection(refLine, self.point)
|
|
|
+ );
|
|
|
+ if (!adopt && !isAll) continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const curLine = [
|
|
|
+ self.point,
|
|
|
+ vector(self.point).add(self.linkDirections[i]),
|
|
|
+ ];
|
|
|
+ if (!join && !checkOffset(() => lineIntersection(refLine, curLine))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ attractSnapInfos.push({
|
|
|
+ ref,
|
|
|
+ current: self,
|
|
|
+ refLinkNdx: j,
|
|
|
+ currentLinkNdx: i,
|
|
|
+ angle,
|
|
|
+ join: join!,
|
|
|
+ offset: offset!,
|
|
|
+ refDirection: ref.linkDirections[j],
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
+ return attractSnapInfos;
|
|
|
+};
|
|
|
+
|
|
|
+const moveSnap = (
|
|
|
+ refInfos: ComponentSnapInfo[],
|
|
|
+ selfInfos: ComponentSnapInfo[],
|
|
|
+ filter: FilterAttrib
|
|
|
+) => {
|
|
|
+ filter.maxOffset = filter.maxOffset || 15;
|
|
|
+ const exclude = new Map<AttractSnapInfo, AttractSnapInfo>();
|
|
|
+ const addExclude = (nor: AttractSnapInfo, act: AttractSnapInfo) => {
|
|
|
+ exclude.set(nor, act);
|
|
|
+ exclude.set(act, nor);
|
|
|
+ };
|
|
|
+ const getAttractSnapJoin = (nor: AttractSnapInfo, act: AttractSnapInfo) => {
|
|
|
+ if (nor === act || exclude.get(nor) === act) {
|
|
|
+ return void 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ const norJoin = nor.join;
|
|
|
+ const norDire = vector(nor.refDirection);
|
|
|
+ const norLine = createLine(norJoin, norDire);
|
|
|
+
|
|
|
+ // TODO 确保移动前后normal的 direction 保持一致
|
|
|
+ const useDire = act.refDirection;
|
|
|
+ if (eqNGDire(norDire, useDire)) {
|
|
|
+ return addExclude(nor, act);
|
|
|
+ }
|
|
|
+ const useJoin = act.join;
|
|
|
+ const useLine = createLine(useJoin, useDire);
|
|
|
+ const nuJoin = lineIntersection(norLine, useLine);
|
|
|
+
|
|
|
+ if (!nuJoin || lineLen(nuJoin, norJoin) > filter.maxOffset!) {
|
|
|
+ return addExclude(nor, act);
|
|
|
+ } else {
|
|
|
+ return nuJoin;
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- let ySnapInfos: AxisSnapInfo[] = [];
|
|
|
- for (const snap of refPoints) {
|
|
|
- for (const current of selfPoints) {
|
|
|
- const offset = snap.y - current.y;
|
|
|
- if (Math.abs(offset) < snapOffset) {
|
|
|
- ySnapInfos.push({
|
|
|
- ref: snap,
|
|
|
- current,
|
|
|
- offset: offset,
|
|
|
- });
|
|
|
+ const useAttractSnaps: AttractSnapInfo[] = [];
|
|
|
+ let end: Pos | null = null;
|
|
|
+ let start: Pos | null = null;
|
|
|
+
|
|
|
+ // TODO 最多参考2个信息
|
|
|
+ const attractSnapGroups = filterAttractSnapInfos(
|
|
|
+ refInfos,
|
|
|
+ selfInfos,
|
|
|
+ (self, ref, selfGroup) => {
|
|
|
+ const attractSnapInfos = getAttractSnapInfos(ref, self, filter);
|
|
|
+ const nor = selfGroup[0] || attractSnapInfos[0];
|
|
|
+ const checks = [...selfGroup, ...attractSnapInfos];
|
|
|
+ // TODO 尽快提前结束
|
|
|
+ for (const check of checks) {
|
|
|
+ const join = getAttractSnapJoin(nor, check);
|
|
|
+ if (join) {
|
|
|
+ end = join;
|
|
|
+ start = nor.current.point;
|
|
|
+ useAttractSnaps.push(nor, check);
|
|
|
+ return { stop: true };
|
|
|
+ }
|
|
|
}
|
|
|
+ return attractSnapInfos;
|
|
|
+ },
|
|
|
+ "offset"
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!end) {
|
|
|
+ if (!attractSnapGroups.length || !attractSnapGroups[0].length) return null;
|
|
|
+ const nor = attractSnapGroups[0][0];
|
|
|
+
|
|
|
+ end = nor.join;
|
|
|
+ start = nor.current.point;
|
|
|
+ useAttractSnaps.push(nor);
|
|
|
+
|
|
|
+ // TODO 如果没有同一个点的两线段,则使用2垂直的两点线段
|
|
|
+ const move = vector(end!).sub(start!);
|
|
|
+ for (let i = 1; i < attractSnapGroups.length; i++) {
|
|
|
+ let j = 0;
|
|
|
+ for (; j < attractSnapGroups[i].length; j++) {
|
|
|
+ const attractSnap = attractSnapGroups[i][j];
|
|
|
+ const rDire = attractSnap.refDirection;
|
|
|
+ const angle = vector2IncludedAngle(nor.refDirection, rDire);
|
|
|
+ if (!numEq(rangMod(angle, Math.PI), Math.PI / 2)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const cPoint = vector(attractSnap.current.point).add(move);
|
|
|
+ const rPoint = attractSnap.ref.point;
|
|
|
+ const inter = lineIntersection(
|
|
|
+ createLine(cPoint, nor.refDirection),
|
|
|
+ createLine(rPoint, rDire)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (inter) {
|
|
|
+ useAttractSnaps.push(attractSnap);
|
|
|
+ end = vector(end).add(inter.sub(cPoint));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (j !== attractSnapGroups[i].length) break;
|
|
|
}
|
|
|
}
|
|
|
- return { xSnapInfos, ySnapInfos };
|
|
|
+
|
|
|
+ const norMove = vector(end!).sub(start!);
|
|
|
+ return {
|
|
|
+ useAttractSnaps,
|
|
|
+ transform: new Transform().translate(norMove.x, norMove.y),
|
|
|
+ };
|
|
|
};
|
|
|
|
|
|
type SelfAttitude = {
|
|
|
rotation: number;
|
|
|
origin: Pos;
|
|
|
+ operTarget: Pos;
|
|
|
center: Pos;
|
|
|
};
|
|
|
-const optimumSnapToAxis = (
|
|
|
- xSnapInfos: AxisSnapInfo[],
|
|
|
- ySnapInfos: AxisSnapInfo[],
|
|
|
- type?: TransformerVectorType,
|
|
|
- attitude?: SelfAttitude
|
|
|
+const scaleSnap = (
|
|
|
+ refInfos: ComponentSnapInfo[],
|
|
|
+ selfInfos: ComponentSnapInfo[],
|
|
|
+ filter: Omit<FilterAttrib, "type">,
|
|
|
+ type: ScaleVectorType,
|
|
|
+ attitude: SelfAttitude
|
|
|
) => {
|
|
|
- if (type === "rotater") return null;
|
|
|
- const transform = new Transform();
|
|
|
-
|
|
|
- let minOffset = xSnapInfos[0].offset;
|
|
|
- let xSnapInfo: AxisSnapInfo | undefined = xSnapInfos[0];
|
|
|
- for (let i = 0; i < xSnapInfos.length; i++) {
|
|
|
- if (xSnapInfos[i].offset < minOffset) {
|
|
|
- xSnapInfo = xSnapInfos[i];
|
|
|
- minOffset = xSnapInfo.offset;
|
|
|
+ const { origin, operTarget } = attitude;
|
|
|
+ const attractSnaps: AttractSnapInfo[] = [];
|
|
|
+ const operVector = vector(operTarget).sub(origin).normalize();
|
|
|
+ const vOperVector = verticalVector(operVector);
|
|
|
+ const vLine = createLine(origin, vOperVector);
|
|
|
+ const proportional = [
|
|
|
+ "top-right",
|
|
|
+ "top-left",
|
|
|
+ "bottom-right",
|
|
|
+ "bottom-left",
|
|
|
+ ].includes(type);
|
|
|
+
|
|
|
+ const limitOffset = filter.maxOffset;
|
|
|
+ const asFilter: FilterAttrib = { ...filter, type: "none" };
|
|
|
+ delete asFilter.maxOffset;
|
|
|
+
|
|
|
+ const exclude = new Set<AttractSnapInfo>();
|
|
|
+ const getAttractSnapJoin = (nor: AttractSnapInfo) => {
|
|
|
+ if (exclude.has(nor)) return;
|
|
|
+ if (
|
|
|
+ eqNGDire(nor.refDirection, operVector) ||
|
|
|
+ eqNGDire(nor.refDirection, nor.current.linkDirections[nor.currentLinkNdx])
|
|
|
+ ) {
|
|
|
+ exclude.add(nor);
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- minOffset = ySnapInfos[0].offset;
|
|
|
- let ySnapInfo: AxisSnapInfo | undefined = ySnapInfos[0];
|
|
|
- for (let i = 0; i < ySnapInfos.length; i++) {
|
|
|
- if (ySnapInfos[i].offset < minOffset) {
|
|
|
- ySnapInfo = ySnapInfos[i];
|
|
|
- minOffset = ySnapInfo.offset;
|
|
|
+ const cur = nor.current.point;
|
|
|
+ if (
|
|
|
+ eqNGDire(vOperVector, nor.refDirection) &&
|
|
|
+ zeroEq(linePointLen(vLine, nor.ref.point))
|
|
|
+ ) {
|
|
|
+ exclude.add(nor);
|
|
|
+ attractSnaps.push(nor);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const refLine = [
|
|
|
+ nor.ref.point,
|
|
|
+ vector(nor.ref.point).add(nor.refDirection),
|
|
|
+ ];
|
|
|
+ const norLine = proportional
|
|
|
+ ? [origin, cur]
|
|
|
+ : [cur, vector(cur).add(operVector)];
|
|
|
+ const join = lineIntersection(refLine, norLine);
|
|
|
+ if (!join || (limitOffset && lineLen(join, cur) > limitOffset)) {
|
|
|
+ exclude.add(nor);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!eqPoint(vector(join).sub(cur).normalize(), operVector)) {
|
|
|
+ exclude.add(nor);
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (!xSnapInfo && !ySnapInfo) return null;
|
|
|
- if (!type) {
|
|
|
- return {
|
|
|
- xSnapInfo,
|
|
|
- ySnapInfo,
|
|
|
- transform: transform.translate(
|
|
|
- xSnapInfo?.offset || 0,
|
|
|
- ySnapInfo?.offset || 0
|
|
|
- ),
|
|
|
- };
|
|
|
- }
|
|
|
- if (!attitude) return null;
|
|
|
+ return join;
|
|
|
+ };
|
|
|
|
|
|
- const { center, rotation, origin } = attitude;
|
|
|
+ let useAttractSnap: AttractSnapInfo;
|
|
|
+ let useJoin: Pos;
|
|
|
+
|
|
|
+ // TODO 最多参考1个信息
|
|
|
+ filterAttractSnapInfos(refInfos, selfInfos, (self, ref) => {
|
|
|
+ if (eqPoint(self.point, origin)) return { stopSelf: true };
|
|
|
+ const attractSnapInfos = getAttractSnapInfos(ref, self, asFilter);
|
|
|
+ for (const info of attractSnapInfos) {
|
|
|
+ const join = getAttractSnapJoin(info);
|
|
|
+ if (join) {
|
|
|
+ info.join = join;
|
|
|
+ useJoin = join;
|
|
|
+ useAttractSnap = info;
|
|
|
+ return { stop: true };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return attractSnapInfos;
|
|
|
+ });
|
|
|
+ if (!useAttractSnap! || !useJoin!) return null;
|
|
|
|
|
|
- const scaleDirection = vector({ x: 0, y: 0 });
|
|
|
- type.includes("left") && (scaleDirection.x -= 1);
|
|
|
- type.includes("right") && (scaleDirection.x += 1);
|
|
|
- type.includes("top") && (scaleDirection.y -= 1);
|
|
|
- type.includes("bottom") && (scaleDirection.y += 1);
|
|
|
+ const rotation = Math.atan2(operVector.y, operVector.x);
|
|
|
+ const invRotateTransform = new Transform()
|
|
|
+ .rotate(-rotation)
|
|
|
+ .translate(-origin.x, -origin.y);
|
|
|
|
|
|
- scaleDirection.normalize();
|
|
|
- scaleDirection.rotateAround(
|
|
|
- { x: origin.x - center.x, y: origin.y - center.y },
|
|
|
- rotation
|
|
|
- );
|
|
|
- if (scaleDirection.x === 0) {
|
|
|
- xSnapInfo = undefined;
|
|
|
- }
|
|
|
- if (scaleDirection.y === 0) {
|
|
|
- ySnapInfo = undefined;
|
|
|
- }
|
|
|
- if (!xSnapInfo && !ySnapInfo) return null;
|
|
|
-
|
|
|
- const xScaleFactor =
|
|
|
- xSnapInfo &&
|
|
|
- (xSnapInfo.ref.x - origin.x) /
|
|
|
- ((xSnapInfo.current.x - origin.x) * scaleDirection.x);
|
|
|
- const yScaleFactor =
|
|
|
- ySnapInfo &&
|
|
|
- (ySnapInfo.ref.y - origin.y) /
|
|
|
- ((ySnapInfo.current.y - origin.y) * scaleDirection.y);
|
|
|
-
|
|
|
- if (!xScaleFactor && !yScaleFactor) return null;
|
|
|
-
|
|
|
- let scaleFactor;
|
|
|
- if (xScaleFactor && yScaleFactor) {
|
|
|
- if (xScaleFactor > yScaleFactor) {
|
|
|
- scaleFactor = yScaleFactor;
|
|
|
- xSnapInfo = undefined;
|
|
|
- } else {
|
|
|
- scaleFactor = xScaleFactor;
|
|
|
- ySnapInfo = undefined;
|
|
|
- }
|
|
|
- } else if (xScaleFactor) {
|
|
|
- scaleFactor = xScaleFactor;
|
|
|
- ySnapInfo = undefined;
|
|
|
- } else {
|
|
|
- scaleFactor = yScaleFactor;
|
|
|
- xSnapInfo = undefined;
|
|
|
+ const t = invRotateTransform.point(useJoin!);
|
|
|
+ const c = invRotateTransform.point(useAttractSnap.current.point);
|
|
|
+
|
|
|
+ const currentFactor = vector({
|
|
|
+ x: numEq(t.x, c.x) ? 1 : t.x / c.x,
|
|
|
+ y: numEq(t.y, c.y) ? 1 : t.y / c.y,
|
|
|
+ });
|
|
|
+ if (proportional) {
|
|
|
+ currentFactor.y = currentFactor.x;
|
|
|
}
|
|
|
|
|
|
- scaleDirection.multiplyScalar(scaleFactor!);
|
|
|
- transform
|
|
|
- .translate(origin.x, origin.y)
|
|
|
- .scale(scaleDirection.x, scaleDirection.y)
|
|
|
- .translate(-origin.x, -origin.y);
|
|
|
+ attractSnaps.push(useAttractSnap);
|
|
|
+ return {
|
|
|
+ useAttractSnaps: attractSnaps,
|
|
|
+ transform: new Transform()
|
|
|
+ .multiply(invRotateTransform.copy().invert())
|
|
|
+ .scale(currentFactor.x, currentFactor.y)
|
|
|
+ .multiply(invRotateTransform),
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const useSnap = (
|
|
|
+ snapInfos: { value: ComponentSnapInfo[] } = useGlobalSnapInfos()
|
|
|
+) => {
|
|
|
+ const snapResultInfo = useSnapResultInfo();
|
|
|
+ const snapConfig = useSnapConfig();
|
|
|
+ const afterHandler = (result: ReturnType<typeof moveSnap>) => {
|
|
|
+ if (result) {
|
|
|
+ snapResultInfo.attractSnaps = result.useAttractSnaps;
|
|
|
+ return result.transform;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ };
|
|
|
|
|
|
+ const move = (selfSnapInfos: ComponentSnapInfo[]) => {
|
|
|
+ const result = moveSnap(snapInfos.value, selfSnapInfos, {
|
|
|
+ maxOffset: snapConfig.snapOffset,
|
|
|
+ });
|
|
|
+ return afterHandler(result);
|
|
|
+ };
|
|
|
+ const scale = (
|
|
|
+ selfSnapInfos: ComponentSnapInfo[],
|
|
|
+ attitude: SelfAttitude & { type: ScaleVectorType }
|
|
|
+ ) => {
|
|
|
+ const result = scaleSnap(
|
|
|
+ snapInfos.value,
|
|
|
+ selfSnapInfos,
|
|
|
+ { maxOffset: snapConfig.snapOffset },
|
|
|
+ attitude.type,
|
|
|
+ attitude
|
|
|
+ );
|
|
|
+ return afterHandler(result);
|
|
|
+ };
|
|
|
return {
|
|
|
- xSnapInfo,
|
|
|
- ySnapInfo,
|
|
|
- transform: transform,
|
|
|
+ move,
|
|
|
+ scale,
|
|
|
+ clear: snapResultInfo.clear,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
-const CanSnapOffset = 15;
|
|
|
-export const useSnapToPoints = (itemId?: string) => {
|
|
|
+export const useComponentSnap = (componentId: string) => {
|
|
|
const store = useStore();
|
|
|
- const type = itemId ? store.getType(itemId as any) : undefined;
|
|
|
- const points = useSnapPoints();
|
|
|
- const snapInfo = useSnapInfo();
|
|
|
- const refPoints = computed(() => points.value.filter((p) => p.id !== itemId));
|
|
|
- const api = type && (components as any)[type]?.getSnapPoints;
|
|
|
+ const type = componentId ? store.getType(componentId) : undefined;
|
|
|
+ const comp = type && components[type];
|
|
|
+ const api = type && comp?.getSnapInfos;
|
|
|
+ if (!api) return null;
|
|
|
+
|
|
|
+ const snapInfos = useGlobalSnapInfos();
|
|
|
+ const refSnapInfos = computed(() =>
|
|
|
+ snapInfos.value.filter((p) => p.id !== componentId)
|
|
|
+ );
|
|
|
+ const baseSnap = useSnap(refSnapInfos);
|
|
|
+ const getOperType = useGetTransformerOperType();
|
|
|
+ const getTransformerOperDirection = useGetTransformerOperDirection();
|
|
|
const transformer = useTransformer();
|
|
|
- const getTransformerOrigin = useGetTransformerOrigin();
|
|
|
- const transform = new Transform();
|
|
|
- const rotateTransform = new Transform();
|
|
|
- const invRotateTransform = new Transform();
|
|
|
const viewerInvertTransform = useViewerInvertTransform();
|
|
|
- const layer = useTempLayer();
|
|
|
- const circle = new Circle({ fill: "rgb(255, 0, 0)", width: 20, height: 20 });
|
|
|
|
|
|
- const snap = (item: BaseItem | Pos, snapOffset = CanSnapOffset) => {
|
|
|
- const operateType = transformer.getActiveAnchor() as TransformerVectorType;
|
|
|
+ const snap = (item: BaseItem) => {
|
|
|
+ const operateType = getOperType();
|
|
|
+ const selfSnapInfos = api(item as any);
|
|
|
+ baseSnap.clear();
|
|
|
|
|
|
- let attitude: SelfAttitude | undefined = void 0;
|
|
|
- if (operateType && operateType !== "rotater") {
|
|
|
- const origin = getTransformerOrigin();
|
|
|
- if (!origin) return null;
|
|
|
+ // move
|
|
|
+ if (!operateType) {
|
|
|
+ return baseSnap.move(selfSnapInfos);
|
|
|
+ } else if (operateType !== "rotater") {
|
|
|
+ const direction = getTransformerOperDirection()!;
|
|
|
const node = transformer.nodes()[0];
|
|
|
const rect = node.getClientRect();
|
|
|
- attitude = {
|
|
|
+ const attitude = {
|
|
|
center: viewerInvertTransform.value.point({
|
|
|
x: rect.x + rect.width / 2,
|
|
|
y: rect.y + rect.height / 2,
|
|
|
}),
|
|
|
+ operTarget: direction[1],
|
|
|
rotation: MathUtils.degToRad(node.rotation()),
|
|
|
- origin,
|
|
|
+ origin: direction[0],
|
|
|
+ type: operateType,
|
|
|
};
|
|
|
+ return baseSnap.scale(selfSnapInfos, attitude);
|
|
|
}
|
|
|
-
|
|
|
- const selfPoints = (api ? api(item) : [item]) as Pos[];
|
|
|
- const axisInfos = getAxisSnapInfos(refPoints.value, selfPoints, snapOffset);
|
|
|
-
|
|
|
- snapInfo.clear();
|
|
|
- snapInfo.xSnaps = axisInfos.xSnapInfos
|
|
|
- snapInfo.ySnaps = axisInfos.ySnapInfos
|
|
|
-
|
|
|
- const toAxisResult = optimumSnapToAxis(
|
|
|
- axisInfos.xSnapInfos,
|
|
|
- axisInfos.ySnapInfos,
|
|
|
- operateType,
|
|
|
- attitude
|
|
|
- );
|
|
|
- if (!toAxisResult) return null;
|
|
|
-
|
|
|
- snapInfo.useXSnap = toAxisResult.xSnapInfo
|
|
|
- snapInfo.useYSnap = toAxisResult.xSnapInfo
|
|
|
-
|
|
|
- return toAxisResult.transform;
|
|
|
-
|
|
|
-
|
|
|
- transform.reset();
|
|
|
- const offset = {
|
|
|
- x: info.xSnapInfos[0]?.offset || 0,
|
|
|
- y: info.ySnapInfos[0]?.offset || 0,
|
|
|
- };
|
|
|
- if (Math.abs(offset.x) < 0.0001 && Math.abs(offset.y) < 0.0001) return null;
|
|
|
-
|
|
|
- if (!operateType) {
|
|
|
- snapInfo.xSnaps = info.xSnapInfos;
|
|
|
- snapInfo.ySnaps = info.ySnapInfos;
|
|
|
- return transform.translate(offset.x, offset.y);
|
|
|
- }
|
|
|
-
|
|
|
- const origin = getTransformerOrigin();
|
|
|
- if (!origin) return null;
|
|
|
-
|
|
|
- const node = transformer.nodes()[0];
|
|
|
- const rotation = MathUtils.degToRad(node.rotation());
|
|
|
- const rect = node.getClientRect();
|
|
|
- const center = viewerInvertTransform.value.point({
|
|
|
- x: rect.x + rect.width / 2,
|
|
|
- y: rect.y + rect.height / 2,
|
|
|
- });
|
|
|
-
|
|
|
- rotateTransform.reset();
|
|
|
- rotateTransform
|
|
|
- .translate(center.x, center.y)
|
|
|
- .rotate(rotation)
|
|
|
- .translate(-center.x, -center.y);
|
|
|
-
|
|
|
- rotateTransform.copyInto(invRotateTransform);
|
|
|
- invRotateTransform.invert();
|
|
|
-
|
|
|
- const norOrigin = invRotateTransform.point(origin);
|
|
|
- let scaleFactor: Partial<Pos> | null = null;
|
|
|
-
|
|
|
- // const getTarget = (snap: ) => {
|
|
|
-
|
|
|
- // }
|
|
|
-
|
|
|
- for (const snap of info.xSnapInfos) {
|
|
|
- scaleFactor = calcScaleFactor({
|
|
|
- origin: norOrigin,
|
|
|
- target: invRotateTransform.point({
|
|
|
- x: snap.ref.x,
|
|
|
- y: snap.current.y,
|
|
|
- }),
|
|
|
- current: invRotateTransform.point(snap.current),
|
|
|
- });
|
|
|
- if (scaleFactor) {
|
|
|
- console.log("a?");
|
|
|
- snapInfo.xSnaps = [snap];
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (!scaleFactor) {
|
|
|
- for (const snap of info.ySnapInfos) {
|
|
|
- scaleFactor = calcScaleFactor({
|
|
|
- origin: norOrigin,
|
|
|
- target: invRotateTransform.point({
|
|
|
- x: snap.current.x,
|
|
|
- y: snap.ref.y,
|
|
|
- }),
|
|
|
- current: invRotateTransform.point(snap.current),
|
|
|
- });
|
|
|
- if (scaleFactor) {
|
|
|
- console.log("b?");
|
|
|
- snapInfo.ySnaps = [snap];
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!scaleFactor) return null;
|
|
|
-
|
|
|
- const scaleTransform = new Transform()
|
|
|
- .translate(norOrigin.x, norOrigin.y)
|
|
|
- .scale(scaleFactor!.x!, 1)
|
|
|
- .translate(-norOrigin.x, -norOrigin.y);
|
|
|
-
|
|
|
- transform.reset();
|
|
|
- return transform
|
|
|
- .multiply(rotateTransform)
|
|
|
- .multiply(scaleTransform)
|
|
|
- .multiply(invRotateTransform);
|
|
|
};
|
|
|
|
|
|
- return [snap, snapInfo.clear] as const;
|
|
|
+ return [snap, baseSnap.clear] as const;
|
|
|
};
|