2024-08-01 13:55:50 +09:00

335 lines
9.1 KiB
JavaScript

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() {},
})