qcast-front/src/hooks/useMode.js

1105 lines
34 KiB
JavaScript

import { useEffect, useRef, useState } from 'react'
import QRect from '@/components/fabric/QRect'
import QPolygon from '@/components/fabric/QPolygon'
import {
getStartIndex,
rearrangeArray,
findTopTwoIndexesByDistance,
getDirection,
getCenterPoint,
} from '@/util/canvas-util'
import { useRecoilState, useSetRecoilState } from 'recoil'
import {
fontSizeState,
roofState,
sortedPolygonArray,
wallState,
} from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine'
export const Mode = {
DRAW_LINE: 'drawLine', // 기준선 긋기모드
EDIT: 'edit',
TEMPLATE: 'template',
PATTERNA: 'patterna',
PATTERNB: 'patternb',
TEXTBOX: 'textbox',
DRAW_RECT: 'drawRect',
DEFAULT: 'default',
}
export function useMode() {
const [mode, setMode] = useState()
const points = useRef([])
const historyPoints = useRef([])
const historyLines = useRef([])
const [canvas, setCanvas] = useState(null)
const [zoom, setZoom] = useState(100)
const [fontSize] = useRecoilState(fontSizeState)
const [shape, setShape] = useState(0)
const [sortedArray, setSortedArray] = useRecoilState(sortedPolygonArray)
const [roof, setRoof] = useRecoilState(roofState)
const [wall, setWall] = useRecoilState(wallState)
const addEvent = (mode) => {
switch (mode) {
case 'default':
canvas?.off('mouse:down')
break
case 'drawLine':
drawLineMode()
break
case 'edit':
editMode()
break
case 'template':
templateMode()
break
case 'patterna':
break
case 'patternb':
applyTemplateB()
break
case 'textbox':
textboxMode()
break
case 'drawRect':
drawRectMode()
break
}
}
const changeMode = (canvas, mode) => {
setMode(mode)
// mode변경 시 이전 이벤트 제거
setCanvas(canvas)
canvas?.off('mouse:down')
addEvent(mode)
}
const editMode = () => {
canvas?.on('mouse:down', function (options) {
const pointer = canvas?.getPointer(options.e)
const circle = new fabric.Circle({
radius: 1,
fill: 'transparent', // 원 안을 비웁니다.
stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다.
left: pointer.x,
top: pointer.y,
originX: 'center',
originY: 'center',
selectable: false,
})
historyPoints.current.push(circle)
points.current.push(circle)
canvas?.add(circle)
if (points.current.length === 2) {
const length = Number(prompt('길이를 입력하세요:'))
// length 값이 숫자가 아닌 경우
if (isNaN(length) || length === 0) {
//마지막 추가 된 points 제거합니다.
const lastPoint =
historyPoints.current[historyPoints.current.length - 1]
canvas?.remove(lastPoint)
historyPoints.current.pop()
points.current.pop()
return
}
if (length) {
const vector = {
x: points.current[1].left - points.current[0].left,
y: points.current[1].top - points.current[0].top,
}
const slope = Math.abs(vector.y / vector.x) // 기울기 계산
let scaledVector
if (slope >= 1) {
// 기울기가 1 이상이면 x축 방향으로 그림
scaledVector = {
x: 0,
y: vector.y >= 0 ? Number(length) : -Number(length),
}
} else {
// 기울기가 1 미만이면 y축 방향으로 그림
scaledVector = {
x: vector.x >= 0 ? Number(length) : -Number(length),
y: 0,
}
}
const line = new QLine(
[
points.current[0].left,
points.current[0].top,
points.current[0].left + scaledVector.x,
points.current[0].top + scaledVector.y,
],
{
stroke: 'black',
strokeWidth: 2,
selectable: false,
viewLengthText: true,
direction: getDirection(points.current[0], points.current[1]),
fontSize: fontSize,
},
)
pushHistoryLine(line)
// 라인의 끝에 점을 추가합니다.
const endPointCircle = new fabric.Circle({
radius: 1,
fill: 'transparent', // 원 안을 비웁니다.
stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다.
left: points.current[0].left + scaledVector.x,
top: points.current[0].top + scaledVector.y,
originX: 'center',
originY: 'center',
selectable: false,
})
canvas?.add(endPointCircle)
historyPoints.current.push(endPointCircle)
points.current.forEach((point) => {
canvas?.remove(point)
})
points.current = [endPointCircle]
}
}
canvas?.renderAll()
})
}
const pushHistoryLine = (line) => {
if (
historyLines.current.length > 0 &&
historyLines.current[historyLines.current.length - 1].direction ===
line.direction
) {
// 같은 방향의 선이 두 번 연속으로 그려지면 이전 선을 제거하고, 새로운 선과 merge한다.
const lastLine = historyLines.current.pop()
canvas?.remove(lastLine)
const mergedLine = new QLine(
[lastLine.x1, lastLine.y1, line.x2, line.y2],
{
stroke: 'black',
strokeWidth: 2,
selectable: false,
viewLengthText: true,
direction: lastLine.direction,
fontSize: fontSize,
},
)
historyLines.current.push(mergedLine)
canvas?.add(mergedLine)
} else {
historyLines.current.push(line)
canvas?.add(line)
}
}
/**
* 마우스로 그린 점 기준으로 외벽선을 완성시켜준다.
* makePolygon 함수에 포함되어있던 내용을 다른 템플릿 적용에서도 사용할수 있도록 함수로 대체
*/
const drawWallPolygon = () => {
const firstPoint = historyPoints.current[0]
const lastPoint = historyPoints.current[historyPoints.current.length - 1]
historyPoints.current.forEach((point) => {
canvas?.remove(point)
})
drawLineWithLength(lastPoint, firstPoint)
points.current = []
historyPoints.current = []
// handleOuterlines()
const wall = makePolygon()
setWall(wall)
return wall
}
const templateMode = () => {
changeMode(canvas, Mode.EDIT)
if (historyPoints.current.length >= 4) {
// handleOuterlinesTest() //외곽선 그리기 테스트
// drawWallPolygon()
//아래 내용 drawWallPolygon()으로 대체
const firstPoint = historyPoints.current[0]
const lastPoint = historyPoints.current[historyPoints.current.length - 1]
historyPoints.current.forEach((point) => {
canvas?.remove(point)
})
drawLineWithLength(lastPoint, firstPoint)
points.current = []
historyPoints.current = []
handleOuterlinesTest()
const wall = makePolygon()
setWall(wall)
}
}
const textboxMode = () => {
canvas?.on('mouse:down', function (options) {
if (canvas?.getActiveObject()?.type === 'textbox') return
const pointer = canvas?.getPointer(options.e)
const textbox = new fabric.Textbox('텍스트를 입력하세요', {
left: pointer.x,
top: pointer.y,
width: 150, // 텍스트박스의 너비를 설정합니다.
fontSize: fontSize, // 텍스트의 크기를 설정합니다.
})
canvas?.add(textbox)
canvas?.setActiveObject(textbox) // 생성된 텍스트박스를 활성 객체로 설정합니다.
canvas?.renderAll()
// textbox가 active가 풀린 경우 editing mode로 변경
textbox?.on('editing:exited', function () {
changeMode(canvas, Mode.EDIT)
})
})
}
const drawLineMode = () => {
canvas?.on('mouse:down', function (options) {
const pointer = canvas?.getPointer(options.e)
const line = new QLine(
[pointer.x, 0, pointer.x, canvas.height], // y축에 1자 선을 그립니다.
{
stroke: 'black',
strokeWidth: 2,
viewLengthText: true,
selectable: false,
fontSize: fontSize,
},
)
canvas?.add(line)
canvas?.renderAll()
})
}
const drawRectMode = () => {
let rect, isDown, origX, origY
canvas.on('mouse:down', function (o) {
isDown = true
const pointer = canvas.getPointer(o.e)
origX = pointer.x
origY = pointer.y
rect = new fabric.Rect({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
width: pointer.x - origX,
height: pointer.y - origY,
angle: 0,
fill: 'transparent',
stroke: 'black',
transparentCorners: false,
})
canvas.add(rect)
})
canvas.on('mouse:move', function (o) {
if (!isDown) return
const pointer = canvas.getPointer(o.e)
if (origX > pointer.x) {
rect.set({ left: Math.abs(pointer.x) })
}
if (origY > pointer.y) {
rect.set({ top: Math.abs(pointer.y) })
}
rect.set({ width: Math.abs(origX - pointer.x) })
rect.set({ height: Math.abs(origY - pointer.y) })
})
canvas.on('mouse:up', function (o) {
const pointer = canvas.getPointer(o.e)
const qRect = new QRect({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
width: pointer.x - origX,
height: pointer.y - origY,
angle: 0,
viewLengthText: true,
fill: 'transparent',
stroke: 'black',
transparentCorners: false,
fontSize: fontSize,
})
canvas.remove(rect)
canvas.add(qRect)
isDown = false
})
}
/**
* 두 점을 연결하는 선과 길이를 그립니다.
* a : 시작점, b : 끝점
*/
const drawLineWithLength = (a, b) => {
const line = new QLine([a.left, a.top, b.left, b.top], {
stroke: 'black',
strokeWidth: 2,
selectable: false,
viewLengthText: true,
direction: getDirection(a, b),
fontSize: fontSize,
})
pushHistoryLine(line)
canvas?.renderAll()
}
const makePolygon = (otherLines) => {
// 캔버스에서 모든 라인 객체를 찾습니다.
const lines = otherLines || historyLines.current
if (!otherLines) {
//외각선 기준
const topIndex = findTopTwoIndexesByDistance(sortedArray) //배열중에 큰 2값을 가져옴 TODO: 나중에는 인자로 받아서 다각으로 수정 해야됨
//일단 배열 6개 짜리 기준의 선 번호
if (topIndex[0] === 4) {
if (topIndex[1] === 5) {
//1번
setShape(1)
}
} else if (topIndex[0] === 1) {
//4번
if (topIndex[1] === 2) {
setShape(4)
}
} else if (topIndex[0] === 0) {
if (topIndex[1] === 1) {
//2번
setShape(2)
} else if (topIndex[1] === 5) {
setShape(3)
}
}
historyLines.current = []
}
// 각 라인의 시작점과 끝점을 사용하여 다각형의 점 배열을 생성합니다.
const points = lines.map((line) => ({ x: line.x1, y: line.y1 }))
// 모든 라인 객체를 캔버스에서 제거합니다.
lines.forEach((line) => {
canvas?.remove(line)
})
// 점 배열을 사용하여 새로운 다각형 객체를 생성합니다.
const polygon = new QPolygon(
points,
{
stroke: 'black',
fill: 'transparent',
viewLengthText: true,
selectable: true,
fontSize: fontSize,
},
canvas,
)
// 새로운 다각형 객체를 캔버스에 추가합니다.
canvas.add(polygon)
// 캔버스를 다시 그립니다.
if (!otherLines) {
// polygon.fillCell()
canvas.renderAll()
polygon.setViewLengthText(false)
setMode(Mode.DEFAULT)
}
return polygon
}
/**
* 해당 캔버스를 비운다.
*/
const handleClear = () => {
canvas?.clear()
points.current = []
historyPoints.current = []
historyLines.current = []
}
const zoomIn = () => {
canvas?.setZoom(canvas.getZoom() + 0.1)
setZoom(Math.round(zoom + 10))
}
const zoomOut = () => {
canvas?.setZoom(canvas.getZoom() - 0.1)
setZoom(Math.ceil(zoom - 10))
}
const handleOuterlines = () => {
const newOuterlines = []
for (let i = 0; i < historyLines.current.length; i++) {
const next = historyLines.current[i + 1]
const prev =
historyLines.current[i - 1] ??
historyLines.current[historyLines.current.length - 1]
if (next) {
if (next.direction === 'right') {
// 다름 라인이 오른쪽으로 이동
if (historyLines.current[i].direction === 'top') {
if (prev.direction !== 'right') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
// bottom
if (prev?.direction !== 'right') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
}
}
} else if (next.direction === 'left') {
if (historyLines.current[i].direction === 'top') {
if (prev?.direction !== 'left') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
}
} else {
// bottom
if (prev?.direction !== 'left') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
}
}
} else if (next.direction === 'top') {
if (historyLines.current[i].direction === 'right') {
if (prev?.direction !== 'top') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
// left
if (prev?.direction !== 'top') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
}
}
} else if (next.direction === 'bottom') {
if (historyLines.current[i].direction === 'right') {
if (prev?.direction !== 'bottom') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
// left
if (prev.direction !== 'bottom') {
if (historyLines.current.length === 4) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 6) {
newOuterlines.push({
x1: historyLines.current[i].x1 + 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 - 50,
y2: historyLines.current[i].y2 - 50,
})
} else if (historyLines.current.length === 8) {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 + 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 + 50,
})
}
} else {
newOuterlines.push({
x1: historyLines.current[i].x1 - 50,
y1: historyLines.current[i].y1 - 50,
x2: historyLines.current[i].x2 + 50,
y2: historyLines.current[i].y2 - 50,
})
}
}
}
} else {
const tmp = newOuterlines[newOuterlines.length - 1]
newOuterlines.push({
x1: tmp.x2,
y1: tmp.y2,
x2: newOuterlines[0].x1,
y2: newOuterlines[0].y1,
})
}
}
makePolygon(newOuterlines)
}
/**
*벽 지붕 외곽선 생성
*/
const handleOuterlinesTest = (offset = 71) => {
var offsetPoints = []
const sortedIndex = getStartIndex(historyLines.current)
let tmpArraySorted = rearrangeArray(historyLines.current, sortedIndex)
if (tmpArraySorted[0].direction === 'right') {
//시계방향
tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정
}
setSortedArray(tmpArraySorted) //recoil에 넣음
const points = tmpArraySorted.map((line) => ({
x: line.x1,
y: line.y1,
}))
for (var i = 0; i < points.length; i++) {
var prev = points[(i - 1 + points.length) % points.length]
var current = points[i]
var next = points[(i + 1) % points.length]
// 두 벡터 계산 (prev -> current, current -> next)
var vector1 = { x: current.x - prev.x, y: current.y - prev.y }
var vector2 = { x: next.x - current.x, y: next.y - current.y }
// 벡터의 길이 계산
var length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
var length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
// 벡터를 단위 벡터로 정규화
var unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
var unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
// 법선 벡터 계산 (왼쪽 방향)
var normal1 = { x: -unitVector1.y, y: unitVector1.x }
var normal2 = { x: -unitVector2.y, y: unitVector2.x }
// 법선 벡터 평균 계산
var averageNormal = {
x: (normal1.x + normal2.x) / 2,
y: (normal1.y + normal2.y) / 2,
}
// 평균 법선 벡터를 단위 벡터로 정규화
var lengthNormal = Math.sqrt(
averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y,
)
var unitNormal = {
x: averageNormal.x / lengthNormal,
y: averageNormal.y / lengthNormal,
}
// 오프셋 적용
var offsetPoint = {
x1: current.x + unitNormal.x * offset,
y1: current.y + unitNormal.y * offset,
}
offsetPoints.push(offsetPoint)
}
const roof = makePolygon(offsetPoints)
setRoof(roof)
roof.drawHelpLine()
}
/**
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
*/
const handleOuterlinesTest2 = (polygon, offset = 71) => {
const offsetPoints = []
const sortedIndex = getStartIndex(polygon.lines)
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
if (tmpArraySorted[0].direction === 'right') {
//시계방향
tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정
}
setSortedArray(tmpArraySorted) //recoil에 넣음
const points = tmpArraySorted.map((line) => ({
x: line.x1,
y: line.y1,
}))
for (let i = 0; i < points.length; i++) {
const prev = points[(i - 1 + points.length) % points.length]
const current = points[i]
const next = points[(i + 1) % points.length]
// 두 벡터 계산 (prev -> current, current -> next)
const vector1 = { x: current.x - prev.x, y: current.y - prev.y }
const vector2 = { x: next.x - current.x, y: next.y - current.y }
// 벡터의 길이 계산
const length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
const length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
// 벡터를 단위 벡터로 정규화
const unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
const unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
// 법선 벡터 계산 (왼쪽 방향)
const normal1 = { x: -unitVector1.y, y: unitVector1.x }
const normal2 = { x: -unitVector2.y, y: unitVector2.x }
// 법선 벡터 평균 계산
const averageNormal = {
x: (normal1.x + normal2.x) / 2,
y: (normal1.y + normal2.y) / 2,
}
// 평균 법선 벡터를 단위 벡터로 정규화
const lengthNormal = Math.sqrt(
averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y,
)
const unitNormal = {
x: averageNormal.x / lengthNormal,
y: averageNormal.y / lengthNormal,
}
// 오프셋 적용
const offsetPoint = {
x1: current.x + unitNormal.x * offset,
y1: current.y + unitNormal.y * offset,
}
offsetPoints.push(offsetPoint)
}
const roof = makePolygon(offsetPoints)
setRoof(roof)
roof.drawHelpLine()
}
const togglePolygonLine = (obj) => {
const rtnLines = []
if (obj.type === 'QPolygon') {
const points = obj.getCurrentPoints()
points.forEach((point, index) => {
const nextPoint = points[(index + 1) % points.length] // 마지막 점이면 첫 번째 점으로 연결
const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], {
stroke: 'black',
strokeWidth: 2,
selectable: false,
fontSize: fontSize, // fontSize는 필요에 따라 조정
parent: obj,
})
obj.visible = false
canvas.add(line)
rtnLines.push(line)
})
canvas.renderAll()
}
if (obj.type === 'QLine') {
const parent = obj.parent
canvas
?.getObjects()
.filter((obj) => obj.parent === parent)
.forEach((obj) => {
rtnLines.push(obj)
canvas.remove(obj)
})
parent.visible = true
canvas.renderAll()
}
return rtnLines
}
/**
* 템플릿 B 적용
* 1. 모드 체인지
* 2. 외벽선 그리기 마무리
*/
const applyTemplateB = () => {
changeMode(canvas, Mode.EDIT)
const polygon = drawWallPolygon()
handleOuterLineTemplateB(polygon)
}
const handleOuterLineTemplateB = (polygon) => {
polygon.points.forEach((point, index) => {
let x2 =
index === polygon.points.length - 1
? polygon.points[0].x
: polygon.points[index + 1].x
let y2 =
index === polygon.points.length - 1
? polygon.points[0].y
: polygon.points[index + 1].y
let x1 = point.x
let y1 = point.y
if (index % 2 === 0) {
if (polygon.lines[index].direction === 'bottom') {
y1 = y1 - 50
y2 = y2 + 50
x1 = x1 - 20
x2 = x2 - 20
} else {
y1 = y1 + 50
y2 = y2 - 50
x1 = x1 + 20
x2 = x2 + 20
}
} else {
if (polygon.lines[index].direction === 'right') {
x1 = x1 - 20
x2 = x2 + 20
y1 = y1 + 50
y2 = y2 + 50
} else {
x1 = x1 + 20
x2 = x2 - 20
y1 = y1 - 50
y2 = y2 - 50
}
}
switch (polygon.shape) {
case 1:
break
case 2:
const centerPoint =
polygon.points[3].y +
(polygon.points[2].y - polygon.points[3].y) / 2
if (index === 0) {
const subLine = new QLine(
[
point.x - 20,
polygon.points[0].y +
(polygon.points[1].y - polygon.points[0].y) / 2,
polygon.points[5].x + 20,
polygon.points[0].y +
(polygon.points[1].y - polygon.points[0].y) / 2,
],
{
stroke: 'blue',
strokeWidth: 2,
selectable: false,
fontSize: fontSize,
},
)
canvas.add(subLine)
}
if (index === 3) {
x2 = x2 + 20
const subLine = new QLine([x2, y2, x2, centerPoint], {
stroke: 'blue',
strokeWidth: 2,
selectable: false,
fontSize: fontSize,
})
canvas.add(subLine)
}
if (index === 4) {
y1 =
point.y +
(polygon.points[index - 2].y - polygon.points[index - 1].y) / 2
const subLine = new QLine(
[point.x, centerPoint, polygon.points[2].x + 20, centerPoint],
{
stroke: 'blue',
strokeWidth: 2,
selectable: false,
fontSize: fontSize,
},
)
canvas.add(subLine)
const subVerticalLine = new QLine(
[
getCenterPoint(point.x, polygon.points[2].x + 20),
polygon.points[3].y - 50,
getCenterPoint(point.x, polygon.points[2].x + 20),
centerPoint,
],
{
stroke: 'black',
strokeWidth: 2,
strokeDashArray: [5, 5],
selectable: false,
fontSize: fontSize,
},
)
canvas.add(subVerticalLine)
}
if (index === 5) {
const centeredPoint = getCenterPoint(
polygon.points[0].x,
polygon.points[5].x,
)
const verticalSubLine = new QLine(
[
centeredPoint,
polygon.points[0].y - 50,
centeredPoint,
polygon.points[1].y + 50,
],
{
stroke: 'black',
strokeWidth: 2,
strokeDashArray: [5, 5],
selectable: false,
fontSize: fontSize,
},
)
canvas.add(verticalSubLine)
}
break
case 3:
break
case 4:
break
default:
break
}
const line = new QLine([x1, y1, x2, y2], {
stroke: 'blue',
strokeWidth: 2,
selectable: false,
fontSize: fontSize,
})
canvas.add(line)
})
canvas.renderAll()
}
return {
mode,
changeMode,
setCanvas,
handleClear,
zoomIn,
zoomOut,
zoom,
togglePolygonLine,
handleOuterlinesTest2,
}
}