import { eqPoint, getLEJJoinNdxs, getLEJLineAngle, getLineEdgeJoinInfo, getLineEdges, LEJInfo, LEJLine, lineVector, Pos, verticalVector, } from "@/utils/math"; import { LineData, LineDataLine } from "."; import { getJoinLine, getLinePoints } from "./attach-server"; import { MathUtils } from "three"; import { diffArrayChange, round } from "@/utils/shared"; import { globalWatch, installGlobalVar } from "@/core/hook/use-global-vars"; import { useStore } from "@/core/store"; import { computed, nextTick, reactive, Ref, watch, watchEffect } from "vue"; import { Transform } from "konva/lib/Util"; import { sortFn } from "@/core/store/store"; import { getLineIconEndpoints } from "../line-icon"; import { useDrawIngData } from "@/core/hook/use-draw"; import { polygonDifference, polygonDifferenceOnly } from "@/utils/math-clip"; export const useGetExtendPolygon = (lineData: Ref) => { const minAngle = MathUtils.degToRad(0.1); const palAngle = MathUtils.degToRad(20); type JInfo = { lej: LEJInfo | undefined; diffPolygons?: Pos[][] }; const joinInfos = reactive({}) as Record>; const getInfoKey = (line: LEJLine) => line.points.reduce((t, p) => round(p.x, 3) + round(p.y, 3) + t, "") + line.width; const setLEJInfo = ( data: LineData, originLine: LineDataLine, targetLine: LineDataLine ) => { const origin = { points: getLinePoints(data, originLine), width: originLine.strokeWidth, }; const target = { points: getLinePoints(data, targetLine), width: targetLine.strokeWidth, }; const { originNdx } = getLEJJoinNdxs(origin.points, target.points); const lej = getLineEdgeJoinInfo(origin, target, minAngle, palAngle); const originKey = getInfoKey(origin); if (!(originKey in joinInfos)) { joinInfos[originKey] = {}; } if (!(originNdx in joinInfos[originKey])) { joinInfos[originKey][originNdx] = { lej }; } return joinInfos[originKey][originNdx]; }; const getLEJPolygon = (data: LineData, originLine: LineDataLine) => { const origin = { points: getLinePoints(data, originLine), width: originLine.strokeWidth, }; if (!origin.points[0] || !origin.points[1]) return []; const key = getInfoKey(origin); let originEdges: Pos[] = getLineEdges(origin.points, origin.width); const initOriginEdges = [...originEdges]; const jInfos: (JInfo | undefined)[] = [ joinInfos[key]?.[0], joinInfos[key]?.[1], ]; if (!(key in joinInfos)) { return originEdges; } for (const info of jInfos) { if (!info?.lej) continue; for (const rep of info.lej) { const ndx = originEdges.indexOf(initOriginEdges[rep.rep]); originEdges.splice(ndx, 1, ...rep.points); } } for (const info of jInfos) { if (!info?.diffPolygons) continue; originEdges = polygonDifferenceOnly(originEdges, info.diffPolygons); } return originEdges; }; const setManyJoinInfo = (data: LineData, lines: LineDataLine[]) => { type Select = ReturnType & { origin: LineDataLine; target: LineDataLine; }; const selectLEJLines = (lines: LineDataLine[]) => { let select: Select; let maxAngle = -999; for (let i = 0; i < lines.length; i++) { const line1 = getLinePoints(data, lines[i]); for (let j = i + 1; j < lines.length; j++) { const line2 = getLinePoints(data, lines[j]); const ejlAngle = getLEJLineAngle(line1, line2); if (ejlAngle.norAngle > maxAngle) { maxAngle = ejlAngle.norAngle; select = { ...ejlAngle, origin: lines[i], target: lines[j], }; } } } return select!; }; let diffPolygons: Pos[][] = []; const pointIds = lines.flatMap(l => [l.a, l.b]) const lineCount = lines.length while (lines.length) { if (lines.length > 1) { const select = selectLEJLines(lines)!; const origin = setLEJInfo(data, select.origin, select.target); const target = setLEJInfo(data, select.target, select.origin); lines = lines.filter( (line) => line !== select.origin && line !== select.target ); origin.diffPolygons = diffPolygons target.diffPolygons = diffPolygons diffPolygons = [ ...diffPolygons, getLEJPolygon(data, select.origin), getLEJPolygon(data, select.target), ]; } else { const key = getInfoKey({ points: getLinePoints(data, lines[0]), width: lines[0].strokeWidth, }); if (!(key in joinInfos)) { joinInfos[key] = {}; } const ndx = [lines[0].a, lines[0].b].findIndex( (id) => pointIds.filter(pid => id === pid).length === lineCount ); joinInfos[key][ndx] = { lej: undefined, diffPolygons }; lines = []; } } }; const genLEJLine = (lineData: LineData, line: LineDataLine) => ({ points: getLinePoints(lineData, line), width: line.strokeWidth, }); const init = (data: LineData) => { const watchLine = (line: LineDataLine) => { const joina = computed(() => getJoinLine(data, line, line.a)); const joinb = computed(() => getJoinLine(data, line, line.b)); const self = computed(() => genLEJLine(data, line)); const getWatchKey = () => { const lines = [ ...joina.value.map((l) => genLEJLine(data, l)), ...joinb.value.map((l) => genLEJLine(data, l)), self.value, ]; if (lines.some((l) => !l.points[0] || !l.points[1])) { return null; } else { return lines.map(getInfoKey).join(","); } }; return watch( getWatchKey, (wkey, _2, onCleanup) => { if (!wkey) return; const key = getInfoKey(self.value); const calcNdxs: number[] = []; if (!(key in joinInfos)) { joinInfos[key] = {}; calcNdxs.push(0, 1); } else { "0" in joinInfos[key] || calcNdxs.push(0); "1" in joinInfos[key] || calcNdxs.push(1); } for (const ndx of calcNdxs) { const joins = ndx === 0 ? joina.value : joinb.value; if (joins.length === 0) { joinInfos[key][ndx] = { lej: undefined }; } else if (joins.length !== 1) { setManyJoinInfo(data, [line, ...joins]); } else { setLEJInfo(data, line, joins[0]); setLEJInfo(data, joins[0], line); } } onCleanup(() => { delete joinInfos[key]; }); }, { immediate: true, flush: 'post' } ); }; let isStop = false; const stopMap = new WeakMap void>(); const stopWatch = watch( () => [...data.lines], async (newLines, oldLines = []) => { const { added, deleted } = diffArrayChange(newLines, oldLines); deleted.forEach((line) => { const fn = stopMap.get(line); fn && fn(); }); await nextTick(); if (!isStop) { added.forEach((line) => { stopMap.set(line, watchLine(line)); }); } deleted; }, { immediate: true } ); return () => { isStop = true; stopWatch(); data.lines.forEach((line) => { const fn = stopMap.get(line); fn && fn(); }); }; }; watch( lineData, (data, _, onCleanup) => { data && onCleanup(init(data)); }, { immediate: true } ); return (line: LineDataLine) => { const polygon = lineData.value ? getLEJPolygon(lineData.value, line) : []; return polygon }; }; // 计算与icon相差的多边形 export const useGetDiffIconPolygons = installGlobalVar(() => { const store = useStore(); const iconPolygons = reactive({}) as { [key in string]: Pos[] }; const icons = computed(() => store.getTypeItems("icon")); const line = computed(() => store.getTypeItems("line")[0]); const watchIcon = (id: string) => { const stopWatch = watchEffect(() => { const icon = icons.value.find((item) => item.id === id); if (!icon) { stopWatch(); delete iconPolygons[id]; return; } if (!line.value || sortFn(line.value, icon) > 0) { delete iconPolygons[id]; return; } const rect = [ { x: -icon.width / 2, y: -icon.height / 2 }, { x: icon.width / 2, y: -icon.height / 2 }, { x: icon.width / 2, y: icon.height / 2 }, { x: -icon.width / 2, y: icon.height / 2 }, ]; const mat = new Transform(icon.mat); iconPolygons[id] = rect.map((p) => mat.point(p)); }); }; const stopWatch = globalWatch( () => { return icons.value.map((item) => item.id); }, (ids, oIds = []) => { const { added, deleted } = diffArrayChange(ids, oIds); deleted.forEach((id) => { delete iconPolygons[id]; }); added.forEach(watchIcon); }, { immediate: true } ); return { var: (polygon: Pos[]) => { const targets = Object.values(iconPolygons); if (!targets.length) return [polygon]; return polygonDifference(polygon, targets); }, onDestroy: stopWatch, }; }); // 计算与icon相差的多边形 export const useGetDiffLineIconPolygons = ( line: LineDataLine, linePoints: Ref ) => { const store = useStore(); const drawStore = useDrawIngData(); const linevv = computed(() => verticalVector(lineVector(linePoints.value!))); const icons = computed(() => { const icons = store .getTypeItems("lineIcon") .concat(drawStore.lineIcon || []); return icons.filter((item) => !item.hide); }); const lineIcons = computed(() => icons.value.filter((icon) => icon.lineId === line.id) ); const interSteps = computed(() => { const lineSteps = lineIcons.value .map((icon) => { const openSide = icon.openSide; const endLen = icon.endLen || 0.001; const startLen = icon.startLen || 0.001; const inv = endLen < startLen; return { lens: inv ? [endLen, startLen] : [startLen, endLen], openSide: inv ? (openSide === "LEFT" ? "RIGHT" : "LEFT") : openSide, }; }) .sort((line1, line2) => line1.lens[0] - line2.lens[0]); if (!lineSteps.length) return []; const interSteps: { lens: number[]; openSide: "LEFT" | "RIGHT" }[] = []; let i = 0; do { const startStep = lineSteps[i].lens[0]; const openSide = lineSteps[i].openSide; let endStep = lineSteps[i].lens[1]; for (i++; i < lineSteps.length; i++) { if (lineSteps[i].lens[0] <= endStep) { if (lineSteps[i].lens[1] > endStep) { endStep = lineSteps[i].lens[1]; } } else { break; } } interSteps.push({ lens: [startStep, endStep], openSide, }); } while (i < lineSteps.length); return interSteps; }); const interLines = computed(() => interSteps.value.map((steps) => ({ openSide: steps.openSide, points: getLineIconEndpoints(linePoints.value!, { startLen: steps.lens[0], endLen: steps.lens[1], }), })) ); const stepLines = computed(() => { if (!linePoints.value?.length || !interLines.value.length) return []; const steps: Pos[][] = []; if (!eqPoint(linePoints.value[0], interLines.value[0].points[0])) { steps.push([linePoints.value[0], interLines.value[0].points[0]]); } let start = interLines.value[0].points[1]; let i = 1; for (; i < interLines.value.length; i++) { const iLine = interLines.value[i]; steps.push([start, iLine.points[0]]); start = iLine.points[1]; } if (!eqPoint(start, linePoints.value[1])) { steps.push([start, linePoints.value[1]]); } return steps; }); const subStepsLines = computed(() => { return interLines.value.map((il) => { return il.openSide === "RIGHT" ? il.points : [...il.points].reverse(); }); }); const interPolygons = computed(() => { return interLines.value.map((il) => { const interLine = il.points; const topOffset = linevv.value.clone().multiplyScalar(line.strokeWidth); const botOffset = topOffset.clone().multiplyScalar(-1); return [ topOffset.clone().add(interLine[0]), topOffset.clone().add(interLine[1]), botOffset.clone().add(interLine[1]), botOffset.clone().add(interLine[0]), ]; }); }); return { diff: (polygon: Pos[]) => { const result = interPolygons.value.length ? polygonDifference(polygon, interPolygons.value) : [polygon]; return result; }, subSteps: subStepsLines, steps: stepLines, }; };