템플릿 사각형 수정

This commit is contained in:
hyojun.choi 2024-07-10 17:00:16 +09:00
parent c44fa88ed8
commit aebf4e0c42
6 changed files with 258 additions and 91 deletions

View File

@ -155,7 +155,6 @@ export default function Roof2() {
canvas?.add(polygon) canvas?.add(polygon)
polygon.drawRoof(-50)
addBackgroundInPolygon(polygon) addBackgroundInPolygon(polygon)
const lines = togglePolygonLine(polygon) const lines = togglePolygonLine(polygon)
@ -176,11 +175,15 @@ export default function Roof2() {
const makeQLine = () => { const makeQLine = () => {
if (canvas) { if (canvas) {
const line = new QLine([50, 50, 200, 50], { const line = new QLine(
stroke: 'black', [50, 50, 200, 50],
strokeWidth: 1, {
fontSize: fontSize, stroke: 'black',
}) strokeWidth: 1,
fontSize: fontSize,
},
50,
)
canvas?.add(line) canvas?.add(line)
} }

View File

@ -4,7 +4,7 @@ export class QLine extends fabric.Group {
line line
text text
fontSize fontSize
length length = 0
x1 x1
y1 y1
x2 x2
@ -13,12 +13,13 @@ export class QLine extends fabric.Group {
type = 'QLine' type = 'QLine'
parent parent
constructor(points, option) { constructor(points, option, lengthTxt) {
const [x1, y1, x2, y2] = points const [x1, y1, x2, y2] = points
if (!option.fontSize) { if (!option.fontSize) {
throw new Error('Font size is required.') throw new Error('Font size is required.')
} }
const line = new fabric.Line(points, { ...option, strokeWidth: 1 }) const line = new fabric.Line(points, { ...option, strokeWidth: 1 })
super([line], {}) super([line], {})
@ -31,6 +32,10 @@ export class QLine extends fabric.Group {
this.direction = option.direction this.direction = option.direction
this.parent = option.parent this.parent = option.parent
if (lengthTxt > 0) {
this.length = Number(lengthTxt)
}
this.#init() this.#init()
this.#addControl() this.#addControl()
} }
@ -66,7 +71,16 @@ export class QLine extends fabric.Group {
this.removeWithUpdate(this.text) this.removeWithUpdate(this.text)
this.text = null this.text = null
} }
if (this.length > 0) {
const text = new fabric.Textbox(this.length.toFixed(0).toString(), {
left: (this.x1 + this.x2) / 2,
top: (this.y1 + this.y2) / 2,
fontSize: this.fontSize,
})
this.text = text
this.addWithUpdate(text)
return
}
const scaleX = this.scaleX const scaleX = this.scaleX
const scaleY = this.scaleY const scaleY = this.scaleY
const x1 = this.left const x1 = this.left
@ -77,7 +91,7 @@ export class QLine extends fabric.Group {
const dy = y2 - y1 const dy = y2 - y1
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0))
const text = new fabric.Textbox(this.length.toString(), { const text = new fabric.Textbox(this.length.toFixed(0).toString(), {
left: (x1 + x2) / 2, left: (x1 + x2) / 2,
top: (y1 + y2) / 2, top: (y1 + y2) / 2,
fontSize: this.fontSize, fontSize: this.fontSize,

View File

@ -1,8 +1,12 @@
import { fabric } from 'fabric' import { fabric } from 'fabric'
import { import {
calculateIntersection,
distanceBetweenPoints, distanceBetweenPoints,
getDegreeByChon,
getDirection, getDirection,
getDirectionByPoint, getDirectionByPoint,
getRoofHeight,
getRoofHypotenuse,
} from '@/util/canvas-util' } from '@/util/canvas-util'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
@ -15,7 +19,6 @@ export default class QPolygon extends fabric.Group {
canvas canvas
fontSize fontSize
qCells = [] qCells = []
roof
name name
constructor(points, options, canvas) { constructor(points, options, canvas) {
if (!options.fontSize) { if (!options.fontSize) {
@ -43,8 +46,8 @@ export default class QPolygon extends fabric.Group {
this.points.forEach((point, i) => { this.points.forEach((point, i) => {
const nextPoint = this.points[(i + 1) % this.points.length] const nextPoint = this.points[(i + 1) % this.points.length]
const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], {
stroke: 'black', stroke: this.stroke,
strokeWidth: 1, strokeWidth: this.strokeWidth,
fontSize: this.fontSize, fontSize: this.fontSize,
direction: getDirectionByPoint(point, nextPoint), direction: getDirectionByPoint(point, nextPoint),
}) })
@ -88,7 +91,7 @@ export default class QPolygon extends fabric.Group {
}) })
this.texts = [] this.texts = []
} }
const points = this.getCurrentPoints() const points = this.points
points.forEach((start, i) => { points.forEach((start, i) => {
const end = points[(i + 1) % points.length] const end = points[(i + 1) % points.length]
@ -281,77 +284,170 @@ export default class QPolygon extends fabric.Group {
}) })
} }
/**
* 지붕 그리기
* @param offset
*/
drawRoof(offset = -10) {
const points = this.getCurrentPoints()
const offsetPoints = []
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 = {
x: current.x + unitNormal.x * offset,
y: current.y + unitNormal.y * offset,
}
offsetPoints.push(offsetPoint)
}
const roof = new QPolygon(
offsetPoints,
{
stroke: 'black',
strokeWidth: 1,
fill: 'transparent',
fontSize: this.fontSize,
},
this.canvas,
)
this.roof = roof
this.addWithUpdate(outPolygon)
this.canvas.renderAll()
}
fillBackground(pattern) { fillBackground(pattern) {
this.polygon.set({ fill: pattern }) this.polygon.set({ fill: pattern })
this.canvas.renderAll() this.canvas.requestRenderAll()
}
// 보조선 그리기 사각형에서만
drawHelpLine(chon) {
let type
let smallestLength = Infinity
let maxLength = 0
this.lines.forEach((line) => {
if (line.length < smallestLength) {
smallestLength = line.length
}
if (line.length > maxLength) {
maxLength = line.length
}
})
const smallestLines = this.lines.filter(
(line) => line.length === smallestLength,
)
let needPlusLine
let needMinusLine
const direction = smallestLines[0].direction
if (direction === 'top' || direction === 'bottom') {
needPlusLine =
smallestLines[0].x1 < smallestLines[1].x1
? smallestLines[0]
: smallestLines[1]
needMinusLine =
needPlusLine === smallestLines[0] ? smallestLines[1] : smallestLines[0]
type = 1 // 가로가 긴 사각형
}
if (direction === 'left' || direction === 'right') {
needPlusLine =
smallestLines[0].y1 < smallestLines[1].y1
? smallestLines[0]
: smallestLines[1]
needMinusLine =
needPlusLine === smallestLines[0] ? smallestLines[1] : smallestLines[0]
type = 2 // 세로가 긴 사각형
}
let point1
let point2
if (type === 1) {
point1 = {
x: needPlusLine.x1 + smallestLength / 2,
y:
needPlusLine.y1 > needPlusLine.y2
? needPlusLine.y1 - smallestLength / 2
: needPlusLine.y2 - smallestLength / 2,
}
point2 = {
x: needMinusLine.x1 - smallestLength / 2,
y:
needMinusLine.y1 > needMinusLine.y2
? needMinusLine.y1 - smallestLength / 2
: needMinusLine.y2 - smallestLength / 2,
}
} else if (type === 2) {
point1 = {
x:
needPlusLine.x1 > needPlusLine.x2
? needPlusLine.x1 - smallestLength / 2
: needPlusLine.x2 - smallestLength / 2,
y: needPlusLine.y1 + smallestLength / 2,
}
point2 = {
x:
needMinusLine.x1 > needMinusLine.x2
? needMinusLine.x1 - smallestLength / 2
: needMinusLine.x2 - smallestLength / 2,
y: needMinusLine.y1 - smallestLength / 2,
}
}
// 빗변1
const realLine1 = new QLine(
[needPlusLine.x1, needPlusLine.y1, point1.x, point1.y],
{ fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 },
getRoofHypotenuse(smallestLength / 2),
)
// 빗변2
const realLine2 = new QLine(
[needPlusLine.x2, needPlusLine.y2, point1.x, point1.y],
{ fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 },
getRoofHypotenuse(smallestLength / 2),
)
// 빗변3
const realLine3 = new QLine(
[needMinusLine.x1, needMinusLine.y1, point2.x, point2.y],
{ fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 },
getRoofHypotenuse(smallestLength / 2),
)
// 빗변4
const realLine4 = new QLine(
[needMinusLine.x2, needMinusLine.y2, point2.x, point2.y],
{ fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 },
getRoofHypotenuse(smallestLength / 2),
)
let centerPoint1
let centerPoint2
if (type === 1) {
centerPoint1 = { x: point1.x - smallestLength / 2, y: point1.y }
centerPoint2 = { x: point2.x + smallestLength / 2, y: point2.y }
} else if (type === 2) {
centerPoint1 = { x: point1.x, y: point1.y - smallestLength / 2 }
centerPoint2 = { x: point2.x, y: point2.y + smallestLength / 2 }
}
// 옆으로 누워있는 지붕의 높이
const realLine5 = new QLine(
[point1.x, point1.y, centerPoint1.x, centerPoint1.y],
{
fontSize: this.fontSize,
stroke: 'black',
strokeWidth: 1,
strokeDashArray: [5, 5],
},
getRoofHeight(smallestLength / 2, getDegreeByChon(4)),
)
// 옆으로 누워있는 지붕의 높이
const realLine6 = new QLine(
[point2.x, point2.y, centerPoint2.x, centerPoint2.y],
{
fontSize: this.fontSize,
stroke: 'black',
strokeWidth: 1,
strokeDashArray: [5, 5],
},
getRoofHeight(smallestLength / 2, getDegreeByChon(4)),
)
// 용마루
const ridge = new QLine([point1.x, point1.y, point2.x, point2.y], {
fontSize: this.fontSize,
stroke: 'black',
strokeWidth: 1,
})
this.addWithUpdate(realLine1)
this.addWithUpdate(realLine2)
this.addWithUpdate(realLine3)
this.addWithUpdate(realLine4)
this.addWithUpdate(realLine5)
this.addWithUpdate(realLine6)
this.addWithUpdate(ridge)
} }
} }

View File

@ -7,8 +7,13 @@ import {
findTopTwoIndexesByDistance, findTopTwoIndexesByDistance,
getDirection, getDirection,
} from '@/util/canvas-util' } from '@/util/canvas-util'
import { useRecoilState } from 'recoil' import { useRecoilState, useSetRecoilState } from 'recoil'
import { fontSizeState, sortedPolygonArray } from '@/store/canvasAtom' import {
fontSizeState,
roofState,
sortedPolygonArray,
wallState,
} from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine' import { QLine } from '@/components/fabric/QLine'
export const Mode = { export const Mode = {
@ -30,6 +35,8 @@ export function useMode() {
const [fontSize] = useRecoilState(fontSizeState) const [fontSize] = useRecoilState(fontSizeState)
const [shape, setShape] = useState(0) const [shape, setShape] = useState(0)
const [sortedArray, setSortedArray] = useRecoilState(sortedPolygonArray) const [sortedArray, setSortedArray] = useRecoilState(sortedPolygonArray)
const [roof, setRoof] = useRecoilState(roofState)
const [wall, setWall] = useRecoilState(wallState)
const addEvent = (mode) => { const addEvent = (mode) => {
switch (mode) { switch (mode) {
@ -210,7 +217,8 @@ export function useMode() {
// handleOuterlines() // handleOuterlines()
handleOuterlinesTest() //외곽선 그리기 테스트 handleOuterlinesTest() //외곽선 그리기 테스트
makePolygon() const wall = makePolygon()
setWall(wall)
} }
} }
@ -398,11 +406,13 @@ export function useMode() {
// 캔버스를 다시 그립니다. // 캔버스를 다시 그립니다.
if (!otherLines) { if (!otherLines) {
polygon.fillCell() // polygon.fillCell()
canvas.renderAll() canvas.renderAll()
polygon.setViewLengthText(false) polygon.setViewLengthText(false)
setMode(Mode.DEFAULT) setMode(Mode.DEFAULT)
} }
return polygon
} }
/** /**
@ -714,7 +724,6 @@ export function useMode() {
} }
} }
console.log(newOuterlines)
makePolygon(newOuterlines) makePolygon(newOuterlines)
} }
@ -780,7 +789,9 @@ export function useMode() {
offsetPoints.push(offsetPoint) offsetPoints.push(offsetPoint)
} }
makePolygon(offsetPoints) const roof = makePolygon(offsetPoints)
setRoof(roof)
roof.drawHelpLine()
} }
const togglePolygonLine = (obj) => { const togglePolygonLine = (obj) => {

View File

@ -20,6 +20,18 @@ export const canvasSizeState = atom({
export const sortedPolygonArray = atom({ export const sortedPolygonArray = atom({
key: 'sortedArray', key: 'sortedArray',
default : [], default: [],
dangerouslyAllowMutability: true,
})
export const roofState = atom({
key: 'roof',
default: {},
dangerouslyAllowMutability: true,
})
export const wallState = atom({
key: 'wall',
default: {},
dangerouslyAllowMutability: true, dangerouslyAllowMutability: true,
}) })

View File

@ -264,3 +264,34 @@ export const getDirectionByPoint = (a, b) => {
return vector.y > 0 ? 'bottom' : 'top' return vector.y > 0 ? 'bottom' : 'top'
} }
} }
export function calculateIntersection(line1, line2) {
const x1 = line1.x1,
y1 = line1.y1,
x2 = line1.x2,
y2 = line1.y2
const x3 = line2.x1,
y3 = line2.y1,
x4 = line2.x2,
y4 = line2.y2
const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if (denom === 0) return null // 선분이 평행하거나 일치
const intersectX =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom
const intersectY =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom
// 교차점이 두 선분의 x 좌표 범위 내에 있는지 확인
if (
intersectX < Math.min(x1, x2) ||
intersectX > Math.max(x1, x2) ||
intersectX < Math.min(x3, x4) ||
intersectX > Math.max(x3, x4)
) {
return null // 교차점이 선분 범위 밖에 있음
}
return { x: intersectX, y: intersectY }
}