829 lines
26 KiB
JavaScript
829 lines
26 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 { calculateAngle, drawGabledRoof, drawRidgeRoof, drawShedRoof, toGeoJSON } from '@/util/qpolygon-utils'
|
|
import * as turf from '@turf/turf'
|
|
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
|
import Big from 'big.js'
|
|
|
|
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|
type: 'QPolygon',
|
|
// lines: [],
|
|
// texts: [],
|
|
id: null,
|
|
length: 0,
|
|
// hips: [],
|
|
// ridges: [],
|
|
// connectRidges: [],
|
|
// cells: [],
|
|
parentId: null,
|
|
// innerLines: [],
|
|
// children: [],
|
|
initOptions: null,
|
|
direction: null,
|
|
arrow: null,
|
|
toFixed: 1,
|
|
initialize: function (points, options, canvas) {
|
|
this.lines = []
|
|
this.texts = []
|
|
this.hips = []
|
|
this.ridges = []
|
|
this.cells = []
|
|
this.innerLines = []
|
|
this.children = []
|
|
this.separatePolygon = []
|
|
this.toFixed = options.toFixed ?? 1
|
|
this.baseLines = []
|
|
// this.colorLines = []
|
|
|
|
// 소수점 전부 제거
|
|
points.forEach((point) => {
|
|
point.x = Number(point.x.toFixed(this.toFixed))
|
|
point.y = Number(point.y.toFixed(this.toFixed))
|
|
})
|
|
options.selectable = options.selectable ?? true
|
|
options.sort = options.sort ?? true
|
|
options.parentId = options.parentId ?? null
|
|
|
|
if (!options.sort && points.length <= 8) {
|
|
points = sortedPointLessEightPoint(points)
|
|
} else {
|
|
let isDiagonal = false
|
|
points.forEach((point, i) => {
|
|
if (isDiagonal) {
|
|
return
|
|
}
|
|
const nextPoint = points[(i + 1) % points.length]
|
|
const angle = calculateAngle(point, nextPoint)
|
|
if (!(Math.abs(angle) === 0 || Math.abs(angle) === 180 || Math.abs(angle) === 90)) {
|
|
isDiagonal = true
|
|
}
|
|
})
|
|
|
|
if (!isDiagonal) {
|
|
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.initOptions = options
|
|
|
|
this.initLines()
|
|
this.init()
|
|
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
|
|
},
|
|
init: function () {
|
|
this.addLengthText()
|
|
|
|
this.on('moving', () => {
|
|
this.addLengthText()
|
|
})
|
|
|
|
this.on('modified', (e) => {
|
|
this.addLengthText()
|
|
})
|
|
|
|
this.on('selected', () => {
|
|
Object.keys(this.controls).forEach((controlKey) => {
|
|
this.setControlVisible(controlKey, false)
|
|
})
|
|
this.set({ hasBorders: false })
|
|
})
|
|
|
|
this.on('removed', () => {
|
|
// const children = getAllRelatedObjects(this.id, this.canvas)
|
|
const children = this.canvas.getObjects().filter((obj) => obj.parentId === this.id)
|
|
|
|
children.forEach((child) => {
|
|
this.canvas.remove(child)
|
|
|
|
//그룹일때
|
|
if (child.hasOwnProperty('_objects')) {
|
|
child._objects.forEach((obj) => {
|
|
if (obj.hasOwnProperty('texts')) {
|
|
obj.texts.forEach((text) => {
|
|
this.canvas?.remove(text)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
//QPolygon 좌표 이동시 좌표 재계산
|
|
this.on('polygonMoved', () => {
|
|
//폴리곤일때만 사용
|
|
let matrix = this.calcTransformMatrix()
|
|
|
|
let transformedPoints = this.get('points')
|
|
.map((p) => {
|
|
return new fabric.Point(p.x - this.pathOffset.x, p.y - this.pathOffset.y)
|
|
})
|
|
.map((p) => {
|
|
return fabric.util.transformPoint(p, matrix)
|
|
})
|
|
this.points = transformedPoints
|
|
const { left, top } = this.calcOriginCoords()
|
|
this.set('pathOffset', { x: left, y: top })
|
|
this.setCoords()
|
|
this.initLines()
|
|
})
|
|
|
|
// polygon.fillCell({ width: 50, height: 30, padding: 10 })
|
|
},
|
|
|
|
initLines() {
|
|
let attributes = null
|
|
if (this.lines.length > 0) {
|
|
attributes = this.lines.map((line) => line.attributes)
|
|
}
|
|
|
|
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,
|
|
attributes: attributes
|
|
? attributes[i]
|
|
: {
|
|
offset: 0,
|
|
},
|
|
textVisible: false,
|
|
parent: this,
|
|
parentId: this.id,
|
|
direction: getDirectionByPoint(point, nextPoint),
|
|
idx: i + 1,
|
|
})
|
|
line.startPoint = point
|
|
line.endPoint = nextPoint
|
|
this.lines.push(line)
|
|
})
|
|
},
|
|
|
|
/**
|
|
* 보조선 그리기
|
|
* @param settingModalFirstOptions
|
|
*/
|
|
drawHelpLine(settingModalFirstOptions) {
|
|
/* innerLines 초기화 */
|
|
this.innerLines.forEach((line) => {
|
|
this.canvas.remove(line)
|
|
})
|
|
this.canvas.renderAll()
|
|
|
|
let textMode = 'plane'
|
|
|
|
const dimensionDisplay = settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id
|
|
? settingModalFirstOptions?.dimensionDisplay.find((opt) => opt.selected).id
|
|
: 1
|
|
switch (dimensionDisplay) {
|
|
case 1:
|
|
textMode = 'plane'
|
|
break
|
|
case 2:
|
|
textMode = 'actual'
|
|
break
|
|
case 3:
|
|
textMode = 'none'
|
|
break
|
|
}
|
|
|
|
const types = []
|
|
this.lines.forEach((line) => types.push(line.attributes.type))
|
|
|
|
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
|
|
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
|
|
|
|
// const isEaves = types.every((type) => eavesType.includes(type))
|
|
const gableOdd = types.filter((type, i) => i % 2 === 0)
|
|
const gableEven = types.filter((type, i) => i % 2 === 1)
|
|
const hasShed = types.includes(LINE_TYPE.WALLLINE.SHED)
|
|
|
|
// A형, B형 박공 지붕
|
|
if (
|
|
(gableOdd.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableEven.every((type) => gableType.includes(type))) ||
|
|
(gableEven.every((type) => type === LINE_TYPE.WALLLINE.EAVES) && gableOdd.every((type) => gableType.includes(type)))
|
|
) {
|
|
drawGabledRoof(this.id, this.canvas, textMode)
|
|
} else if (hasShed) {
|
|
const sheds = this.lines.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.SHED)
|
|
const areLinesParallel = function (line1, line2) {
|
|
const angle1 = calculateAngle(line1.startPoint, line1.endPoint)
|
|
const angle2 = calculateAngle(line2.startPoint, line2.endPoint)
|
|
return angle1 === angle2
|
|
}
|
|
|
|
let isShedRoof = true
|
|
sheds.forEach((shed, i) => {
|
|
isShedRoof = areLinesParallel(shed, sheds[(i + 1) % sheds.length])
|
|
})
|
|
if (isShedRoof) {
|
|
const eaves = this.lines
|
|
.filter((line) => line.attributes !== undefined && line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
|
.filter((line) => {
|
|
const angle1 = calculateAngle(sheds[0].startPoint, sheds[0].endPoint)
|
|
const angle2 = calculateAngle(line.startPoint, line.endPoint)
|
|
if (Math.abs(angle1 - angle2) === 180) {
|
|
return line
|
|
}
|
|
})
|
|
if (eaves.length > 0) {
|
|
const gables = this.lines.filter((line) => sheds.includes(line) === false && eaves.includes(line) === false)
|
|
const isGable = gables.every((line) => gableType.includes(line.attributes.type))
|
|
if (isGable) {
|
|
drawShedRoof(this.id, this.canvas, textMode)
|
|
} else {
|
|
drawRidgeRoof(this.id, this.canvas, textMode)
|
|
}
|
|
} else {
|
|
drawRidgeRoof(this.id, this.canvas, textMode)
|
|
}
|
|
} else {
|
|
drawRidgeRoof(this.id, this.canvas, textMode)
|
|
}
|
|
} else {
|
|
drawRidgeRoof(this.id, this.canvas, textMode)
|
|
}
|
|
},
|
|
|
|
addLengthText() {
|
|
if ([POLYGON_TYPE.MODULE, 'arrow', POLYGON_TYPE.MODULE_SETUP_SURFACE, POLYGON_TYPE.OBJECT_SURFACE].includes(this.name)) {
|
|
return
|
|
}
|
|
|
|
if (!this.fontSize) {
|
|
return
|
|
}
|
|
this.canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
|
.forEach((text) => {
|
|
this.canvas.remove(text)
|
|
})
|
|
|
|
let points = this.getCurrentPoints()
|
|
|
|
this.texts = []
|
|
|
|
points.forEach((start, i) => {
|
|
const end = points[(i + 1) % points.length]
|
|
const dx = Big(end.x).minus(Big(start.x))
|
|
const dy = Big(end.y).minus(Big(start.y))
|
|
const length = dx.pow(2).plus(dy.pow(2)).sqrt().times(10).round().toNumber()
|
|
|
|
let midPoint
|
|
|
|
midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2)
|
|
|
|
const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber()
|
|
|
|
// Create new text object if it doesn't exist
|
|
const text = new fabric.Text(length.toString(), {
|
|
left: midPoint.x,
|
|
top: midPoint.y,
|
|
fontSize: this.fontSize,
|
|
parentId: this.id,
|
|
minX: Math.min(start.x, end.x),
|
|
maxX: Math.max(start.x, end.x),
|
|
minY: Math.min(start.y, end.y),
|
|
maxY: Math.max(start.y, end.y),
|
|
parentDirection: getDirectionByPoint(start, end),
|
|
parentDegree: degree,
|
|
dirty: true,
|
|
editable: true,
|
|
selectable: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
idx: i,
|
|
actualSize: this.lines[i].attributes?.actualSize,
|
|
planeSize: this.lines[i].attributes?.planeSize,
|
|
name: 'lengthText',
|
|
parent: this,
|
|
})
|
|
|
|
this.texts.push(text)
|
|
this.canvas.add(text)
|
|
this.canvas.renderAll()
|
|
})
|
|
},
|
|
setFontSize(fontSize) {
|
|
this.fontSize = fontSize
|
|
this.canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.name === 'lengthText' && obj.parent === this)
|
|
.forEach((text) => {
|
|
text.set({ fontSize: fontSize })
|
|
})
|
|
},
|
|
_render: function (ctx) {
|
|
this.callSuper('_render', ctx)
|
|
},
|
|
_set: function (key, value) {
|
|
this.callSuper('_set', key, value)
|
|
},
|
|
setCanvas(canvas) {
|
|
this.canvas = canvas
|
|
},
|
|
fillCellABType(
|
|
cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1, isCellCenter: false },
|
|
) {
|
|
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로 나누면 반높이를 넣어서 중간으로 정렬
|
|
|
|
let centerHeight = rows > 1 ? (boundingBoxHeight - (rectHeight + cell.padding / 2) * rows) / 2 : (boundingBoxHeight - rectHeight * rows) / 2 //rows 1개 이상이면 cell 을 반 나눠서 중간을 맞춘다
|
|
let centerWidth = cols > 1 ? (boundingBoxWidth - (rectWidth + cell.padding / 2) * cols) / 2 : (boundingBoxWidth - rectWidth * cols) / 2
|
|
|
|
const drawCellsArray = [] //그려진 셀의 배열
|
|
|
|
let idx = 1
|
|
let startXPos, startYPos
|
|
|
|
for (let i = 0; i < cols; i++) {
|
|
for (let j = 0; j < rows; j++) {
|
|
const rectPoints = []
|
|
|
|
if (cell.referenceDirection !== 'none') {
|
|
//4각형은 기준점이 없다
|
|
|
|
if (cell.referenceDirection === 'top') {
|
|
//top, bottom은 A패턴만
|
|
if (cell.wallDirection === 'left') {
|
|
startXPos = minX + i * rectWidth
|
|
startYPos = minY + j * rectHeight
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding //옆으로 패딩
|
|
}
|
|
} else {
|
|
startXPos = maxX - (1 + i) * rectWidth - 0.01
|
|
startYPos = minY + j * rectHeight + 0.01
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
|
}
|
|
}
|
|
|
|
if (j > 0) {
|
|
startYPos = startYPos + j * cell.padding
|
|
}
|
|
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos + rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos + rectHeight },
|
|
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
|
)
|
|
} else if (cell.referenceDirection === 'bottom') {
|
|
if (cell.wallDirection === 'left') {
|
|
startXPos = minX + i * rectWidth
|
|
startYPos = maxY - j * rectHeight - 0.01
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding
|
|
}
|
|
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos + rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos - rectHeight },
|
|
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
|
)
|
|
} else {
|
|
startXPos = maxX - i * rectWidth - 0.01
|
|
startYPos = maxY - j * rectHeight - 0.01
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos - i * cell.padding
|
|
}
|
|
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos - rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos - rectHeight },
|
|
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
|
)
|
|
startXPos = startXPos - rectWidth //우 -> 좌 들어가야해서 마이너스 처리
|
|
}
|
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
|
|
|
if (j > 0) {
|
|
startYPos = startYPos - j * cell.padding
|
|
}
|
|
} else if (cell.referenceDirection === 'left') {
|
|
//여기서부턴 B패턴임
|
|
if (cell.wallDirection === 'top') {
|
|
startXPos = minX + i * rectWidth
|
|
startYPos = minY + j * rectHeight
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding //밑으로
|
|
}
|
|
if (j > 0) {
|
|
startYPos = startYPos + j * cell.padding //옆으로 패딩
|
|
}
|
|
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos + rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos + rectHeight },
|
|
{ x: startXPos + rectWidth, y: startYPos + rectHeight },
|
|
)
|
|
} else {
|
|
startXPos = minX + i * rectWidth
|
|
startYPos = maxY - j * rectHeight - 0.01
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding
|
|
}
|
|
|
|
if (j > 0) {
|
|
startYPos = startYPos - j * cell.padding
|
|
}
|
|
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos + rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos - rectHeight },
|
|
{ x: startXPos + rectWidth, y: startYPos - rectHeight },
|
|
)
|
|
|
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
|
}
|
|
} else if (cell.referenceDirection === 'right') {
|
|
if (cell.wallDirection === 'top') {
|
|
startXPos = maxX - i * rectWidth - 0.01
|
|
startYPos = minY + j * rectHeight + 0.01
|
|
|
|
if (j > 0) {
|
|
startYPos = startYPos + j * cell.padding //위에서 밑으로라 +
|
|
}
|
|
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos - rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos + rectHeight },
|
|
{ x: startXPos - rectWidth, y: startYPos + rectHeight },
|
|
)
|
|
} else {
|
|
startXPos = maxX - i * rectWidth - 0.01
|
|
startYPos = maxY - j * rectHeight - 0.01
|
|
|
|
if (j > 0) {
|
|
startYPos = startYPos - j * cell.padding
|
|
}
|
|
rectPoints.push(
|
|
{ x: startXPos, y: startYPos },
|
|
{ x: startXPos - rectWidth, y: startYPos },
|
|
{ x: startXPos, y: startYPos - rectHeight },
|
|
{ x: startXPos - rectWidth, y: startYPos - rectHeight },
|
|
)
|
|
startYPos = startYPos - rectHeight //밑에서 위로 올라가는거라 마이너스 처리
|
|
}
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos - i * cell.padding //옆으로 패딩
|
|
}
|
|
startXPos = startXPos - rectWidth // 우측에서 -> 좌측으로 그려짐
|
|
}
|
|
} else {
|
|
// centerWidth = 0 //나중에 중간 정렬 이면 어쩌구 함수 만들어서 넣음
|
|
if (['left', 'right'].includes(cell.wallDirection)) {
|
|
centerWidth = cell.isCellCenter ? centerWidth : 0
|
|
} else if (['top', 'bottom'].includes(cell.wallDirection)) {
|
|
centerHeight = cell.isCellCenter ? centerHeight : 0
|
|
}
|
|
|
|
if (cell.wallDirection === 'left') {
|
|
startXPos = minX + i * rectWidth + centerWidth
|
|
startYPos = minY + j * rectHeight + centerHeight
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding
|
|
}
|
|
if (j > 0 && j < rows) {
|
|
startYPos = startYPos + j * cell.padding
|
|
}
|
|
} else if (cell.wallDirection === 'right') {
|
|
startXPos = maxX - (1 + i) * rectWidth - 0.01 - centerWidth
|
|
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
|
if (i > 0) {
|
|
startXPos = startXPos - i * cell.padding
|
|
}
|
|
if (j > 0 && j < rows) {
|
|
startYPos = startYPos + j * cell.padding
|
|
}
|
|
} else if (cell.wallDirection === 'top') {
|
|
startXPos = minX + i * rectWidth - 0.01 + centerWidth
|
|
startYPos = minY + j * rectHeight + 0.01 + centerHeight
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding
|
|
}
|
|
if (j > 0 && j < rows) {
|
|
startYPos = startYPos + j * cell.padding
|
|
}
|
|
} else if (cell.wallDirection === 'bottom') {
|
|
startXPos = minX + i * rectWidth + 0.01 + centerWidth
|
|
startYPos = maxY - (j + 1) * rectHeight - 0.01 - centerHeight
|
|
|
|
if (i > 0) {
|
|
startXPos = startXPos + i * cell.padding
|
|
}
|
|
|
|
if (j > 0 && j < rows) {
|
|
startYPos = startYPos - j * cell.padding
|
|
}
|
|
}
|
|
}
|
|
|
|
const allPointsInside = rectPoints.every((point) => this.inPolygonABType(point.x, point.y, points))
|
|
|
|
if (allPointsInside) {
|
|
//먼저 그룹화를 시켜놓고 뒤에서 글씨를 넣어서 변경한다
|
|
const text = new fabric.Text(``, {
|
|
fontFamily: 'serif',
|
|
fontSize: 30,
|
|
fill: 'black',
|
|
type: 'cellText',
|
|
})
|
|
|
|
const rect = new fabric.Rect({
|
|
// left: startXPos,
|
|
// top: startYPos,
|
|
width: rectWidth,
|
|
height: rectHeight,
|
|
fill: '#BFFD9F',
|
|
stroke: 'black',
|
|
selectable: true, // 선택 가능하게 설정
|
|
// lockMovementX: true, // X 축 이동 잠금
|
|
// lockMovementY: true, // Y 축 이동 잠금
|
|
// lockRotation: true, // 회전 잠금
|
|
// lockScalingX: true, // X 축 크기 조정 잠금
|
|
// lockScalingY: true, // Y 축 크기 조정 잠금
|
|
opacity: 0.8,
|
|
name: 'cell',
|
|
idx: idx,
|
|
type: 'cellRect',
|
|
})
|
|
|
|
const group = new fabric.Group([rect, text], {
|
|
left: startXPos,
|
|
top: startYPos,
|
|
})
|
|
|
|
idx++
|
|
drawCellsArray.push(group) //배열에 넣어서 반환한다
|
|
this.canvas.add(group)
|
|
this.canvas?.renderAll()
|
|
}
|
|
}
|
|
}
|
|
|
|
this.cells = drawCellsArray
|
|
return drawCellsArray
|
|
},
|
|
|
|
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
|
|
},
|
|
containsPoint: function (point) {
|
|
if (this.name === POLYGON_TYPE.ROOF && this.isFixed) {
|
|
const isInside = this.inPolygon(point)
|
|
this.set('selectable', isInside)
|
|
return isInside
|
|
} else {
|
|
return this.callSuper('containsPoint', point)
|
|
}
|
|
},
|
|
|
|
inPolygonABType(x, y, polygon) {
|
|
let inside = false
|
|
let n = polygon.length
|
|
|
|
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
let xi = polygon[i].x
|
|
let yi = polygon[i].y
|
|
let xj = polygon[j].x
|
|
let yj = polygon[j].y
|
|
|
|
// console.log('xi : ', xi, 'yi : ', yi, 'xj : ', xj, 'yj : ', yj)
|
|
|
|
let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
|
|
if (intersect) inside = !inside
|
|
}
|
|
|
|
return inside
|
|
},
|
|
|
|
inPolygon2(rectPoints) {
|
|
const polygonCoords = toGeoJSON(this.points)
|
|
const rectCoords = toGeoJSON(rectPoints)
|
|
|
|
const outerPolygon = turf.polygon([polygonCoords])
|
|
const innerPolygon = turf.polygon([rectCoords])
|
|
// 각 점이 다각형 내부에 있는지 확인
|
|
const allPointsInside = rectCoords.every((coord) => {
|
|
const point = turf.point(coord)
|
|
return turf.booleanPointInPolygon(point, outerPolygon)
|
|
})
|
|
|
|
// 사각형의 변 정의
|
|
const rectEdges = [
|
|
[rectCoords[0], rectCoords[1]],
|
|
[rectCoords[1], rectCoords[2]],
|
|
[rectCoords[2], rectCoords[3]],
|
|
[rectCoords[3], rectCoords[0]],
|
|
]
|
|
|
|
// 다각형의 변 정의
|
|
const outerEdges = turf.lineString(outerPolygon.geometry.coordinates[0])
|
|
|
|
// 사각형의 변들이 다각형의 변과 교차하는지 확인
|
|
const noEdgesIntersect = rectEdges.every((edge) => {
|
|
const line = turf.lineString(edge)
|
|
const intersects = turf.lineIntersect(line, outerEdges)
|
|
return intersects.features.length === 0
|
|
})
|
|
|
|
return allPointsInside && noEdgesIntersect
|
|
},
|
|
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 pathOffset = this.get('pathOffset')
|
|
const matrix = this.calcTransformMatrix()
|
|
return this.get('points')
|
|
.map(function (p) {
|
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
|
})
|
|
.map(function (p) {
|
|
return fabric.util.transformPoint(p, matrix)
|
|
})
|
|
},
|
|
setWall: function (wall) {
|
|
this.wall = wall
|
|
},
|
|
setViewLengthText(isView) {
|
|
this.canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id)
|
|
.forEach((text) => {
|
|
text.set({ visible: isView })
|
|
})
|
|
},
|
|
setScaleX(scale) {
|
|
this.scaleX = scale
|
|
this.addLengthText()
|
|
},
|
|
setScaleY(scale) {
|
|
this.scaleY = scale
|
|
this.addLengthText()
|
|
},
|
|
|
|
calcOriginCoords() {
|
|
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))
|
|
|
|
let left = 0
|
|
let top = 0
|
|
|
|
if (this.originX === 'center') {
|
|
left = (minX + maxX) / 2
|
|
} else if (this.originX === 'left') {
|
|
left = minX
|
|
} else if (this.originX === 'right') {
|
|
left = maxX
|
|
}
|
|
|
|
if (this.originY === 'center') {
|
|
top = (minY + maxY) / 2
|
|
} else if (this.originY === 'top') {
|
|
top = minY
|
|
} else if (this.originY === 'bottom') {
|
|
top = maxY
|
|
}
|
|
|
|
return { left, top }
|
|
},
|
|
})
|