123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769 |
- 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",
- props: {
- proportion: true,
- },
-
- 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,
- };
- };
|