Merge branch 'dev' into feature/test-jy
# Conflicts: # src/components/Roof2.jsx # src/components/fabric/QPolygon.js # src/hooks/useMode.js # src/util/qpolygon-utils.js
This commit is contained in:
commit
b03cea96bb
@ -1,6 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// reactStrictMode: false,
|
||||
reactStrictMode: false,
|
||||
webpack: (config) => {
|
||||
config.externals.push({
|
||||
"utf-8-validate": "commonjs utf-8-validate",
|
||||
|
||||
15
src/app/error.jsx
Normal file
15
src/app/error.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
|
||||
export default function ServerError() {
|
||||
return (
|
||||
<section className="bg-white dark:bg-gray-900">
|
||||
<div className="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
|
||||
<div className="mx-auto max-w-screen-sm text-center">
|
||||
<h1 className="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">500</h1>
|
||||
<p className="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Internal Server Error.</p>
|
||||
<p className="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">We are already working to solve the problem. </p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
25
src/app/not-found.jsx
Normal file
25
src/app/not-found.jsx
Normal file
@ -0,0 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<section className="bg-white dark:bg-gray-900">
|
||||
<div className="py-8 px-4 mx-auto max-w-screen-xl lg:py-16 lg:px-6">
|
||||
<div className="mx-auto max-w-screen-sm text-center">
|
||||
<h1 className="mb-4 text-7xl tracking-tight font-extrabold lg:text-9xl text-primary-600 dark:text-primary-500">404</h1>
|
||||
<p className="mb-4 text-3xl tracking-tight font-bold text-gray-900 md:text-4xl dark:text-white">Something's missing.</p>
|
||||
<p className="mb-4 text-lg font-light text-gray-500 dark:text-gray-400">
|
||||
Sorry, we can't find that page. You'll find lots to explore on the home page.{' '}
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex text-white bg-primary-600 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:focus:ring-primary-900 my-4"
|
||||
>
|
||||
Back to Homepage
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@ -5,11 +5,11 @@ import { Button } from '@nextui-org/react'
|
||||
import QRect from '@/components/fabric/QRect'
|
||||
|
||||
import RangeSlider from './ui/RangeSlider'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { canvasSizeState, fontSizeState, sortedPolygonArray } from '@/store/canvasAtom'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { canvasSizeState, fontSizeState, roofState, sortedPolygonArray } from '@/store/canvasAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
||||
import { calculateIntersection2 } from '@/util/canvas-util'
|
||||
import { calculateIntersection } from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
|
||||
export default function Roof2() {
|
||||
@ -107,28 +107,24 @@ export default function Roof2() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* canvas 사이즈 변경 함수
|
||||
*/
|
||||
const canvasSizeMode = () => {
|
||||
if (canvas) {
|
||||
canvas.setWidth(horizontalSize)
|
||||
canvas.setHeight(verticalSize)
|
||||
canvas.renderAll()
|
||||
|
||||
setCanvasSize(() => ({
|
||||
vertical: verticalSize,
|
||||
horizontal: horizontalSize,
|
||||
}))
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
setCanvasSize({ ...canvasSize, vertical: verticalSize, horizontal: horizontalSize })
|
||||
}, [verticalSize, horizontalSize])
|
||||
|
||||
/**
|
||||
* 값 변경시
|
||||
*/
|
||||
// useEffect(() => {
|
||||
// canvasSizeMode()
|
||||
// }, [verticalSize, horizontalSize])
|
||||
useEffect(() => {
|
||||
canvasSizeMode()
|
||||
}, [verticalSize, horizontalSize])
|
||||
const { vertical, horizontal } = canvasSize
|
||||
if (vertical !== verticalSize || horizontal !== horizontalSize) {
|
||||
canvas.setWidth(horizontalSize)
|
||||
canvas.setHeight(verticalSize)
|
||||
canvas.renderAll()
|
||||
}
|
||||
}, [canvasSize, canvas])
|
||||
|
||||
const makeQPolygon = () => {
|
||||
const type1 = [
|
||||
@ -207,14 +203,14 @@ export default function Roof2() {
|
||||
]
|
||||
|
||||
const eightPoint3 = [
|
||||
{ x: 200, y: 200 },
|
||||
{ x: 200, y: 1000 },
|
||||
{ x: 1000, y: 1000 },
|
||||
{ x: 1000, y: 800 },
|
||||
{ x: 600, y: 800 },
|
||||
{ x: 600, y: 350 },
|
||||
{ x: 1000, y: 350 },
|
||||
{ x: 1000, y: 200 },
|
||||
{ x: 190, y: 147 },
|
||||
{ x: 190, y: 747 },
|
||||
{ x: 490, y: 747 },
|
||||
{ x: 490, y: 497 },
|
||||
{ x: 640, y: 497 },
|
||||
{ x: 640, y: 747 },
|
||||
{ x: 1090, y: 747 },
|
||||
{ x: 1090, y: 147 },
|
||||
]
|
||||
|
||||
const eightPoint4 = [
|
||||
@ -270,25 +266,39 @@ export default function Roof2() {
|
||||
{ x: 300, y: 100 },
|
||||
]
|
||||
|
||||
if (canvas) {
|
||||
const polygon = new QPolygon(diagonalType, {
|
||||
// const polygon = new QPolygon(eightPoint, {
|
||||
fill: 'transparent',
|
||||
stroke: 'green',
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
fontSize: fontSize,
|
||||
name: 'QPolygon1',
|
||||
})
|
||||
const twelvePoint = [
|
||||
{ x: 195, y: 166 },
|
||||
{ x: 195, y: 466 },
|
||||
{ x: 395, y: 466 },
|
||||
{ x: 395, y: 766 },
|
||||
{ x: 545, y: 766 },
|
||||
{ x: 545, y: 466 },
|
||||
{ x: 695, y: 466 },
|
||||
{ x: 695, y: 666 },
|
||||
{ x: 845, y: 666 },
|
||||
{ x: 845, y: 466 },
|
||||
{ x: 995, y: 466 },
|
||||
{ x: 995, y: 166 },
|
||||
]
|
||||
|
||||
const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint]
|
||||
|
||||
const polygon = new QPolygon(type1B, {
|
||||
fill: 'transparent',
|
||||
stroke: 'black',
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
fontSize: fontSize,
|
||||
name: 'QPolygon1',
|
||||
})
|
||||
|
||||
canvas?.add(polygon)
|
||||
drawRoofPolygon(polygon)
|
||||
|
||||
// handleOuterlinesTest2(polygon)
|
||||
|
||||
// const lines = togglePolygonLine(polygon)
|
||||
// togglePolygonLine(lines[0])
|
||||
}
|
||||
// const lines = togglePolygonLine(polygon)
|
||||
// togglePolygonLine(lines[0])
|
||||
}
|
||||
|
||||
const rotateShape = () => {
|
||||
@ -321,7 +331,7 @@ export default function Roof2() {
|
||||
canvas?.add(line)
|
||||
canvas?.add(line2)
|
||||
|
||||
const interSectionPoint = calculateIntersection2(line, line2)
|
||||
const interSectionPoint = calculateIntersection(line, line2)
|
||||
|
||||
if (interSectionPoint) {
|
||||
const circle = new fabric.Circle({
|
||||
@ -429,8 +439,12 @@ export default function Roof2() {
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => drawRoofPatterns(2)}>
|
||||
지붕패턴2
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.MODULE)}>
|
||||
모듈
|
||||
<Button
|
||||
className="m-1 p-2"
|
||||
color={`${mode === Mode.TEMPLATE ? 'primary' : 'default'}`}
|
||||
onClick={() => changeMode(canvas, Mode.ROOT_TRESTLE)}
|
||||
>
|
||||
지붕가대생성
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => changeMode(canvas, Mode.TEXTBOX)}>
|
||||
텍스트박스 모드
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
/**
|
||||
* 문제 없는경우 제거
|
||||
*/
|
||||
import { fabric } from 'fabric'
|
||||
import { getDirection, getDirectionByPoint } from '@/util/canvas-util'
|
||||
|
||||
export class QLine2 extends fabric.Group {
|
||||
line
|
||||
text
|
||||
fontSize
|
||||
length = 0
|
||||
x1
|
||||
y1
|
||||
x2
|
||||
y2
|
||||
direction
|
||||
idx
|
||||
type = 'QLine2'
|
||||
parent
|
||||
isAlreadyInterSection = false
|
||||
|
||||
interSectionPoints = []
|
||||
lengthTxt = 0
|
||||
|
||||
initPoints
|
||||
initOption
|
||||
initLengthTxt
|
||||
|
||||
constructor(points, option = { isActiveLengthText: true }, lengthTxt) {
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
point = Math.round(point)
|
||||
})
|
||||
|
||||
const [x1, y1, x2, y2] = points
|
||||
|
||||
if (!option.fontSize) {
|
||||
throw new Error('Font size is required.')
|
||||
}
|
||||
|
||||
const line = new fabric.Line(points, { ...option, strokeWidth: 1 })
|
||||
|
||||
// 길이가 1이하인 선은 생성하지 않음
|
||||
if (Math.abs(x1 - x2) <= 1 && Math.abs(y1 - y2) <= 1) {
|
||||
super([], {})
|
||||
this.initPoints = points
|
||||
this.initOption = option
|
||||
this.initLengthTxt = lengthTxt
|
||||
} else {
|
||||
super([line], {})
|
||||
this.initPoints = points
|
||||
this.initOption = option
|
||||
this.initLengthTxt = lengthTxt
|
||||
|
||||
this.x1 = x1
|
||||
this.y1 = y1
|
||||
this.x2 = x2
|
||||
this.y2 = y2
|
||||
this.line = line
|
||||
this.fontSize = option.fontSize
|
||||
this.direction = option.direction ?? getDirectionByPoint({ x: x1, y: y1 }, { x: x2, y: y2 })
|
||||
this.parent = option.parent
|
||||
this.idx = option.idx
|
||||
|
||||
if (lengthTxt > 0) {
|
||||
this.lengthTxt = Number(lengthTxt)
|
||||
}
|
||||
|
||||
option.isActiveLengthText ?? this.init()
|
||||
this.addControl()
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.addLengthText(true)
|
||||
}
|
||||
|
||||
addControl() {
|
||||
this.on('moving', () => {
|
||||
this.addLengthText(false)
|
||||
})
|
||||
|
||||
this.on('modified', (e) => {
|
||||
this.addLengthText(false)
|
||||
})
|
||||
|
||||
this.on('selected', () => {
|
||||
console.log(this)
|
||||
Object.keys(this.controls).forEach((controlKey) => {
|
||||
if (controlKey !== 'ml' && controlKey !== 'mr') {
|
||||
this.setControlVisible(controlKey, false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static fromObject(object, callback) {
|
||||
const { points, initOption, initLengthTxt } = object
|
||||
const instance = new QLine(points, initOption, initLengthTxt)
|
||||
callback && callback(instance)
|
||||
return instance
|
||||
}
|
||||
|
||||
addLengthText(isFirst) {
|
||||
if (this.text) {
|
||||
this.removeWithUpdate(this.text)
|
||||
this.text = null
|
||||
}
|
||||
|
||||
if (isFirst && this.lengthTxt > 0) {
|
||||
const text = new fabric.Textbox(this.lengthTxt.toFixed(0).toString(), {
|
||||
left: (this.x1 + this.x2) / 2,
|
||||
top: (this.y1 + this.y2) / 2,
|
||||
fontSize: this.fontSize,
|
||||
})
|
||||
this.length = this.lengthTxt
|
||||
this.text = text
|
||||
this.addWithUpdate(text)
|
||||
return
|
||||
}
|
||||
|
||||
const scaleX = this.scaleX
|
||||
const scaleY = this.scaleY
|
||||
const x1 = this.left
|
||||
const y1 = this.top
|
||||
const x2 = this.left + this.width * scaleX
|
||||
const y2 = this.top + this.height * scaleY
|
||||
const dx = x2 - x1
|
||||
const dy = y2 - y1
|
||||
this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0))
|
||||
|
||||
const text = new fabric.Textbox(this.length.toFixed(0).toString(), {
|
||||
left: (x1 + x2) / 2,
|
||||
top: (y1 + y2) / 2,
|
||||
fontSize: this.fontSize,
|
||||
})
|
||||
this.text = text
|
||||
this.addWithUpdate(text)
|
||||
}
|
||||
|
||||
setFontSize(fontSize) {
|
||||
this.fontSize = fontSize
|
||||
this.text.set({ fontSize })
|
||||
this.addWithUpdate()
|
||||
}
|
||||
|
||||
fromObject(object, callback) {
|
||||
console.log('fromObject', object, callback)
|
||||
}
|
||||
|
||||
async = true
|
||||
|
||||
toObject(propertiesToInclude) {
|
||||
return fabric.util.object.extend(this.callSuper('toObject'), {
|
||||
length: this.length,
|
||||
line: this.line,
|
||||
text: this.text,
|
||||
fontSize: this.fontSize,
|
||||
x1: this.x1,
|
||||
y1: this.y1,
|
||||
x2: this.x2,
|
||||
y2: this.y2,
|
||||
direction: this.direction,
|
||||
idx: this.idx,
|
||||
type: this.type,
|
||||
parent: this.parent,
|
||||
isAlreadyInterSection: this.isAlreadyInterSection,
|
||||
interSectionPoints: this.interSectionPoints,
|
||||
lengthTxt: this.lengthTxt,
|
||||
setFontSize: this.setFontSize,
|
||||
addLengthText: this.addLengthText,
|
||||
init: this.init,
|
||||
addControl: this.addControl,
|
||||
initPoints: this.initPoints,
|
||||
initOption: this.initOption,
|
||||
initLengthTxt: this.initLengthTxt,
|
||||
points: this.points,
|
||||
})
|
||||
}
|
||||
|
||||
_set(key, value) {
|
||||
this.callSuper('_set', key, value)
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,8 @@
|
||||
import { fabric } from 'fabric'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import {
|
||||
distanceBetweenPoints,
|
||||
findTopTwoIndexesByDistance,
|
||||
getDirectionByPoint,
|
||||
sortedPoints,
|
||||
} from '@/util/canvas-util'
|
||||
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, {
|
||||
@ -15,19 +11,22 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
texts: [],
|
||||
id: null,
|
||||
length: 0,
|
||||
ridges: [],
|
||||
hips: [],
|
||||
ridges: [],
|
||||
connectRidges: [],
|
||||
innerLines: [],
|
||||
initialize: function(points, options, canvas) {
|
||||
console.log('sorted 전 points : ', points)
|
||||
initialize: function (points, options, canvas) {
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
point.x = Math.round(point.x)
|
||||
point.y = Math.round(point.y)
|
||||
})
|
||||
points = sortedPoints(points)
|
||||
if (points.length <= 8) {
|
||||
points = sortedPointLessEightPoint(points)
|
||||
} else {
|
||||
points = sortedPoints(points)
|
||||
}
|
||||
|
||||
console.log('sorted 후 points : ', points)
|
||||
this.callSuper('initialize', points, options)
|
||||
if (options.id) {
|
||||
this.id = options.id
|
||||
@ -133,7 +132,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
},
|
||||
|
||||
addLengthText() {
|
||||
const points = this.getCurrentPoints()
|
||||
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)
|
||||
@ -219,9 +218,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
top: rectTop,
|
||||
width: rectWidth,
|
||||
height: rectHeight,
|
||||
stroke: 'black', // or any other color
|
||||
fill: 'transparent',
|
||||
fill: '#BFFD9F',
|
||||
selectable: false,
|
||||
opacity: 0.6,
|
||||
})
|
||||
|
||||
this.canvas.add(rect)
|
||||
@ -320,4 +319,5 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
text.set({ visible: isView })
|
||||
})
|
||||
},
|
||||
divideLine() {},
|
||||
})
|
||||
|
||||
@ -1,941 +0,0 @@
|
||||
/**
|
||||
* 문제 없는경우 제거
|
||||
*/
|
||||
|
||||
import { fabric } from 'fabric'
|
||||
import {
|
||||
calculateIntersection,
|
||||
calculateIntersection2,
|
||||
distanceBetweenPoints,
|
||||
findClosestLineToPoint,
|
||||
findTopTwoIndexesByDistance,
|
||||
getDegreeByChon,
|
||||
getDirectionByPoint,
|
||||
getRoofHeight,
|
||||
getRoofHypotenuse,
|
||||
sortedPoints,
|
||||
} from '@/util/canvas-util'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { drawHelpLineInHexagon } from '@/util/qpolygon-utils'
|
||||
|
||||
export default class QPolygon3 extends fabric.Group {
|
||||
type = 'QPolygon3'
|
||||
polygon
|
||||
points
|
||||
texts = []
|
||||
lines = []
|
||||
canvas
|
||||
fontSize
|
||||
qCells = []
|
||||
name
|
||||
shape = 0 // 점 6개일때의 shape 모양
|
||||
helpPoints = []
|
||||
helpLines = []
|
||||
|
||||
wall
|
||||
|
||||
initPoints
|
||||
initOption
|
||||
|
||||
constructor(points, options, canvas) {
|
||||
/*if (points.length !== 4 && points.length !== 6) {
|
||||
throw new Error('Points must be 4 or 6.')
|
||||
}*/
|
||||
if (!options.fontSize) {
|
||||
throw new Error('Font size is required.')
|
||||
}
|
||||
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
point.x = Math.round(point.x)
|
||||
point.y = Math.round(point.y)
|
||||
})
|
||||
|
||||
const sortPoints = sortedPoints(points)
|
||||
const polygon = new fabric.Polygon(sortPoints, options)
|
||||
|
||||
super([polygon], {})
|
||||
|
||||
this.fontSize = options.fontSize
|
||||
this.points = sortPoints
|
||||
this.polygon = polygon
|
||||
this.name = options.name
|
||||
|
||||
this.initPoints = points
|
||||
this.initOption = options
|
||||
|
||||
this.init()
|
||||
this.addEvent()
|
||||
this.initLines()
|
||||
this.setShape()
|
||||
}
|
||||
|
||||
initLines() {
|
||||
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,
|
||||
idx: i,
|
||||
direction: getDirectionByPoint(point, nextPoint),
|
||||
})
|
||||
|
||||
this.lines.push(line)
|
||||
})
|
||||
}
|
||||
|
||||
init() {
|
||||
this.addLengthText()
|
||||
}
|
||||
|
||||
addEvent() {
|
||||
this.on('scaling', (e) => {
|
||||
this.updateLengthText()
|
||||
})
|
||||
|
||||
this.on('selected', function () {
|
||||
// 모든 컨트롤 떼기
|
||||
|
||||
Object.keys(this.controls).forEach((controlKey) => {
|
||||
if (controlKey !== 'mtr') {
|
||||
this.setControlVisible(controlKey, false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
setWall(wall) {
|
||||
this.wall = wall
|
||||
}
|
||||
|
||||
setFontSize(fontSize) {
|
||||
this.fontSize = fontSize
|
||||
this.texts.forEach((text) => {
|
||||
text.set({ fontSize })
|
||||
})
|
||||
|
||||
this.getObjects().forEach((obj) => {
|
||||
if (obj.type[0] === 'Q') {
|
||||
obj.setFontSize(fontSize)
|
||||
}
|
||||
})
|
||||
|
||||
this.canvas.add()
|
||||
}
|
||||
|
||||
addLengthText() {
|
||||
if (this.texts.length > 0) {
|
||||
this.texts.forEach((text) => {
|
||||
this.canvas.remove(text)
|
||||
})
|
||||
this.texts = []
|
||||
}
|
||||
const points = this.points
|
||||
|
||||
points.forEach((start, 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)
|
||||
|
||||
// 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,
|
||||
})
|
||||
|
||||
this.texts.push(text)
|
||||
this.canvas.add(text)
|
||||
})
|
||||
|
||||
this.canvas.renderAll()
|
||||
}
|
||||
|
||||
updateLengthText() {
|
||||
const points = this.getCurrentPoints()
|
||||
|
||||
points.forEach((start, 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)
|
||||
|
||||
// Update the text object with the new length
|
||||
this.texts[i].set({ text: length.toFixed(0) })
|
||||
})
|
||||
|
||||
this.canvas.renderAll()
|
||||
}
|
||||
|
||||
fillCell(cell = { width: 50, height: 100, padding: 10 }) {
|
||||
const points = this.getCurrentPoints()
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* this.lines의 direction이 top 인 line의 모든 합이 bottom 인 line의 모든 합과 같은지 확인
|
||||
* this.lines의 direction이 left 인 line의 모든 합이 right 인 line의 모든 합과 같은지 확인
|
||||
* return {boolean}
|
||||
*/
|
||||
isValid() {
|
||||
const leftLinesLengthSum = this.lines.filter((line) => line.direction === 'left').reduce((sum, line) => sum + line.length, 0)
|
||||
const rightLinesLengthSum = this.lines.filter((line) => line.direction === 'right').reduce((sum, line) => sum + line.length, 0)
|
||||
|
||||
const topLinesLengthSum = this.lines.filter((line) => line.direction === 'top').reduce((sum, line) => sum + line.length, 0)
|
||||
const bottomLinesLengthSum = this.lines.filter((line) => line.direction === 'bottom').reduce((sum, line) => sum + line.length, 0)
|
||||
|
||||
return leftLinesLengthSum === rightLinesLengthSum && topLinesLengthSum === bottomLinesLengthSum
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
setViewLengthText(boolean) {
|
||||
this.texts.forEach((text) => {
|
||||
text.visible = boolean
|
||||
})
|
||||
|
||||
this.canvas.renderAll()
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fillBackground(pattern) {
|
||||
this.polygon.set({ fill: pattern })
|
||||
this.canvas.requestRenderAll()
|
||||
}
|
||||
|
||||
// 보조선 그리기
|
||||
drawHelpLine(chon = 4) {
|
||||
drawHelpLineInHexagon(this, chon)
|
||||
/*if (!this.isValid()) {
|
||||
return
|
||||
}*/
|
||||
|
||||
/*if (this.lines.length === 4) {
|
||||
this.drawHelpLineInRect(chon)
|
||||
} else if (this.lines.length === 6 || this.lines.length === 8) {
|
||||
// TODO : 6각형
|
||||
drawHelpLineInHexagon2(this, chon)
|
||||
} /!* else if (this.lines.length === 8) {
|
||||
// TODO : 8각형
|
||||
this.drawHelpLineInOctagon(chon)
|
||||
}*!/*/
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 점 6개만 가능
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 점 6개만 가능
|
||||
* @returns {number}
|
||||
*/
|
||||
getShape() {
|
||||
return this.shape
|
||||
}
|
||||
|
||||
drawHelpLineInRect(chon) {
|
||||
let type = 1
|
||||
let smallestLength = Infinity
|
||||
let maxLength = 0
|
||||
|
||||
this.lines.forEach((line) => {
|
||||
if (line.length < smallestLength) {
|
||||
smallestLength = line.length
|
||||
}
|
||||
if (line.length > maxLength) {
|
||||
maxLength = line.length
|
||||
}
|
||||
})
|
||||
|
||||
// QPolygon 객체의 모든 선들을 가져옵니다.
|
||||
const lines = [...this.lines]
|
||||
|
||||
// 이 선들을 길이에 따라 정렬합니다.
|
||||
lines.sort((a, b) => a.length - b.length)
|
||||
|
||||
// 정렬된 배열에서 가장 작은 두 선을 선택합니다.
|
||||
let smallestLines
|
||||
|
||||
if (smallestLength === maxLength) {
|
||||
// 정사각형인 경우 0, 2번째 라인이 가장 짧은 라인
|
||||
|
||||
smallestLines = [lines[0], lines[2]]
|
||||
} else {
|
||||
smallestLines = lines.slice(0, 2)
|
||||
}
|
||||
|
||||
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(chon)),
|
||||
)
|
||||
|
||||
// 옆으로 누워있는 지붕의 높이
|
||||
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(chon)),
|
||||
)
|
||||
|
||||
// 용마루
|
||||
const ridge = new QLine([point1.x, point1.y, point2.x, point2.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'black',
|
||||
strokeWidth: 1,
|
||||
})
|
||||
|
||||
this.canvas.add(realLine1)
|
||||
this.canvas.add(realLine2)
|
||||
this.canvas.add(realLine3)
|
||||
this.canvas.add(realLine4)
|
||||
this.canvas.add(realLine5)
|
||||
this.canvas.add(realLine6)
|
||||
if (smallestLength !== maxLength) {
|
||||
// 정사각형이 아닌경우에만 용마루를 추가한다.
|
||||
this.canvas.add(ridge)
|
||||
}
|
||||
}
|
||||
drawHelpLineInHexagon2(chon) {
|
||||
const oneSideLines = [...this.lines].map((line) => {
|
||||
let newX1, newY1, newX2, newY2
|
||||
if (line.direction === 'top') {
|
||||
newX1 = line.x2
|
||||
newY1 = line.y2
|
||||
newX2 = line.x1
|
||||
newY2 = line.y1
|
||||
|
||||
line.x1 = newX1
|
||||
line.y1 = newY1
|
||||
line.x2 = newX2
|
||||
line.y2 = newY2
|
||||
line.direction = 'bottom'
|
||||
} else if (line.direction === 'left') {
|
||||
newX1 = line.x2
|
||||
newY1 = line.y2
|
||||
newX2 = line.x1
|
||||
newY2 = line.y1
|
||||
|
||||
line.x1 = newX1
|
||||
line.y1 = newY1
|
||||
line.x2 = newX2
|
||||
line.y2 = newY2
|
||||
line.direction = 'right'
|
||||
}
|
||||
return line
|
||||
})
|
||||
|
||||
const centerLines = []
|
||||
const helpLines = []
|
||||
const ridgeStartPoints = []
|
||||
|
||||
const horizontalLines = oneSideLines.filter((line) => line.direction === 'right')
|
||||
const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom')
|
||||
|
||||
const horizontalMaxLength = horizontalLines.reduce((max, obj) => Math.max(max, obj.length), 0)
|
||||
const verticalMaxLength = verticalLines.reduce((max, obj) => Math.max(max, obj.length), 0)
|
||||
// 모든 가로선의 중심선을 긋는다.
|
||||
horizontalLines.forEach((line, index) => {
|
||||
const nextLine = horizontalLines[(index + 1) % horizontalLines.length]
|
||||
|
||||
const startCenterX = Math.max(line.x1, nextLine.x1)
|
||||
const startCenterY = (line.y1 + nextLine.y1) / 2
|
||||
|
||||
let endCenterX = line.length >= nextLine.length ? startCenterX + line.length : startCenterX + nextLine.length
|
||||
const endCenterY = startCenterY
|
||||
|
||||
if (endCenterX > Math.max(line.x2, nextLine.x2)) {
|
||||
endCenterX = Math.max(line.x2, nextLine.x2)
|
||||
}
|
||||
|
||||
const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'red',
|
||||
strokeWidth: 1,
|
||||
direction: 'horizontal',
|
||||
})
|
||||
|
||||
// this.addWithUpdate(centerLine)
|
||||
|
||||
centerLines.push(centerLine)
|
||||
})
|
||||
|
||||
// 모든 세로선의 중심선을 긋는다.
|
||||
verticalLines.forEach((line, index) => {
|
||||
const nextLine = verticalLines[(index + 1) % verticalLines.length]
|
||||
|
||||
const startCenterX = (line.x1 + nextLine.x1) / 2
|
||||
const startCenterY = Math.min(line.y1, nextLine.y1)
|
||||
|
||||
const endCenterX = startCenterX
|
||||
let endCenterY = line.length >= nextLine.length ? startCenterY + line.length : startCenterY + nextLine.length
|
||||
|
||||
if (endCenterY > Math.max(line.y2, nextLine.y2)) {
|
||||
endCenterY = Math.max(line.y2, nextLine.y2)
|
||||
}
|
||||
|
||||
const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'blue',
|
||||
strokeWidth: 1,
|
||||
direction: 'vertical',
|
||||
})
|
||||
// this.addWithUpdate(centerLine)
|
||||
|
||||
centerLines.push(centerLine)
|
||||
})
|
||||
|
||||
const maxLength = horizontalMaxLength < verticalMaxLength ? horizontalMaxLength : verticalMaxLength
|
||||
|
||||
this.points.forEach((point, index) => {
|
||||
const wallPoint = this.wall.points[index]
|
||||
// 두 점의 좌표
|
||||
const x1 = point.x
|
||||
const y1 = point.y
|
||||
const x2 = wallPoint.x
|
||||
const y2 = wallPoint.y
|
||||
|
||||
let newX2, newY2
|
||||
|
||||
// x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1)
|
||||
|
||||
newX2 = Math.floor(x1 + (maxLength / 2 + 50) * Math.cos(angle))
|
||||
newY2 = Math.floor(y1 + (maxLength / 2 + 50) * Math.sin(angle))
|
||||
|
||||
const line = new QLine([x1, y1, newX2, newY2], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'green',
|
||||
idx: index,
|
||||
})
|
||||
this.addWithUpdate(line)
|
||||
helpLines.push(line)
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
|
||||
helpLines.forEach((line, index) => {
|
||||
if (line.isAlreadyInterSection) {
|
||||
return
|
||||
}
|
||||
const nextLine = helpLines[(index + 1 + helpLines.length) % helpLines.length]
|
||||
this.canvas.renderAll()
|
||||
|
||||
let intersectionPoint = calculateIntersection(line, nextLine)
|
||||
if (!intersectionPoint) {
|
||||
return
|
||||
}
|
||||
|
||||
line.set({ isAlreadyInterSection: true })
|
||||
nextLine.set({ isAlreadyInterSection: true })
|
||||
|
||||
const helpLine1 = new QLine([nextLine.x1, nextLine.y1, intersectionPoint.x, intersectionPoint.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'skyblue',
|
||||
})
|
||||
|
||||
const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'skyblue',
|
||||
})
|
||||
|
||||
ridgeStartPoints.push(intersectionPoint)
|
||||
this.addWithUpdate(helpLine1)
|
||||
this.addWithUpdate(helpLine2)
|
||||
this.removeWithUpdate(nextLine)
|
||||
this.removeWithUpdate(line)
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
|
||||
// 안만나는 선들
|
||||
const notInterSectionLines = helpLines.filter((line) => !line.isAlreadyInterSection)
|
||||
const ridgeEndPoints = []
|
||||
const interSectionPoints = []
|
||||
|
||||
notInterSectionLines.forEach((line, index) => {
|
||||
line.line.set({ strokeWidth: (index + 1) * 5 })
|
||||
|
||||
centerLines.forEach((centerLine) => {
|
||||
const interSectionPoint = calculateIntersection2(line, centerLine)
|
||||
|
||||
if (!this.inPolygon(interSectionPoint) || !interSectionPoint) {
|
||||
return
|
||||
}
|
||||
line.interSectionPoints.push(interSectionPoint)
|
||||
interSectionPoints.push(interSectionPoint)
|
||||
})
|
||||
})
|
||||
|
||||
ridgeStartPoints.forEach((point, index) => {
|
||||
let arrivalPoint
|
||||
let distance = Infinity
|
||||
let startPoint
|
||||
interSectionPoints.forEach((interSectionPoint) => {
|
||||
if (Math.abs(point.x - interSectionPoint.x) < 3 || Math.abs(point.y - interSectionPoint.y) < 3) {
|
||||
if (distanceBetweenPoints(point, interSectionPoint) < distance) {
|
||||
startPoint = point
|
||||
distance = distanceBetweenPoints(point, interSectionPoint)
|
||||
arrivalPoint = interSectionPoint
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (arrivalPoint) {
|
||||
const line = notInterSectionLines.filter((line) => line.interSectionPoints.includes(arrivalPoint))[0]
|
||||
|
||||
const ridge = new QLine([startPoint.x, startPoint.y, arrivalPoint.x, arrivalPoint.y], {
|
||||
stroke: 'black',
|
||||
fontSize: this.fontSize,
|
||||
})
|
||||
|
||||
const helpLine = new QLine([line.x1, line.y1, arrivalPoint.x, arrivalPoint.y], {
|
||||
stroke: 'red',
|
||||
fontSize: this.fontSize,
|
||||
})
|
||||
|
||||
ridgeEndPoints.push(arrivalPoint)
|
||||
this.addWithUpdate(ridge)
|
||||
this.addWithUpdate(helpLine)
|
||||
this.removeWithUpdate(line)
|
||||
this.canvas.renderAll()
|
||||
}
|
||||
})
|
||||
|
||||
ridgeEndPoints.forEach((point, index) => {
|
||||
const currentRidgeEndPoint = ridgeEndPoints[index]
|
||||
const nextRidgeEndPoint = ridgeEndPoints[(index + 1) % ridgeEndPoints.length]
|
||||
const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'green',
|
||||
})
|
||||
|
||||
this.addWithUpdate(ridgeConnectLine)
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
}
|
||||
drawHelpLineInHexagon(chon) {
|
||||
const historyLines = []
|
||||
const helpPoints = []
|
||||
const notInterSectionLines = []
|
||||
const ridge = []
|
||||
const maxLength = this.lines.reduce((max, obj) => Math.max(max, obj.length), 0)
|
||||
this.points.forEach((point, index) => {
|
||||
const wallPoint = this.wall.points[index]
|
||||
// 두 점의 좌표
|
||||
const x1 = point.x
|
||||
const y1 = point.y
|
||||
const x2 = wallPoint.x
|
||||
const y2 = wallPoint.y
|
||||
const historyLines = []
|
||||
const helpPoints = []
|
||||
const notInterSectionLines = []
|
||||
const ridge = []
|
||||
const maxLength = this.lines.reduce((max, obj) => Math.max(max, obj.length), 0)
|
||||
this.points.forEach((point, index) => {
|
||||
const wallPoint = this.wall.points[index]
|
||||
// 두 점의 좌표
|
||||
const x1 = point.x
|
||||
const y1 = point.y
|
||||
const x2 = wallPoint.x
|
||||
const y2 = wallPoint.y
|
||||
|
||||
let newX2, newY2
|
||||
|
||||
// x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1)
|
||||
|
||||
newX2 = x1 + (maxLength / 2) * Math.cos(angle)
|
||||
newY2 = y1 + (maxLength / 2) * Math.sin(angle)
|
||||
|
||||
const line = new QLine([x1, y1, newX2, newY2], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'blue',
|
||||
idx: index,
|
||||
})
|
||||
historyLines.push(line)
|
||||
this.canvas.add(line)
|
||||
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
|
||||
/**
|
||||
* 삼각 지붕
|
||||
*/
|
||||
historyLines.forEach((line, index) => {
|
||||
const prevLine = historyLines[(index - 1 + historyLines.length) % historyLines.length]
|
||||
|
||||
let intersectionPoint = calculateIntersection(line, prevLine)
|
||||
|
||||
if (!intersectionPoint) {
|
||||
notInterSectionLines.push(line)
|
||||
return
|
||||
}
|
||||
|
||||
const helpLine1 = new QLine([prevLine.x1, prevLine.y1, intersectionPoint.x, intersectionPoint.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'red',
|
||||
})
|
||||
|
||||
const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'red',
|
||||
})
|
||||
notInterSectionLines.pop()
|
||||
helpPoints.push(intersectionPoint)
|
||||
|
||||
this.canvas.add(helpLine1)
|
||||
this.canvas.add(helpLine2)
|
||||
this.canvas.remove(prevLine)
|
||||
this.canvas.remove(line)
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
// 용마루
|
||||
|
||||
const ridgePoint = []
|
||||
|
||||
helpPoints.forEach((helpPoint, index) => {
|
||||
const closestLine = findClosestLineToPoint(helpPoint, notInterSectionLines)
|
||||
|
||||
// 가까운 선의 중심점
|
||||
const centerClosestLinePoint = {
|
||||
x: (closestLine.x1 + closestLine.x2) / 2,
|
||||
y: (closestLine.y1 + closestLine.y2) / 2,
|
||||
}
|
||||
|
||||
const direction = getDirectionByPoint(helpPoint, centerClosestLinePoint)
|
||||
|
||||
let newX, newY
|
||||
|
||||
switch (direction) {
|
||||
case 'left': {
|
||||
newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1
|
||||
newY = helpPoint.y
|
||||
break
|
||||
}
|
||||
case 'right': {
|
||||
newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1
|
||||
newY = helpPoint.y
|
||||
break
|
||||
}
|
||||
case 'top': {
|
||||
newX = helpPoint.x
|
||||
newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1
|
||||
break
|
||||
}
|
||||
case 'bottom': {
|
||||
newX = helpPoint.x
|
||||
newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const ridgeHelpLine = new QLine([closestLine.x1, closestLine.y1, newX, newY], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'purple',
|
||||
strokeWidth: 5,
|
||||
})
|
||||
|
||||
ridgePoint.push({ x: newX, y: newY })
|
||||
|
||||
const ridge = new QLine([helpPoint.x, helpPoint.y, newX, newY], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'skyblue',
|
||||
strokeWidth: 5,
|
||||
})
|
||||
|
||||
this.canvas.add(ridge)
|
||||
this.canvas.renderAll()
|
||||
|
||||
this.canvas.add(ridgeHelpLine)
|
||||
this.canvas.remove(closestLine)
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
|
||||
// 용마루 끼리 연결
|
||||
for (let i = 0; i < ridgePoint.length; i = i + 2) {
|
||||
const currentRidgeEndPoint = ridgePoint[i]
|
||||
const nextRidgeEndPoint = ridgePoint[(i + 1) % ridgePoint.length]
|
||||
const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], {
|
||||
fontSize: this.fontSize,
|
||||
stroke: 'green',
|
||||
strokeWidth: 5,
|
||||
})
|
||||
this.canvas.add(ridgeConnectLine)
|
||||
this.canvas.renderAll()
|
||||
}
|
||||
|
||||
this.canvas.renderAll()
|
||||
})
|
||||
}
|
||||
|
||||
drawHelpLineInOctagon(chon) {}
|
||||
|
||||
getObject() {
|
||||
return this
|
||||
}
|
||||
sou
|
||||
|
||||
toObject(propertiesToInclude) {
|
||||
return fabric.util.object.extend(this.callSuper('toObject'), {
|
||||
points: this.points,
|
||||
fontSize: this.fontSize,
|
||||
name: this.name,
|
||||
shape: this.shape,
|
||||
texts: this.texts,
|
||||
lines: this.lines,
|
||||
wall: this.wall,
|
||||
initPoints: this.initPoints,
|
||||
initOption: this.initOption,
|
||||
objects: this.getObjects().map((obj) => obj.toObject(propertiesToInclude)),
|
||||
pattern: this.pattern,
|
||||
})
|
||||
}
|
||||
|
||||
_set(key, value) {
|
||||
this.callSuper('_set', key, value)
|
||||
}
|
||||
}
|
||||
@ -42,18 +42,18 @@ export function useCanvas(id) {
|
||||
useEffect(() => {
|
||||
canvas
|
||||
?.getObjects()
|
||||
.filter((obj) => obj.type === 'textbox' || obj.type === 'text' || obj.type === 'i-text')
|
||||
?.filter((obj) => obj.type === 'textbox' || obj.type === 'text' || obj.type === 'i-text')
|
||||
.forEach((obj) => {
|
||||
obj.set({ fontSize: fontSize })
|
||||
})
|
||||
|
||||
canvas
|
||||
?.getObjects()
|
||||
.filter((obj) => obj.type === 'QLine' || obj.type === 'QPolygon' || obj.type === 'QRect')
|
||||
?.filter((obj) => obj.type === 'QLine' || obj.type === 'QPolygon' || obj.type === 'QRect')
|
||||
.forEach((obj) => {
|
||||
obj.setFontSize(fontSize)
|
||||
})
|
||||
canvas?.renderAll()
|
||||
canvas?.getObjects().length > 0 && canvas?.renderAll()
|
||||
}, [fontSize])
|
||||
|
||||
/**
|
||||
@ -103,7 +103,7 @@ export function useCanvas(id) {
|
||||
}
|
||||
|
||||
const initialize = () => {
|
||||
canvas?.clear()
|
||||
canvas.getObjects().length > 0 && canvas?.clear()
|
||||
// settings for all canvas in the app
|
||||
fabric.Object.prototype.transparentCorners = false
|
||||
fabric.Object.prototype.cornerColor = '#2BEBC8'
|
||||
|
||||
@ -3,7 +3,16 @@ import QRect from '@/components/fabric/QRect'
|
||||
import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util'
|
||||
import { useRecoilState } from 'recoil'
|
||||
|
||||
import { canvasSizeState, fontSizeState, roofPolygonPatternArrayState, roofState, sortedPolygonArray, wallState } from '@/store/canvasAtom'
|
||||
import {
|
||||
canvasSizeState,
|
||||
fontSizeState,
|
||||
roofPolygonArrayState,
|
||||
roofPolygonPatternArrayState,
|
||||
roofState,
|
||||
sortedPolygonArray,
|
||||
templateTypeState,
|
||||
wallState,
|
||||
} from '@/store/canvasAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { fabric } from 'fabric'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
@ -17,6 +26,8 @@ export const Mode = {
|
||||
TEXTBOX: 'textbox',
|
||||
DRAW_RECT: 'drawRect',
|
||||
ROOF_PATTERN: 'roofPattern',
|
||||
MODULE: 'module',
|
||||
ROOT_TRESTLE: 'rootTrestle',
|
||||
DEFAULT: 'default',
|
||||
}
|
||||
|
||||
@ -39,6 +50,8 @@ export function useMode() {
|
||||
const pointCount = useRef(0)
|
||||
|
||||
const [roofPolygonPattern, setRoofPolygonPattern] = useRecoilState(roofPolygonPatternArrayState)
|
||||
const [roofPolygonArray, setRoofPolygonArray] = useRecoilState(roofPolygonArrayState)
|
||||
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
||||
|
||||
const [canvasSize] = useRecoilState(canvasSizeState)
|
||||
|
||||
@ -225,6 +238,9 @@ export function useMode() {
|
||||
case 'roofPattern':
|
||||
makeRoofPatternPolygon()
|
||||
break
|
||||
case 'rootTrestle':
|
||||
makeRoofTrestle()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -1013,9 +1029,7 @@ export function useMode() {
|
||||
/**
|
||||
*벽 지붕 외곽선 생성
|
||||
*/
|
||||
const handleOuterlinesTest = (offsetInputX, offsetInputY = 0) => {
|
||||
const polygon = drawWallPolygon()
|
||||
|
||||
const handleOuterlinesTest = (polygon, offsetInputX, offsetInputY = 0) => {
|
||||
let offsetPoints = []
|
||||
const originalMax = 71
|
||||
const transformedMax = 100
|
||||
@ -1083,7 +1097,7 @@ export function useMode() {
|
||||
offsetPoints.push(offsetPoint)
|
||||
}
|
||||
|
||||
makePolygon(offsetPoints)
|
||||
return makePolygon(offsetPoints)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1501,6 +1515,7 @@ export function useMode() {
|
||||
} else if (polygon.lines.length === 8) {
|
||||
handleOuterLineTemplateA8Points(polygon)
|
||||
}
|
||||
setTemplateType(2)
|
||||
}
|
||||
|
||||
const handleOuterLineTemplateA4Points = (polygon) => {
|
||||
@ -1622,15 +1637,15 @@ export function useMode() {
|
||||
|
||||
const dashedCenterLineOpt = {
|
||||
stroke: 'black',
|
||||
strokeWidth: 4,
|
||||
strokeWidth: 1,
|
||||
property: 'centerLine',
|
||||
strokeDashArray: [5, 5],
|
||||
strokeDashArray: [10, 5],
|
||||
fontSize: 14,
|
||||
}
|
||||
|
||||
const centerLineOpt = {
|
||||
stroke: 'blue',
|
||||
strokeWidth: 5,
|
||||
strokeWidth: 1,
|
||||
property: 'bigHoriCenter',
|
||||
fontSize: 14,
|
||||
}
|
||||
@ -1642,7 +1657,7 @@ export function useMode() {
|
||||
|
||||
const line = new QLine([start.x, start.y, end.x, end.y], {
|
||||
stroke: '#A0D468',
|
||||
strokeWidth: 5,
|
||||
strokeWidth: 2,
|
||||
property: 'normal',
|
||||
fontSize: 14,
|
||||
})
|
||||
@ -2944,6 +2959,8 @@ export function useMode() {
|
||||
console.log('6각형')
|
||||
handleTemplateB(params)
|
||||
}
|
||||
|
||||
setTemplateType(3)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3497,13 +3514,34 @@ export function useMode() {
|
||||
fontSize: 15, // fontSize는 필요에 따라 조정
|
||||
}
|
||||
|
||||
let polygonArray = []
|
||||
|
||||
roofPolygonPattern.roofPatternPolygonArray.forEach((patternPolygon, index) => {
|
||||
const drawPolygon = new QPolygon(patternPolygon, commonOption)
|
||||
drawPolygon.setViewLengthText(false)
|
||||
drawPolygon.sendToBack()
|
||||
canvas.add(drawPolygon)
|
||||
drawPolygon.setViewLengthText(false)
|
||||
polygonArray.push(drawPolygon)
|
||||
})
|
||||
canvas.renderAll()
|
||||
|
||||
setRoofPolygonArray(polygonArray)
|
||||
}
|
||||
|
||||
const makeRoofTrestle = () => {
|
||||
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
|
||||
alert('객체가 비어있습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
// 오목한 부분 인덱스 찾기
|
||||
const polygons = roofPolygonArray
|
||||
let concavePolygonObj = []
|
||||
|
||||
polygons.forEach((polygon, index) => {
|
||||
const trestlePolygon = handleOuterlinesTest(polygon, -12)
|
||||
trestlePolygon.set('stroke', 'red').set('strokeDashArray', [9, 5]).set('strokeWidth', 0.3).setViewLengthText(false)
|
||||
trestlePolygon.fillCell({ width: 100, height: 30, padding: 10 })
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
@ -3518,6 +3556,7 @@ export function useMode() {
|
||||
handleOuterlinesTest,
|
||||
handleOuterlinesTest2,
|
||||
makeRoofPatternPolygon,
|
||||
makeRoofTrestle,
|
||||
drawRoofPolygon,
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,12 @@ export const roofPolygonPatternArrayState = atom({
|
||||
dangerouslyAllowMutability: true,
|
||||
})
|
||||
|
||||
export const roofPolygonArrayState = atom({
|
||||
key: 'roofPolygonArray',
|
||||
default: [],
|
||||
dangerouslyAllowMutability: true,
|
||||
})
|
||||
|
||||
export const templateTypeState = atom({
|
||||
key: 'templateType',
|
||||
default: 1, //1:모임지붕, 2:A타입, 3:B타입
|
||||
|
||||
@ -430,6 +430,68 @@ export const sortedPoints = (points) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const sortedPointLessEightPoint = (points) => {
|
||||
const copyPoints = [...points]
|
||||
//points를 x,y좌표를 기준으로 정렬합니다.
|
||||
copyPoints.sort((a, b) => {
|
||||
if (a.x === b.x) {
|
||||
return a.y - b.y
|
||||
}
|
||||
return a.x - b.x
|
||||
})
|
||||
|
||||
const resultPoints = [copyPoints[0]]
|
||||
let index = 1
|
||||
let currentPoint = { ...copyPoints[0] }
|
||||
copyPoints.splice(0, 1)
|
||||
while (index < points.length) {
|
||||
if (index === points.length - 1) {
|
||||
resultPoints.push(copyPoints[0])
|
||||
index++
|
||||
break
|
||||
} else if (index % 2 === 0) {
|
||||
// 짝수번째는 y값이 같은 점을 찾는다.
|
||||
for (let i = 0; i < copyPoints.length; i++) {
|
||||
// y값이 같은 point가 많은 경우 그 중 x값이 가장 큰걸 찾는다.
|
||||
let temp = copyPoints.filter((point) => point.y === currentPoint.y)
|
||||
if (temp.length === 0) {
|
||||
// temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다.
|
||||
temp = Array.of(findClosestPointByY(currentPoint, copyPoints))
|
||||
}
|
||||
// temp중 x값이 가장 가까운 값
|
||||
|
||||
const min = temp.reduce((prev, current) => (Math.abs(current.x - currentPoint.x) <= Math.abs(prev.x - currentPoint.x) ? current : prev))
|
||||
|
||||
resultPoints.push(min)
|
||||
currentPoint = min
|
||||
copyPoints.splice(copyPoints.indexOf(min), 1)
|
||||
index++
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// 홀수번째는 x값이 같은 점을 찾는다.
|
||||
for (let i = 0; i < copyPoints.length; i++) {
|
||||
// x값이 같은 point가 많은 경우 그 중 y값이 가장 큰걸 찾는다.
|
||||
let temp = copyPoints.filter((point) => point.x === currentPoint.x)
|
||||
if (temp.length === 0) {
|
||||
// temp가 비어있을 경우 copyPoints에서 가장 가까운 점을 찾는다.
|
||||
|
||||
temp = Array.of(findClosestPointByX(currentPoint, copyPoints))
|
||||
}
|
||||
// temp중 y값이 가장 가까운 값
|
||||
const min = temp.reduce((prev, current) => (Math.abs(current.y - currentPoint.y) <= Math.abs(prev.y - currentPoint.y) ? current : prev))
|
||||
|
||||
resultPoints.push(min)
|
||||
currentPoint = min
|
||||
copyPoints.splice(copyPoints.indexOf(min), 1)
|
||||
index++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultPoints
|
||||
}
|
||||
|
||||
/**
|
||||
* point가 선 위에 있는지 확인
|
||||
* @param line
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user