|
@@ -0,0 +1,765 @@
|
|
|
+import {
|
|
|
+ SnapResultInfo,
|
|
|
+ useCustomSnapInfos,
|
|
|
+ useSnap,
|
|
|
+ useSnapConfig,
|
|
|
+} from "@/core/hook/use-snap";
|
|
|
+import { defaultStyle, getSnapInfos, LineData, LineDataLine } from ".";
|
|
|
+import {
|
|
|
+ eqPoint,
|
|
|
+ getVectorLine,
|
|
|
+ lineCenter,
|
|
|
+ lineIntersection,
|
|
|
+ lineLen,
|
|
|
+ linePointLen,
|
|
|
+ linePointProjection,
|
|
|
+ lineVector,
|
|
|
+ Pos,
|
|
|
+ vector2IncludedAngle,
|
|
|
+ verticalVector,
|
|
|
+ zeroEq,
|
|
|
+} from "@/utils/math";
|
|
|
+import {
|
|
|
+ copy,
|
|
|
+ frameEebounce,
|
|
|
+ mergeFuns,
|
|
|
+ onlyId,
|
|
|
+ rangMod,
|
|
|
+} from "@/utils/shared";
|
|
|
+import { MathUtils, Vector2 } from "three";
|
|
|
+import { generateSnapInfos, getBaseItem } from "../util";
|
|
|
+import { ComponentSnapInfo } from "..";
|
|
|
+import { useStore } from "@/core/store";
|
|
|
+import { computed, onUnmounted, reactive, ref, Ref, watch } from "vue";
|
|
|
+import {
|
|
|
+ useCursor,
|
|
|
+ usePointerPos,
|
|
|
+ useRunHook,
|
|
|
+ useStage,
|
|
|
+} from "@/core/hook/use-global-vars";
|
|
|
+import { PropertyDescribes } from "@/core/html-mount/propertys";
|
|
|
+import { useMode } from "@/core/hook/use-status";
|
|
|
+import { useListener } from "@/core/hook/use-event";
|
|
|
+import { Mode } from "@/constant/mode";
|
|
|
+import { useViewerInvertTransform } from "@/core/hook/use-viewer";
|
|
|
+import { clickListener } from "@/utils/event";
|
|
|
+import {
|
|
|
+ genGetLineIconAttach,
|
|
|
+ getLineIconMat,
|
|
|
+ LineIconData,
|
|
|
+} from "../line-icon";
|
|
|
+import { useDrawIngData } from "@/core/hook/use-draw";
|
|
|
+import { useComponentDescribes } from "@/core/hook/use-component";
|
|
|
+
|
|
|
+export type NLineDataCtx = {
|
|
|
+ del: {
|
|
|
+ points: Record<string, LineData["points"][0]>;
|
|
|
+ lines: Record<string, LineDataLine>;
|
|
|
+ };
|
|
|
+ add: {
|
|
|
+ points: Record<string, LineData["points"][0]>;
|
|
|
+ lines: Record<string, LineDataLine>;
|
|
|
+ };
|
|
|
+ update: {
|
|
|
+ points: Record<string, LineData["points"][0]>;
|
|
|
+ lines: Record<string, LineDataLine>;
|
|
|
+ };
|
|
|
+};
|
|
|
+export const getInitCtx = (): NLineDataCtx => ({
|
|
|
+ del: {
|
|
|
+ points: {},
|
|
|
+ lines: {},
|
|
|
+ },
|
|
|
+ add: {
|
|
|
+ points: {},
|
|
|
+ lines: {},
|
|
|
+ },
|
|
|
+ update: {
|
|
|
+ points: {},
|
|
|
+ lines: {},
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+export const repPointRef = (
|
|
|
+ data: LineData,
|
|
|
+ delId: string,
|
|
|
+ repId: string,
|
|
|
+ queUpdate = true
|
|
|
+) => {
|
|
|
+ for (let i = 0; i < data.lines.length; i++) {
|
|
|
+ const line = data.lines[i];
|
|
|
+ if (line.a === delId) {
|
|
|
+ if (queUpdate) {
|
|
|
+ data.lines[i] = { ...line, a: repId };
|
|
|
+ } else {
|
|
|
+ data.lines[i].a = repId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (line.b === delId) {
|
|
|
+ if (queUpdate) {
|
|
|
+ data.lines[i] = { ...line, b: repId };
|
|
|
+ } else {
|
|
|
+ data.lines[i].b = repId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+};
|
|
|
+
|
|
|
+export const getLinePoints = (data: LineData, line: LineDataLine) => [
|
|
|
+ data.points.find((p) => p.id === line.a)!,
|
|
|
+ data.points.find((p) => p.id === line.b)!,
|
|
|
+];
|
|
|
+
|
|
|
+export const deduplicateLines = (data: LineData) => {
|
|
|
+ const seen = new Map<string, LineDataLine>();
|
|
|
+ let isChange = false;
|
|
|
+ for (const line of data.lines) {
|
|
|
+ if (line.a === line.b) continue;
|
|
|
+ // 生成标准化键:确保 (a,b) 和 (b,a) 被视为相同,并且 a === b 时也去重
|
|
|
+ const key1 = `${line.a},${line.b}`;
|
|
|
+ const key2 = `${line.b},${line.a}`;
|
|
|
+
|
|
|
+ // 检查是否已存在相同键
|
|
|
+ const existingKey = seen.has(key1) ? key1 : seen.has(key2) ? key2 : null;
|
|
|
+
|
|
|
+ if (existingKey) {
|
|
|
+ // 如果存在重复键,覆盖旧值(保留尾部元素)
|
|
|
+ seen.delete(existingKey);
|
|
|
+ seen.set(key1, line); // 统一存储为 key1 格式
|
|
|
+ isChange = true;
|
|
|
+ } else {
|
|
|
+ // 新记录,直接存储
|
|
|
+ seen.set(key1, line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (isChange) {
|
|
|
+ data.lines = Array.from(seen.values());
|
|
|
+ }
|
|
|
+
|
|
|
+ return data;
|
|
|
+};
|
|
|
+
|
|
|
+export const getJoinLine = (
|
|
|
+ data: LineData,
|
|
|
+ line: LineDataLine,
|
|
|
+ pId: string
|
|
|
+) => {
|
|
|
+ const pointIds = [line.a, line.b];
|
|
|
+ return data.lines
|
|
|
+ .filter(
|
|
|
+ (item) =>
|
|
|
+ (item.a === pId || item.b === pId) &&
|
|
|
+ !(pointIds.includes(item.a) && pointIds.includes(item.b))
|
|
|
+ )
|
|
|
+ .map((line) => {
|
|
|
+ const pointIds = pId === line.a ? [line.a, line.b] : [line.b, line.a];
|
|
|
+ return {
|
|
|
+ ...line,
|
|
|
+ points: pointIds.map((id) => data.points.find((p) => p.id === id)!),
|
|
|
+ };
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+export const foreNormalLineData = (data:LineData) => {
|
|
|
+ for (let i = 0; i < data.lines.length; i++) {
|
|
|
+ const {a, b} = data.lines[i]
|
|
|
+ if (!data.points.some(p => p.id === a) || !data.points.some(p => p.id === b)) {
|
|
|
+ data.lines.splice(i--, 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for (let i = 0; i < data.points.length; i++) {
|
|
|
+ const id = data.points[i].id
|
|
|
+ if (!data.lines.some(l => l.a === id || l.b === id)) {
|
|
|
+ data.points.splice(i, 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export const normalLineData = (data: LineData, ctx: NLineDataCtx) => {
|
|
|
+ const changePoints = [
|
|
|
+ ...Object.values(ctx.add.points),
|
|
|
+ ...Object.values(ctx.update.points),
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 合并相同点
|
|
|
+ for (const p2 of changePoints) {
|
|
|
+ const ndx = data.points.findIndex((item) => item.id === p2.id);
|
|
|
+ if (!~ndx) continue;
|
|
|
+
|
|
|
+ for (let i = 0; i < data.points.length; i++) {
|
|
|
+ const p1 = data.points[i];
|
|
|
+ if (p1.id !== p2.id && eqPoint(p1, p2)) {
|
|
|
+ repPointRef(data, p1.id, p2.id);
|
|
|
+ data.points.splice(i, 1);
|
|
|
+ i--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除线a b 点一样的线段
|
|
|
+ for (let i = 0; i < data.lines.length; i++) {
|
|
|
+ const line = data.lines[i];
|
|
|
+ if (line.a === line.b) {
|
|
|
+ data.lines.splice(i--, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除游离点
|
|
|
+ const pointIds = Object.values(ctx.del.lines).flatMap((item) => [
|
|
|
+ item.a,
|
|
|
+ item.b,
|
|
|
+ ]);
|
|
|
+ pointIds.push(...Object.keys(ctx.add.points));
|
|
|
+ const linePointIds = data.lines.flatMap((item) => [item.a, item.b]);
|
|
|
+ for (let id of pointIds) {
|
|
|
+ if (!linePointIds.includes(id)) {
|
|
|
+ const ndx = data.points.findIndex((p) => p.id === id);
|
|
|
+ ~ndx && data.points.splice(ndx, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // foreNormalLineData(data)
|
|
|
+ return deduplicateLines(data);
|
|
|
+};
|
|
|
+
|
|
|
+export const genMoveLineHandler = (
|
|
|
+ data: LineData,
|
|
|
+ lineId: string,
|
|
|
+ snapConfig: ReturnType<typeof useSnapConfig>,
|
|
|
+ snapResult: SnapResultInfo,
|
|
|
+ ctx = getInitCtx()
|
|
|
+) => {
|
|
|
+ const line = data.lines.find((line) => line.id === lineId)!;
|
|
|
+ const pointIds = [line.a, line.b];
|
|
|
+ const points = pointIds.map((id) => data.points.find((p) => p.id === id)!);
|
|
|
+ const lineDire = lineVector(points);
|
|
|
+ const initPoints = copy(points);
|
|
|
+ const angleRange = [MathUtils.degToRad(10), MathUtils.degToRad(170)];
|
|
|
+
|
|
|
+ const getRefInfo = (moveDire: Vector2, ndx: number) => {
|
|
|
+ const joinLines = getJoinLine(data, line, pointIds[ndx]);
|
|
|
+ const joinLineDires: Vector2[] = [];
|
|
|
+ const linePoints = [points[ndx], points[Number(!ndx)]];
|
|
|
+ const lineDire = lineVector(linePoints);
|
|
|
+ const joinPoints: LineData["points"] = [];
|
|
|
+
|
|
|
+ let invAngle = Number.MAX_VALUE;
|
|
|
+ let invSelectLineId: string;
|
|
|
+ let invSelectLineDire: Vector2 | null = null;
|
|
|
+
|
|
|
+ let alongAngle = -Number.MAX_VALUE;
|
|
|
+ let alongSelectLineId: string;
|
|
|
+ let alongSelectLineDire: Vector2 | null = null;
|
|
|
+
|
|
|
+ for (const line of joinLines) {
|
|
|
+ joinPoints.push(...line.points.filter((p) => !points.includes(p)));
|
|
|
+ const joinDire = lineVector(line.points);
|
|
|
+ joinLineDires.push(joinDire);
|
|
|
+
|
|
|
+ const angle = vector2IncludedAngle(lineDire, joinDire);
|
|
|
+ if (angle > 0) {
|
|
|
+ if (angle < invAngle) {
|
|
|
+ invAngle = angle;
|
|
|
+ invSelectLineId = line.id;
|
|
|
+ invSelectLineDire = joinDire;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (angle > alongAngle) {
|
|
|
+ alongAngle = angle;
|
|
|
+ alongSelectLineId = line.id;
|
|
|
+ alongSelectLineDire = joinDire;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!invSelectLineDire && !alongSelectLineDire) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let isAlong = !invSelectLineDire;
|
|
|
+ if (!isAlong && alongSelectLineDire) {
|
|
|
+ const invMoveAngle = Math.abs(
|
|
|
+ vector2IncludedAngle(moveDire, invSelectLineDire!)
|
|
|
+ );
|
|
|
+ const alongMoveAngle = Math.abs(
|
|
|
+ vector2IncludedAngle(moveDire, alongSelectLineDire!)
|
|
|
+ );
|
|
|
+ isAlong = alongMoveAngle! < invMoveAngle!;
|
|
|
+ }
|
|
|
+ let info = isAlong
|
|
|
+ ? {
|
|
|
+ lineDire,
|
|
|
+ selectLineDire: alongSelectLineDire!,
|
|
|
+ selectLineId: alongSelectLineId!,
|
|
|
+ angle: alongAngle!,
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ lineDire,
|
|
|
+ selectLineDire: invSelectLineDire!,
|
|
|
+ selectLineId: invSelectLineId!,
|
|
|
+ angle: invAngle!,
|
|
|
+ };
|
|
|
+
|
|
|
+ info.angle = rangMod(info.angle, Math.PI);
|
|
|
+ const needVertical =
|
|
|
+ info.angle > angleRange[1] || info.angle < angleRange[0];
|
|
|
+ const needSplit =
|
|
|
+ needVertical ||
|
|
|
+ joinLineDires.some(
|
|
|
+ (dire) =>
|
|
|
+ dire !== info.selectLineDire &&
|
|
|
+ !zeroEq(
|
|
|
+ rangMod(vector2IncludedAngle(dire, info.selectLineDire), Math.PI)
|
|
|
+ )
|
|
|
+ );
|
|
|
+ return { ...info, needSplit, needVertical, joinPoints };
|
|
|
+ };
|
|
|
+
|
|
|
+ let refInfos: ReturnType<typeof getRefInfo>[];
|
|
|
+ let snapLines: (null | Pos[])[];
|
|
|
+ let inited = false;
|
|
|
+ let norNdx = -1;
|
|
|
+
|
|
|
+ const init = (moveDires: Vector2[]) => {
|
|
|
+ refInfos = [getRefInfo(moveDires[0], 0), getRefInfo(moveDires[0], 1)];
|
|
|
+ snapLines = [];
|
|
|
+ let minAngle = Math.PI / 2;
|
|
|
+ const vLineDire = verticalVector(lineDire);
|
|
|
+ for (let i = 0; i < refInfos.length; i++) {
|
|
|
+ const refInfo = refInfos[i];
|
|
|
+ if (!refInfo) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (refInfo.needSplit) {
|
|
|
+ // 拆分点
|
|
|
+ const point = points[i];
|
|
|
+ const newPoint = { ...point, id: onlyId() };
|
|
|
+ data.points.push(newPoint);
|
|
|
+ repPointRef(data, point.id, newPoint.id, false);
|
|
|
+ const newLine = {
|
|
|
+ ...getBaseItem(),
|
|
|
+ ...defaultStyle,
|
|
|
+ a: point.id,
|
|
|
+ b: newPoint.id,
|
|
|
+ };
|
|
|
+ data.lines.push(newLine);
|
|
|
+ ctx.add.lines[newLine.id] = newLine;
|
|
|
+ ctx.add.points[newPoint.id] = newPoint;
|
|
|
+
|
|
|
+ if (i) {
|
|
|
+ line.b = point.id;
|
|
|
+ } else {
|
|
|
+ line.a = point.id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const dire = refInfo.needVertical
|
|
|
+ ? verticalVector(refInfo.selectLineDire)
|
|
|
+ : refInfo.selectLineDire;
|
|
|
+
|
|
|
+ const angle = rangMod(vector2IncludedAngle(dire, vLineDire), Math.PI / 2);
|
|
|
+ if (angle < minAngle) {
|
|
|
+ norNdx = i;
|
|
|
+ minAngle = angle;
|
|
|
+ }
|
|
|
+ snapLines[i] = getVectorLine(dire, copy(points[i]), 10);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const assignPos = (origin: LineData["points"][0], target: Pos) => {
|
|
|
+ origin.x = target.x;
|
|
|
+ origin.y = target.y;
|
|
|
+ ctx.update.points[origin.id] = origin;
|
|
|
+ };
|
|
|
+
|
|
|
+ const updateOtPoint = (ndx: number) => {
|
|
|
+ const uNdx = ndx === 1 ? 0 : 1;
|
|
|
+ const move = new Vector2(
|
|
|
+ points[ndx].x - initPoints[ndx].x,
|
|
|
+ points[ndx].y - initPoints[ndx].y
|
|
|
+ );
|
|
|
+ if (!snapLines[uNdx]) {
|
|
|
+ assignPos(points[uNdx], move.add(initPoints[uNdx]));
|
|
|
+ } else {
|
|
|
+ assignPos(
|
|
|
+ points[uNdx],
|
|
|
+ lineIntersection(getVectorLine(lineDire, points[ndx]), snapLines[uNdx])!
|
|
|
+ );
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const move = (finalPoss: Pos[]) => {
|
|
|
+ if (!inited) {
|
|
|
+ const moveDires = finalPoss.map((pos, ndx) =>
|
|
|
+ lineVector([initPoints[ndx], pos])
|
|
|
+ );
|
|
|
+ inited = true;
|
|
|
+ init(moveDires);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!snapLines[0] && !snapLines[1]) {
|
|
|
+ assignPos(points[0], finalPoss[0]);
|
|
|
+ assignPos(points[1], finalPoss[1]);
|
|
|
+ } else if (!snapLines[0]) {
|
|
|
+ const pos = linePointProjection(snapLines[1]!, finalPoss[1]);
|
|
|
+ assignPos(points[1], pos);
|
|
|
+ updateOtPoint(1);
|
|
|
+ } else if (!snapLines[1]) {
|
|
|
+ const pos = linePointProjection(snapLines[0]!, finalPoss[0]);
|
|
|
+ assignPos(points[0], pos);
|
|
|
+ updateOtPoint(0);
|
|
|
+ } else {
|
|
|
+ const pos = linePointProjection(snapLines[norNdx]!, finalPoss[norNdx]);
|
|
|
+ assignPos(points[norNdx], pos);
|
|
|
+ updateOtPoint(norNdx);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const getSnapRefPoint = (
|
|
|
+ point: Pos,
|
|
|
+ refPoints: Pos[],
|
|
|
+ line: Pos[] | null
|
|
|
+ ) => {
|
|
|
+ for (const refPoint of refPoints) {
|
|
|
+ if (
|
|
|
+ lineLen(refPoint, point) < snapConfig.snapOffset &&
|
|
|
+ (!line || zeroEq(linePointLen(line, refPoint)))
|
|
|
+ ) {
|
|
|
+ return refPoint;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const snap = () => {
|
|
|
+ snapResult.clear();
|
|
|
+ let refPoint: Pos | undefined = undefined;
|
|
|
+ let ndx = -1;
|
|
|
+
|
|
|
+ const useRefPoint = () => {
|
|
|
+ const hv = [
|
|
|
+ { join: refPoint, refDirection: { x: 0, y: 1 } },
|
|
|
+ { join: refPoint, refDirection: { x: 1, y: 0 } },
|
|
|
+ ];
|
|
|
+ snapResult.attractSnaps.push(...(hv as any));
|
|
|
+ assignPos(points[ndx], refPoint!);
|
|
|
+ updateOtPoint(ndx);
|
|
|
+ };
|
|
|
+
|
|
|
+ if (refInfos[0]?.joinPoints) {
|
|
|
+ refPoint = getSnapRefPoint(
|
|
|
+ points[0],
|
|
|
+ refInfos[0]?.joinPoints,
|
|
|
+ snapLines[0]
|
|
|
+ );
|
|
|
+ if (refPoint) {
|
|
|
+ ndx = 0;
|
|
|
+ return useRefPoint();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (refInfos[1]?.joinPoints) {
|
|
|
+ refPoint = getSnapRefPoint(
|
|
|
+ points[1],
|
|
|
+ refInfos[1]?.joinPoints,
|
|
|
+ snapLines[1]
|
|
|
+ );
|
|
|
+ if (refPoint) {
|
|
|
+ ndx = 1;
|
|
|
+ return useRefPoint();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const usedPoints = [
|
|
|
+ ...(refInfos[0]?.joinPoints || []),
|
|
|
+ ...(refInfos[1]?.joinPoints || []),
|
|
|
+ ...points,
|
|
|
+ ];
|
|
|
+ const refPoints = data.points.filter((p) => !usedPoints.includes(p));
|
|
|
+ for (let i = 0; i < points.length; i++) {
|
|
|
+ refPoint = getSnapRefPoint(points[i], refPoints, snapLines[i]);
|
|
|
+ if (refPoint) {
|
|
|
+ ndx = i;
|
|
|
+ return useRefPoint();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const end = () => {
|
|
|
+ snapResult.clear();
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ move: (ps: Pos[]) => {
|
|
|
+ move(ps);
|
|
|
+ snap();
|
|
|
+ },
|
|
|
+ end,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const useLineDataSnapInfos = () => {
|
|
|
+ const infos = useCustomSnapInfos();
|
|
|
+ const store = useStore();
|
|
|
+ const lineData = computed(() => store.getTypeItems("line")[0]);
|
|
|
+ let snapInfos: ComponentSnapInfo[];
|
|
|
+
|
|
|
+ const updateSnapInfos = (pointIds: string[]) => {
|
|
|
+ clear();
|
|
|
+ snapInfos = getSnapInfos({
|
|
|
+ ...lineData.value,
|
|
|
+ lines: lineData.value.lines.filter(
|
|
|
+ (item) => !(pointIds.includes(item.a) || pointIds.includes(item.b))
|
|
|
+ ),
|
|
|
+ points: lineData.value.points.filter(
|
|
|
+ (item) => !pointIds.includes(item.id)
|
|
|
+ ),
|
|
|
+ });
|
|
|
+ snapInfos.forEach((item) => {
|
|
|
+ infos.add(item);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const clear = () => {
|
|
|
+ snapInfos && snapInfos.forEach((item) => infos.remove(item));
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ update: updateSnapInfos,
|
|
|
+ clear,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const updateLineLength = (
|
|
|
+ lineData: LineData,
|
|
|
+ line: LineDataLine,
|
|
|
+ length: number,
|
|
|
+ flex?: "a" | "b" | "both",
|
|
|
+ vector?: Pos
|
|
|
+) => {
|
|
|
+ const points = [
|
|
|
+ lineData.points.find((p) => p.id === line.a)!,
|
|
|
+ lineData.points.find((p) => p.id === line.b)!,
|
|
|
+ ];
|
|
|
+ vector = vector || lineVector(points);
|
|
|
+
|
|
|
+ if (!flex) {
|
|
|
+ const aCount = lineData.lines.filter(
|
|
|
+ (line) => line.a === points[0].id || line.b === points[0].id
|
|
|
+ ).length;
|
|
|
+ const bCount = lineData.lines.filter(
|
|
|
+ (line) => line.a === points[1].id || line.b === points[1].id
|
|
|
+ ).length;
|
|
|
+ if (aCount === bCount || (aCount > 1 && bCount > 1)) {
|
|
|
+ flex = "both";
|
|
|
+ } else {
|
|
|
+ flex = aCount > 1 ? "b" : "a";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let moveVector = new Vector2(vector.x, vector.y);
|
|
|
+ let npoints: Pos[];
|
|
|
+ if (flex === "both") {
|
|
|
+ const center = lineCenter(points);
|
|
|
+ const l1 = getVectorLine(
|
|
|
+ moveVector.clone().multiplyScalar(-1),
|
|
|
+ center,
|
|
|
+ length / 2
|
|
|
+ );
|
|
|
+ const l2 = getVectorLine(moveVector, center, length / 2);
|
|
|
+ npoints = [l1[1], l2[1]];
|
|
|
+ } else {
|
|
|
+ const fNdx = flex === "a" ? 1 : 0;
|
|
|
+ const mNdx = flex === "a" ? 0 : 1;
|
|
|
+ const line = getVectorLine(
|
|
|
+ mNdx === 1 ? moveVector : moveVector.multiplyScalar(-1),
|
|
|
+ points[fNdx],
|
|
|
+ length
|
|
|
+ );
|
|
|
+ const nPoints: Pos[] = [];
|
|
|
+ nPoints[fNdx] = points[fNdx];
|
|
|
+ nPoints[mNdx] = line[1];
|
|
|
+ npoints = nPoints;
|
|
|
+ }
|
|
|
+ Object.assign(points[0], npoints[0]);
|
|
|
+ Object.assign(points[1], npoints[1]);
|
|
|
+};
|
|
|
+
|
|
|
+export const useLineDescribes = (line: Ref<LineDataLine>) => {
|
|
|
+ const d: any = useComponentDescribes(line, ["stroke", "strokeWidth"], {});
|
|
|
+ const store = useStore();
|
|
|
+ const lineData = computed(() => store.getTypeItems("line")[0]);
|
|
|
+ const points = computed(() => [
|
|
|
+ lineData.value.points.find((p) => p.id === line.value.a)!,
|
|
|
+ lineData.value.points.find((p) => p.id === line.value.b)!,
|
|
|
+ ]);
|
|
|
+ let setLineVector: Vector2;
|
|
|
+
|
|
|
+ watch(d, (d) => {
|
|
|
+ d.strokeWidth.props = {
|
|
|
+ ...d.strokeWidth.props,
|
|
|
+ proportion: true,
|
|
|
+ };
|
|
|
+ d.strokeWidth.label = "粗细";
|
|
|
+ d.stroke.label = "颜色";
|
|
|
+
|
|
|
+ d.length = {
|
|
|
+ type: "inputNum",
|
|
|
+ label: "线段长度",
|
|
|
+ "layout-type": "row",
|
|
|
+ get value() {
|
|
|
+ return lineLen(points.value[0], points.value[1]);
|
|
|
+ },
|
|
|
+ set value(val) {
|
|
|
+ console.log(val, d.length.isChange);
|
|
|
+ if (!d.isChange) {
|
|
|
+ setLineVector = lineVector(points.value);
|
|
|
+ }
|
|
|
+ updateLineLength(
|
|
|
+ lineData.value,
|
|
|
+ line.value,
|
|
|
+ val,
|
|
|
+ undefined,
|
|
|
+ setLineVector
|
|
|
+ );
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }, {immediate: true});
|
|
|
+ return d as PropertyDescribes;
|
|
|
+};
|
|
|
+
|
|
|
+export const useDrawLinePoint = (
|
|
|
+ data: Ref<LineData>,
|
|
|
+ line: Ref<LineDataLine>,
|
|
|
+ callback: (data: {
|
|
|
+ prev: LineDataLine;
|
|
|
+ next: LineDataLine;
|
|
|
+ point: LineData["points"][0];
|
|
|
+ oldIcons: LineIconData[];
|
|
|
+ newIcons: LineIconData[];
|
|
|
+ }) => void
|
|
|
+) => {
|
|
|
+ const mode = useMode();
|
|
|
+ let __leave: (() => void) | null;
|
|
|
+ const leave = () => {
|
|
|
+ if (__leave) {
|
|
|
+ __leave();
|
|
|
+ __leave = null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ useListener("contextmenu", (ev) => ev.button === 2 && setTimeout(leave));
|
|
|
+ onUnmounted(leave);
|
|
|
+
|
|
|
+ const pos = usePointerPos();
|
|
|
+ const viewInvMat = useViewerInvertTransform();
|
|
|
+ const drawProps = ref<{
|
|
|
+ data: LineData;
|
|
|
+ prev: LineDataLine;
|
|
|
+ next: LineDataLine;
|
|
|
+ point: LineData["points"][0];
|
|
|
+ }>();
|
|
|
+ const runHook = useRunHook();
|
|
|
+ const snapInfos = useLineDataSnapInfos();
|
|
|
+ const snap = useSnap();
|
|
|
+ const stage = useStage();
|
|
|
+ const store = useStore();
|
|
|
+ const icons = computed(() =>
|
|
|
+ store.getTypeItems("lineIcon").filter((item) => item.lineId === line.value.id)
|
|
|
+ );
|
|
|
+ const drawStore = useDrawIngData();
|
|
|
+ const cursor = useCursor();
|
|
|
+ const enterDraw = () => {
|
|
|
+ const points = getLinePoints(data.value, line.value)
|
|
|
+ console.log(points, data.value, line.value)
|
|
|
+ const cdata: LineData = { ...data.value, points, lines: [] };
|
|
|
+ const point = reactive({ ...lineCenter(points), id: onlyId() });
|
|
|
+ const cIcons = icons.value.map((icon) => ({ ...icon, id: onlyId() }));
|
|
|
+ const iconInfos = icons.value.map((icon) => {
|
|
|
+ const mat = getLineIconMat(points, icon);
|
|
|
+ return {
|
|
|
+ position: { x: mat.m[4], y: mat.m[5] },
|
|
|
+ size: {
|
|
|
+ width: icon.endLen - icon.startLen,
|
|
|
+ height: icon.height,
|
|
|
+ },
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ const prev = { ...line.value, id: onlyId(), b: point.id };
|
|
|
+ const next = { ...line.value, id: onlyId(), a: point.id };
|
|
|
+ cdata.lines.push(prev, next);
|
|
|
+ cdata.points.push(point);
|
|
|
+
|
|
|
+ drawProps.value = { data: cdata, prev, next, point };
|
|
|
+ let isStop = false;
|
|
|
+ const afterUpdate = frameEebounce((position: Pos) => {
|
|
|
+ if (isStop) return;
|
|
|
+ snap.clear();
|
|
|
+ position = viewInvMat.value.point(position);
|
|
|
+
|
|
|
+ const mat = snap.move(generateSnapInfos([position], true, true));
|
|
|
+ Object.assign(point, mat ? mat.point(position) : position);
|
|
|
+
|
|
|
+ drawStore.lineIcon = [];
|
|
|
+ cIcons.forEach((icon, ndx) => {
|
|
|
+ const getAttach = genGetLineIconAttach(cdata, iconInfos[ndx].size, 200);
|
|
|
+ const attach = getAttach(iconInfos[ndx].position);
|
|
|
+ if (attach) {
|
|
|
+ const line = cdata.lines.find((item) => item.id === attach.lineId)!;
|
|
|
+ const snapLine = [
|
|
|
+ cdata.points.find((p) => p.id === line.a)!,
|
|
|
+ cdata.points.find((p) => p.id === line.b)!,
|
|
|
+ ];
|
|
|
+ const iconData = { ...icon, ...attach, __snapLine: snapLine };
|
|
|
+ drawStore.lineIcon!.push(iconData);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ pos.replay();
|
|
|
+ snapInfos.update([]);
|
|
|
+ return mergeFuns(
|
|
|
+ cursor.push("./icons/m_add.png"),
|
|
|
+ runHook(() =>
|
|
|
+ clickListener(stage.value!.getNode().container(), () => {
|
|
|
+ callback({
|
|
|
+ prev,
|
|
|
+ next,
|
|
|
+ point,
|
|
|
+ oldIcons: icons.value,
|
|
|
+ newIcons: drawStore.lineIcon!,
|
|
|
+ });
|
|
|
+ leave();
|
|
|
+ })
|
|
|
+ ),
|
|
|
+ watch(
|
|
|
+ pos,
|
|
|
+ (pos) => {
|
|
|
+ pos && afterUpdate(pos);
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+ ),
|
|
|
+ () => {
|
|
|
+ drawProps.value = undefined;
|
|
|
+ snapInfos.clear();
|
|
|
+ snap.clear();
|
|
|
+ isStop = true;
|
|
|
+ }
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const enter = () => {
|
|
|
+ __leave = mergeFuns(
|
|
|
+ () => (__leave = null),
|
|
|
+ mode.push(Mode.draw),
|
|
|
+ watch(
|
|
|
+ () => mode.include(Mode.draw),
|
|
|
+ (hasDraw, _, onCleanup) => {
|
|
|
+ hasDraw ? onCleanup(enterDraw()) : leave();
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+ )
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ leave,
|
|
|
+ enter,
|
|
|
+ drawProps,
|
|
|
+ };
|
|
|
+};
|