import { fabric } from 'fabric' import { distanceBetweenPoints } from '@/util/canvas-util' export default class QPolygon extends fabric.Polygon { #viewLengthText #text = [] #fontSize type = 'QPolygon' constructor(points, option) { if (!option.fontSize) { throw new Error('Font size is required.') } super(points, option) this.#fontSize = option.fontSize this.#init(points) this.#addControl() } #init(points) { this.#viewLengthText = points.viewLengthText ?? true } setViewLengthText(bool) { this.#viewLengthText = bool this.#addLengthText() } #addControl() { this.on('removed', () => { if (this.#text.length > 0) { this.#text.forEach((text) => { this.canvas.remove(text) }) this.#text = [] } }) this.on('added', () => { this.#addLengthText() }) this.on('modified', (e) => { this.#addLengthText() }) this.on('scaling', (e) => { this.#addLengthText() }) this.on('moving', () => { this.#addLengthText() }) } setTexts(texts) { this.#text = texts } getTexts() { return this.#text } #addLengthText() { return false if (this.#text.length > 0) { this.#text.forEach((text) => { this.canvas.remove(text) }) this.#text = [] } if (!this.#viewLengthText) { return } const scaleX = this.scaleX const scaleY = this.scaleY const points = this.points for (let i = 0; i < points.length; i++) { const start = points[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) const midPoint = new fabric.Point( (start.x + end.x) / 2, (start.y + end.y) / 2, ) const text = new fabric.Text(length.toFixed(0), { left: midPoint.x, top: midPoint.y, fontSize: this.#fontSize, selectable: false, }) this.#text.push(text) this.canvas.add(text) } } setFontSize(fontSize) { this.#fontSize = fontSize this.#addLengthText() } getCurrentPoints() { const scaleX = this.scaleX const scaleY = this.scaleY return this.points.map((point) => { return { x: point.x * scaleX + this.left, y: point.y * scaleY + this.top, } }) } #distanceFromEdge(point) { const vertices = this.points 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 } fillCell(cell = { width: 50, height: 100, padding: 10 }) { const points = this.points let bounds try { bounds = fabric.util.makeBoundingBoxFromPoints(points) } catch (error) { alert('다각형의 꼭지점이 4개 이상이어야 합니다.') return } for ( let x = bounds.left; x < bounds.left + bounds.width; x += cell.width + cell.padding ) { for ( let y = bounds.top; y < bounds.top + bounds.height; y += cell.height + cell.padding ) { const rect = new fabric.Rect({ left: x, top: y, width: cell.width, height: cell.height, fill: 'transparent', stroke: 'black', selectable: false, }) const rectPoints = [ new fabric.Point(rect.left, rect.top), new fabric.Point(rect.left + rect.width, rect.top), new fabric.Point(rect.left, rect.top + rect.height), new fabric.Point(rect.left + rect.width, rect.top + rect.height), ] const isInside = rectPoints.every( (rectPoint) => this.inPolygon(rectPoint) && this.#distanceFromEdge(rectPoint) >= cell.padding, ) if (isInside) { this.canvas.add(rect) } } } this.canvas.renderAll() } inPolygon(point) { const vertices = this.points 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 } getCurrentOptions = () => { return this.options } }