import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' import { drawHelpLineInHexagon } from '@/util/qpolygon-utils' import { drawHippedRoof } from '@/util/qpolygon-utils' export const QPolygon = fabric.util.createClass(fabric.Polygon, { type: 'QPolygon', lines: [], texts: [], id: null, length: 0, hips: [], ridges: [], connectRidges: [], innerLines: [], initialize: function (points, options, canvas) { // 소수점 전부 제거 points.forEach((point) => { point.x = Math.round(point.x) point.y = Math.round(point.y) }) if (points.length <= 8) { points = sortedPointLessEightPoint(points) } else { points = sortedPoints(points) } this.callSuper('initialize', points, options) if (options.id) { this.id = options.id } else { this.id = uuidv4() } if (canvas) { this.canvas = canvas } this.init() this.initLines() this.setShape() }, setShape() { let shape = 0 if (this.lines.length !== 6) { return } //외각선 기준 const topIndex = findTopTwoIndexesByDistance(this.lines).sort((a, b) => a - b) //배열중에 큰 2값을 가져옴 TODO: 나중에는 인자로 받아서 다각으로 수정 해야됨 //일단 배열 6개 짜리 기준의 선 번호 if (topIndex[0] === 4) { if (topIndex[1] === 5) { //1번 shape = 1 } } else if (topIndex[0] === 1) { //4번 if (topIndex[1] === 2) { shape = 4 } } else if (topIndex[0] === 0) { if (topIndex[1] === 1) { //2번 shape = 2 } else if (topIndex[1] === 5) { //3번 shape = 3 } } this.shape = shape }, toObject: function (propertiesToInclude) { return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { type: this.type, text: this.text, }) }, init: function () { this.addLengthText() this.on('moving', () => { this.addLengthText() }) this.on('modified', (e) => { this.addLengthText() }) this.on('selected', () => { Object.keys(this.controls).forEach((controlKey) => { if (controlKey !== 'ml' && controlKey !== 'mr') { this.setControlVisible(controlKey, false) } }) }) this.on('removed', () => { const thisText = this.canvas.getObjects().filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id) thisText.forEach((text) => { this.canvas.remove(text) }) this.texts = null }) }, initLines() { this.lines = [] this.points.forEach((point, i) => { const nextPoint = this.points[(i + 1) % this.points.length] const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { stroke: this.stroke, strokeWidth: this.strokeWidth, fontSize: this.fontSize, direction: getDirectionByPoint(point, nextPoint), idx: i, }) this.lines.push(line) }) }, // 보조선 그리기 drawHelpLine(chon = 4) { // drawHelpLineInHexagon(this, chon) drawHippedRoof(this, chon) }, addLengthText() { let points = this.points points.forEach((start, i) => { const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i) const end = points[(i + 1) % points.length] const dx = end.x - start.x const dy = end.y - start.y const length = Math.sqrt(dx * dx + dy * dy) if (thisText) { thisText.set({ left: (start.x + points[(i + 1) % points.length].x) / 2, top: (start.y + points[(i + 1) % points.length].y) / 2, text: length.toFixed(0), }) return } const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) // Create new text object if it doesn't exist const text = new fabric.Text(length.toFixed(0), { left: midPoint.x, top: midPoint.y, fontSize: this.fontSize, selectable: false, parentId: this.id, idx: i, name: 'lengthText', }) this.texts.push(text) this.canvas.add(text) this.canvas.renderAll() }) }, setFontSize(fontSize) { this.fontSize = fontSize this.text.set({ fontSize }) }, _render: function (ctx) { this.callSuper('_render', ctx) }, _set: function (key, value) { this.callSuper('_set', key, value) }, setCanvas(canvas) { this.canvas = canvas }, fillCell(cell = { width: 50, height: 100, padding: 10 }) { const points = this.points const minX = Math.min(...points.map((p) => p.x)) const maxX = Math.max(...points.map((p) => p.x)) const minY = Math.min(...points.map((p) => p.y)) const maxY = Math.max(...points.map((p) => p.y)) const boundingBoxWidth = maxX - minX const boundingBoxHeight = maxY - minY const rectWidth = cell.width const rectHeight = cell.height const cols = Math.floor((boundingBoxWidth + cell.padding) / (rectWidth + cell.padding)) const rows = Math.floor((boundingBoxHeight + cell.padding) / (rectHeight + cell.padding)) //전체 높이에서 패딩을 포함하고 rows를 곱해서 여백길이를 계산 후에 2로 나누면 반높이를 넣어서 중간으로 정렬 const tmpHeight = (boundingBoxHeight - (rectHeight + cell.padding) * rows) / 2 //센터 정렬시에 쓴다 체크박스가 존재함 TODO: if문 추가해서 정렬해야함 let tmpWidth = (boundingBoxWidth - (rectWidth + cell.padding) * cols) / 2 for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { const rectLeft = minX + i * (rectWidth + cell.padding) + tmpWidth const rectTop = minY + j * (rectHeight + cell.padding) + tmpHeight const rectPoints = [ { x: rectLeft, y: rectTop }, { x: rectLeft + rectWidth, y: rectTop }, { x: rectLeft, y: rectTop + rectHeight }, { x: rectLeft + rectWidth, y: rectTop + rectHeight }, ] const allPointsInside = rectPoints.every((point) => this.inPolygon(point)) if (allPointsInside) { const rect = new fabric.Rect({ left: rectLeft, top: rectTop, width: rectWidth, height: rectHeight, fill: '#BFFD9F', selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 opacity: 0.6, }) this.canvas.add(rect) } } } this.canvas.renderAll() }, inPolygon(point) { const vertices = this.getCurrentPoints() let intersects = 0 for (let i = 0; i < vertices.length; i++) { let vertex1 = vertices[i] let vertex2 = vertices[(i + 1) % vertices.length] if (vertex1.y > vertex2.y) { let tmp = vertex1 vertex1 = vertex2 vertex2 = tmp } if (point.y === vertex1.y || point.y === vertex2.y) { point.y += 0.01 } if (point.y <= vertex1.y || point.y > vertex2.y) { continue } let xInt = ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / (vertex2.y - vertex1.y) + vertex1.x if (xInt < point.x) { intersects++ } } return intersects % 2 === 1 }, distanceFromEdge(point) { const vertices = this.getCurrentPoints() let minDistance = Infinity for (let i = 0; i < vertices.length; i++) { let vertex1 = vertices[i] let vertex2 = vertices[(i + 1) % vertices.length] const dx = vertex2.x - vertex1.x const dy = vertex2.y - vertex1.y const t = ((point.x - vertex1.x) * dx + (point.y - vertex1.y) * dy) / (dx * dx + dy * dy) let closestPoint if (t < 0) { closestPoint = vertex1 } else if (t > 1) { closestPoint = vertex2 } else { closestPoint = new fabric.Point(vertex1.x + t * dx, vertex1.y + t * dy) } const distance = distanceBetweenPoints(point, closestPoint) if (distance < minDistance) { minDistance = distance } } return minDistance }, getCurrentPoints() { const scaleX = this.scaleX const scaleY = this.scaleY const left = this.left const top = this.top // 시작점 const point = this.points[0] const movingX = left - point.x * scaleX const movingY = top - point.y * scaleY return this.points.map((point) => { return { x: point.x * scaleX + movingX, y: point.y * scaleY + movingY, } }) }, setWall: function (wall) { this.wall = wall }, setViewLengthText(isView) { this.texts.forEach((text) => { text.set({ visible: isView }) }) }, divideLine() {}, })