Merge branch 'dev'
This commit is contained in:
commit
fd16b4cba3
BIN
Nextjs 14 컴포넌트에 대해서....pdf
Normal file
BIN
Nextjs 14 컴포넌트에 대해서....pdf
Normal file
Binary file not shown.
BIN
Qcast coding convention.pdf
Normal file
BIN
Qcast coding convention.pdf
Normal file
Binary file not shown.
BIN
Qcast development guilde.pdf
Normal file
BIN
Qcast development guilde.pdf
Normal file
Binary file not shown.
37
README.md
37
README.md
@ -1,19 +1,36 @@
|
||||
# 점 갯수 별 타입
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## 점 6개
|
||||
## Getting Started
|
||||
|
||||
### type1
|
||||
First, run the development server:
|
||||
|
||||

|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
### type2
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||

|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
### type3
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||

|
||||
## Learn More
|
||||
|
||||
### type4
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||

|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
||||
@ -10,6 +10,9 @@ const nextConfig = {
|
||||
// config.infrastructureLogging = { debug: /PackFileCache/ };
|
||||
return config
|
||||
},
|
||||
sassOptions: {
|
||||
includePaths: ['./src/styles'],
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.3.3",
|
||||
"prisma": "^5.17.0",
|
||||
"sass": "^1.77.8",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
19
shape-type.md
Normal file
19
shape-type.md
Normal file
@ -0,0 +1,19 @@
|
||||
# 점 갯수 별 타입
|
||||
|
||||
## 점 6개
|
||||
|
||||
### type1
|
||||
|
||||

|
||||
|
||||
### type2
|
||||
|
||||

|
||||
|
||||
### type3
|
||||
|
||||

|
||||
|
||||
### type4
|
||||
|
||||

|
||||
@ -47,6 +47,9 @@ export default function changelogPage() {
|
||||
<Button onClick={handleUsers}>Button</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="test">
|
||||
<p className="text-white">Sass 테스트입니다.</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css'
|
||||
import '../styles/style.scss'
|
||||
import Headers from '@/components/Headers'
|
||||
import RecoilRootWrapper from './RecoilWrapper'
|
||||
import UIProvider from './UIProvider'
|
||||
|
||||
@ -2,15 +2,15 @@ import { useCanvas } from '@/hooks/useCanvas'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Mode, useMode } from '@/hooks/useMode'
|
||||
import { Button } from '@nextui-org/react'
|
||||
import QRect from '@/components/fabric/QRect'
|
||||
|
||||
import RangeSlider from './ui/RangeSlider'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { canvasSizeState, fontSizeState, roofMaterialState, roofState, sortedPolygonArray } from '@/store/canvasAtom'
|
||||
import { canvasSizeState, fontSizeState, roofMaterialState, sortedPolygonArray } from '@/store/canvasAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
||||
import { calculateIntersection } from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import offsetPolygon from '@/util/qpolygon-utils'
|
||||
|
||||
export default function Roof2() {
|
||||
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
||||
@ -48,6 +48,7 @@ export default function Roof2() {
|
||||
handleOuterlinesTest2,
|
||||
applyTemplateB,
|
||||
makeRoofPatternPolygon,
|
||||
createRoofRack,
|
||||
} = useMode()
|
||||
|
||||
// const [canvasState, setCanvasState] = useRecoilState(canvasAtom)
|
||||
@ -59,22 +60,6 @@ export default function Roof2() {
|
||||
changeMode(canvas, mode)
|
||||
}, [canvas, mode])
|
||||
|
||||
const makeRect = () => {
|
||||
if (canvas) {
|
||||
const rect = new QRect({
|
||||
left: 100,
|
||||
top: 100,
|
||||
fill: 'transparent',
|
||||
stroke: 'black',
|
||||
width: 400,
|
||||
height: 100,
|
||||
fontSize: fontSize,
|
||||
})
|
||||
|
||||
canvas?.add(rect)
|
||||
}
|
||||
}
|
||||
|
||||
const makeLine = () => {
|
||||
if (canvas) {
|
||||
const line = new QLine([50, 50, 200, 50], {
|
||||
@ -264,7 +249,7 @@ export default function Roof2() {
|
||||
{ x: 675, y: 275 },
|
||||
{ x: 450, y: 850 },
|
||||
]
|
||||
const polygon = new QPolygon(type2, {
|
||||
const polygon = new QPolygon(type1, {
|
||||
fill: 'transparent',
|
||||
stroke: 'black',
|
||||
strokeWidth: 1,
|
||||
@ -448,21 +433,6 @@ export default function Roof2() {
|
||||
makeRoofPatternPolygon(roofStyle)
|
||||
}
|
||||
|
||||
const createRoofRack = () => {
|
||||
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
|
||||
roofs.forEach((roof) => {
|
||||
let maxLengthLine = roof.lines.reduce((acc, cur) => {
|
||||
return acc.length > cur.length ? acc : cur
|
||||
})
|
||||
|
||||
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
||||
roof.fillCell({ width: 50, height: 100, padding: 0 })
|
||||
} else {
|
||||
roof.fillCell({ width: 100, height: 50, padding: 0 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{canvas && (
|
||||
@ -501,9 +471,6 @@ export default function Roof2() {
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.TEXTBOX ? 'primary' : 'default'}`} onClick={() => setMode(Mode.TEXTBOX)}>
|
||||
텍스트박스 모드
|
||||
</Button>
|
||||
<Button className="m-1 p-2" color={`${mode === Mode.DRAW_RECT ? 'primary' : 'default'}`} onClick={() => setMode(Mode.DRAW_RECT)}>
|
||||
사각형 생성 모드
|
||||
</Button>
|
||||
<Button className="m-1 p-2" onClick={handleUndo}>
|
||||
Undo
|
||||
</Button>
|
||||
@ -520,9 +487,6 @@ export default function Roof2() {
|
||||
축소
|
||||
</Button>
|
||||
현재 줌 : {zoom}%
|
||||
<Button className="m-1 p-2" onClick={makeRect}>
|
||||
사각형만들기
|
||||
</Button>
|
||||
<Button className="m-1 p-2" onClick={makeLine}>
|
||||
선 추가
|
||||
</Button>
|
||||
|
||||
@ -13,6 +13,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
hips: [],
|
||||
ridges: [],
|
||||
connectRidges: [],
|
||||
cells: [],
|
||||
initialize: function (points, options, canvas) {
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
@ -223,6 +224,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
|
||||
const drawCellsArray = [] //그려진 셀의 배열
|
||||
|
||||
let idx = 1
|
||||
|
||||
for (let i = 0; i < cols; i++) {
|
||||
for (let j = 0; j < rows; j++) {
|
||||
const rectLeft = minX + i * (rectWidth + cell.padding) + tmpWidth
|
||||
@ -251,13 +254,18 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
lockScalingX: true, // X 축 크기 조정 잠금
|
||||
lockScalingY: true, // Y 축 크기 조정 잠금
|
||||
opacity: 0.8,
|
||||
name: 'cell',
|
||||
idx: idx,
|
||||
})
|
||||
|
||||
idx++
|
||||
drawCellsArray.push(rect) //배열에 넣어서 반환한다
|
||||
this.canvas.add(rect)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.canvas?.renderAll()
|
||||
this.cells = drawCellsArray
|
||||
return drawCellsArray
|
||||
},
|
||||
inPolygon(point) {
|
||||
|
||||
@ -1,98 +0,0 @@
|
||||
import { fabric } from 'fabric'
|
||||
export default class QRect extends fabric.Rect {
|
||||
#text = []
|
||||
#viewLengthText
|
||||
#fontSize
|
||||
type = 'QRect'
|
||||
constructor(option) {
|
||||
if (!option.fontSize) {
|
||||
throw new Error('Font size is required.')
|
||||
}
|
||||
super(option)
|
||||
this.#fontSize = option.fontSize
|
||||
this.#init(option)
|
||||
this.#addControl()
|
||||
}
|
||||
|
||||
#init(option) {
|
||||
this.#viewLengthText = option.viewLengthText ?? true
|
||||
}
|
||||
|
||||
setViewLengthText(bool) {
|
||||
this.#viewLengthText = bool
|
||||
this.#addLengthText()
|
||||
}
|
||||
|
||||
setFontSize(fontSize) {
|
||||
this.#fontSize = fontSize
|
||||
this.#addLengthText()
|
||||
}
|
||||
|
||||
#addControl() {
|
||||
this.on('removed', () => {
|
||||
if (this.#text.length > 0) {
|
||||
this.#text.forEach((text) => {
|
||||
this.canvas.remove(text)
|
||||
})
|
||||
this.#text = []
|
||||
}
|
||||
})
|
||||
|
||||
this.on('added', () => {
|
||||
this.#addLengthText()
|
||||
})
|
||||
|
||||
this.on('modified', (e) => {
|
||||
this.#addLengthText()
|
||||
})
|
||||
|
||||
this.on('scaling', (e) => {
|
||||
this.#addLengthText()
|
||||
})
|
||||
|
||||
this.on('moving', () => {
|
||||
this.#addLengthText()
|
||||
})
|
||||
}
|
||||
#addLengthText() {
|
||||
if (this.#text.length > 0) {
|
||||
this.#text.forEach((text) => {
|
||||
this.canvas.remove(text)
|
||||
})
|
||||
this.#text = []
|
||||
}
|
||||
|
||||
if (!this.#viewLengthText) {
|
||||
return
|
||||
}
|
||||
|
||||
const scaleX = this.scaleX
|
||||
const scaleY = this.scaleY
|
||||
|
||||
const lines = [
|
||||
{
|
||||
start: { x: this.left, y: this.top },
|
||||
end: { x: this.left + this.width * scaleX, y: this.top },
|
||||
},
|
||||
{
|
||||
start: { x: this.left, y: this.top + this.height * scaleY },
|
||||
end: { x: this.left, y: this.top },
|
||||
},
|
||||
]
|
||||
|
||||
lines.forEach((line) => {
|
||||
const dx = line.end.x - line.start.x
|
||||
const dy = line.end.y - line.start.y
|
||||
const length = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
const text = new fabric.Text(length.toFixed(0), {
|
||||
left: (line.start.x + line.end.x) / 2,
|
||||
top: (line.start.y + line.end.y) / 2,
|
||||
fontSize: this.#fontSize,
|
||||
selectable: false,
|
||||
})
|
||||
this.#text.push(text)
|
||||
this.canvas.add(text)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,6 @@ import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/can
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { canvasSizeState, fontSizeState } from '@/store/canvasAtom'
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import QRect from '@/components/fabric/QRect'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import { defineQLine } from '@/util/qline-utils'
|
||||
import { defineQPloygon } from '@/util/qpolygon-utils'
|
||||
@ -65,6 +64,7 @@ export function useCanvas(id) {
|
||||
if (canvas) {
|
||||
initialize()
|
||||
canvas?.on('object:added', onChange)
|
||||
canvas?.on('object:added', addEventOnObject)
|
||||
canvas?.on('object:modified', onChange)
|
||||
canvas?.on('object:removed', onChange)
|
||||
canvas?.on('mouse:move', drawMouseLines)
|
||||
@ -93,6 +93,21 @@ export function useCanvas(id) {
|
||||
canvas?.off('mouse:down', handleMouseDown)
|
||||
}
|
||||
|
||||
const addEventOnObject = (e) => {
|
||||
const target = e.target
|
||||
if (target.name === 'cell') {
|
||||
target.on('mousedown', () => {
|
||||
target.set({ fill: 'red' })
|
||||
})
|
||||
}
|
||||
|
||||
if (target.name === 'trestle') {
|
||||
target.on('mousedown', () => {
|
||||
target.set({ strokeWidth: 5 })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 마우스 포인터의 가이드라인을 제거합니다.
|
||||
*/
|
||||
@ -116,7 +131,6 @@ export function useCanvas(id) {
|
||||
fabric.QPolygon = QPolygon
|
||||
QPolygon.prototype.canvas = canvas
|
||||
QLine.prototype.canvas = canvas
|
||||
QRect.prototype.canvas = canvas
|
||||
defineQLine()
|
||||
defineQPloygon()
|
||||
}
|
||||
@ -542,7 +556,24 @@ export function useCanvas(id) {
|
||||
const addCanvas = () => {
|
||||
// const canvasState = canvas
|
||||
|
||||
const objs = canvas?.toJSON(['selectable', 'name', 'parentId', 'id', 'length', 'idx', 'direction', 'lines', 'points'])
|
||||
const objs = canvas?.toJSON([
|
||||
'selectable',
|
||||
'name',
|
||||
'parentId',
|
||||
'id',
|
||||
'length',
|
||||
'idx',
|
||||
'direction',
|
||||
'lines',
|
||||
'points',
|
||||
'lockMovementX',
|
||||
'lockMovementY',
|
||||
'lockRotation',
|
||||
'lockScalingX',
|
||||
'lockScalingY',
|
||||
'opacity',
|
||||
'cells',
|
||||
])
|
||||
|
||||
const str = JSON.stringify(objs)
|
||||
|
||||
@ -552,16 +583,12 @@ export function useCanvas(id) {
|
||||
// 역직렬화하여 캔버스에 객체를 다시 추가합니다.
|
||||
canvas?.loadFromJSON(JSON.parse(str), function () {
|
||||
// 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
|
||||
console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
|
||||
canvas?.renderAll() // 캔버스를 다시 그립니다.
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const changeCanvas = (idx) => {
|
||||
canvas?.clear()
|
||||
const canvasState = JSON.parse(canvasList[idx])
|
||||
}
|
||||
|
||||
return {
|
||||
canvas,
|
||||
addShape,
|
||||
@ -577,6 +604,5 @@ export function useCanvas(id) {
|
||||
handleFlip,
|
||||
setCanvasBackgroundWithDots,
|
||||
addCanvas,
|
||||
changeCanvas,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import QRect from '@/components/fabric/QRect'
|
||||
import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util'
|
||||
import { useRecoilState } from 'recoil'
|
||||
|
||||
@ -17,6 +16,7 @@ import {
|
||||
import { QLine } from '@/components/fabric/QLine'
|
||||
import { fabric } from 'fabric'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
import offsetPolygon from '@/util/qpolygon-utils'
|
||||
|
||||
export const Mode = {
|
||||
DRAW_LINE: 'drawLine', // 기준선 긋기모드`
|
||||
@ -58,6 +58,7 @@ export function useMode() {
|
||||
|
||||
const [selectedCellRoofArray, setSelectedCellRoofArray] = useState([])
|
||||
const [drewRoofCells, setDrewRoofCells] = useRecoilState(drewRoofCellsState)
|
||||
const [roofStyle, setRoofStyle] = useState(1) //기본 지붕 패턴
|
||||
|
||||
useEffect(() => {
|
||||
// 이벤트 리스너 추가
|
||||
@ -521,6 +522,7 @@ export function useMode() {
|
||||
|
||||
// handleOuterlines()
|
||||
const wall = makePolygon()
|
||||
wall.set({ name: 'wall' })
|
||||
setWall(wall)
|
||||
|
||||
return wall
|
||||
@ -614,27 +616,6 @@ export function useMode() {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1112,67 +1093,14 @@ export function useMode() {
|
||||
/**
|
||||
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
|
||||
*/
|
||||
const handleOuterlinesTest2 = (polygon, offset = 71) => {
|
||||
const offsetPoints = []
|
||||
const sortedIndex = getStartIndex(polygon.lines)
|
||||
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
|
||||
const handleOuterlinesTest2 = (polygon, offset = 50) => {
|
||||
const offsetPoints = offsetPolygon(polygon.points, offset)
|
||||
|
||||
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)
|
||||
const roof = makePolygon(
|
||||
offsetPoints.map((point) => {
|
||||
return { x1: point.x, y1: point.y }
|
||||
}),
|
||||
)
|
||||
roof.setWall(polygon)
|
||||
setRoof(roof)
|
||||
|
||||
@ -1233,15 +1161,15 @@ export function useMode() {
|
||||
setTemplateType(2)
|
||||
}
|
||||
|
||||
const handleOuterLineTemplateA4Points = (polygon) => {
|
||||
const edge = 20
|
||||
const eaves = 50
|
||||
const handleOuterLineTemplateA4Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
|
||||
const edge = offsetInputX
|
||||
const eaves = offsetInputY
|
||||
|
||||
// 폴리곤의 각 변을 선으로 생성
|
||||
const createLine = (start, end, stroke, property) =>
|
||||
new QLine([start.x, start.y, end.x, end.y], {
|
||||
stroke,
|
||||
strokeWidth: 5,
|
||||
strokeWidth: 1,
|
||||
property,
|
||||
fontSize: 14,
|
||||
})
|
||||
@ -1279,7 +1207,7 @@ export function useMode() {
|
||||
strokeDashArray: dashArray,
|
||||
})
|
||||
|
||||
const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 4, 'center')
|
||||
const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 1, 'center')
|
||||
canvas.add(vertCenterLine)
|
||||
|
||||
const horiCenterLineLeft = createCenterLine(
|
||||
@ -1341,6 +1269,31 @@ export function useMode() {
|
||||
const outLine = createLine({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, 'blue', 'normal')
|
||||
canvas.add(outLine)
|
||||
})
|
||||
|
||||
const roofPatternPolygonArray = []
|
||||
|
||||
const leftLine = drawArray[0]
|
||||
const rightLine = drawArray[3]
|
||||
|
||||
//사각형 왼쪽 지붕 패턴 생성 배열
|
||||
const leftPolygon = [
|
||||
{ x: leftLine.x1, y: leftLine.y1 },
|
||||
{ x: leftLine.x2, y: leftLine.y2 },
|
||||
{ x: vertCenterLine.x1, y: vertCenterLine.y1 },
|
||||
{ x: vertCenterLine.x2, y: vertCenterLine.y2 },
|
||||
]
|
||||
roofPatternPolygonArray.push(leftPolygon)
|
||||
|
||||
//사각형 오른쪽 지붕 패턴 생성 배열
|
||||
const rightPolygon = [
|
||||
{ x: vertCenterLine.x1, y: vertCenterLine.y1 },
|
||||
{ x: vertCenterLine.x2, y: vertCenterLine.y2 },
|
||||
{ x: rightLine.x1, y: rightLine.y1 },
|
||||
{ x: rightLine.x2, y: rightLine.y2 },
|
||||
]
|
||||
roofPatternPolygonArray.push(rightPolygon)
|
||||
|
||||
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장
|
||||
}
|
||||
|
||||
//탬플릿A 적용
|
||||
@ -2379,8 +2332,6 @@ export function useMode() {
|
||||
let tmpArray = []
|
||||
let tmpBigArray = []
|
||||
|
||||
console.log('tmpVertCenterLine', tmpVertCenterLine)
|
||||
|
||||
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
|
||||
|
||||
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
|
||||
@ -2455,8 +2406,6 @@ export function useMode() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('roofPatternPolygonArray', roofPatternPolygonArray)
|
||||
|
||||
setRoofPolygonPattern({ roofPatternPolygonArray, lines })
|
||||
} else {
|
||||
// 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ
|
||||
@ -3179,23 +3128,7 @@ export function useMode() {
|
||||
canvas?.renderAll()
|
||||
}
|
||||
|
||||
/**
|
||||
* 지붕 패턴 생성 로직
|
||||
* @param roofStyle
|
||||
*/
|
||||
const makeRoofPatternPolygon = (roofStyle) => {
|
||||
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
|
||||
alert('객체가 비어있습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
//내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요
|
||||
roofPolygonPattern.lines.forEach((line, index) => {
|
||||
line.line.set('strokeDashArray', [10, 5, 2, 5])
|
||||
line.line.set('stroke', 'blue')
|
||||
line.line.set('strokeWidth', 1)
|
||||
})
|
||||
|
||||
const getRoofPattern = (roofStyle, mode = 'normal') => {
|
||||
const ratio = window.devicePixelRatio || 1
|
||||
|
||||
const inputPatternSize = { width: 30, height: 20 } //임시 사이즈
|
||||
@ -3214,6 +3147,12 @@ export function useMode() {
|
||||
|
||||
// 벽돌 패턴 그리기
|
||||
ctx.scale(ratio, ratio)
|
||||
|
||||
if (mode === 'cell') {
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'
|
||||
ctx.fillRect(0, 0, patternSize.width * 2, patternSize.height * 2)
|
||||
}
|
||||
|
||||
ctx.strokeStyle = 'green'
|
||||
ctx.lineWidth = 0.4
|
||||
|
||||
@ -3241,6 +3180,30 @@ export function useMode() {
|
||||
repeat: 'repeat',
|
||||
})
|
||||
|
||||
return pattern
|
||||
}
|
||||
|
||||
/**
|
||||
* 지붕 패턴 생성 로직
|
||||
* @param roofStyle
|
||||
*/
|
||||
const makeRoofPatternPolygon = (roofStyle) => {
|
||||
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
|
||||
alert('객체가 비어있습니다.')
|
||||
return
|
||||
}
|
||||
|
||||
setRoofStyle(roofStyle) //클릭한 지붕패턴을 저장
|
||||
|
||||
//내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요
|
||||
roofPolygonPattern.lines.forEach((line, index) => {
|
||||
line.line.set('strokeDashArray', [10, 5, 2, 5])
|
||||
line.line.set('stroke', 'blue')
|
||||
line.line.set('strokeWidth', 1)
|
||||
})
|
||||
|
||||
const pattern = getRoofPattern(roofStyle)
|
||||
|
||||
const commonOption = {
|
||||
fill: pattern,
|
||||
selectable: false,
|
||||
@ -3316,14 +3279,16 @@ export function useMode() {
|
||||
canvas.discardActiveObject() // 객체의 활성 상태 해제
|
||||
|
||||
//폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함
|
||||
const removeIndex = polygon.customIndex
|
||||
const removeArrayIndex = selectedAreaArray.findIndex((x) => x.customIndex === removeIndex)
|
||||
const removeIndex = polygon.idx
|
||||
const removeArrayIndex = selectedAreaArray.findIndex((x) => x.idx === removeIndex)
|
||||
selectedAreaArray.splice(removeArrayIndex, 1)
|
||||
}
|
||||
canvas?.renderAll()
|
||||
}
|
||||
|
||||
//외각선을 안쪽으로 그려 가대선을 그린다.
|
||||
const pattern = getRoofPattern(roofStyle, 'cell')
|
||||
|
||||
// 외각선을 안쪽으로 그려 가대선을 그린다.
|
||||
polygons.forEach((polygon, index) => {
|
||||
const trestlePolygon = handleOuterlinesTest(polygon, -12)
|
||||
trestlePolygon.setViewLengthText(false) //얘는 set으로 안먹는다...
|
||||
@ -3337,7 +3302,8 @@ export function useMode() {
|
||||
lockScalingX: true,
|
||||
lockScalingY: true,
|
||||
bringToFront: true,
|
||||
customIndex: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌
|
||||
idx: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌
|
||||
name: 'trestlePolygon',
|
||||
})
|
||||
|
||||
/**
|
||||
@ -3346,8 +3312,15 @@ export function useMode() {
|
||||
trestlePolygon.on('mousedown', function () {
|
||||
toggleSelection(trestlePolygon)
|
||||
})
|
||||
|
||||
console.log('polygon', polygon)
|
||||
|
||||
polygon.set({ fill: pattern })
|
||||
})
|
||||
|
||||
setSelectedCellRoofArray(selectedAreaArray)
|
||||
canvas?.renderAll()
|
||||
setMode(Mode.DEFAULT) //default 모드로 변경
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3392,6 +3365,51 @@ export function useMode() {
|
||||
setMode(Mode.DEFAULT) //default 모드로 변경
|
||||
}
|
||||
|
||||
const createRoofRack = () => {
|
||||
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
|
||||
let roofCells = [] // roof에 적재된 cell들
|
||||
roofs.forEach((roof, index) => {
|
||||
let maxLengthLine = roof.lines.reduce((acc, cur) => {
|
||||
return acc.length > cur.length ? acc : cur
|
||||
})
|
||||
|
||||
const offsetPolygonPoint = offsetPolygon(roof.points, -20, 0)
|
||||
|
||||
const trestlePoly = new QPolygon(offsetPolygonPoint, {
|
||||
fill: 'transparent',
|
||||
stroke: 'red',
|
||||
strokeDashArray: [5, 5],
|
||||
strokeWidth: 1,
|
||||
selectable: true,
|
||||
fontSize: fontSize,
|
||||
name: 'trestle',
|
||||
lockMovementX: true, // X 축 이동 잠금
|
||||
lockMovementY: true, // Y 축 이동 잠금
|
||||
lockRotation: true, // 회전 잠금
|
||||
lockScalingX: true, // X 축 크기 조정 잠금
|
||||
lockScalingY: true, // Y 축 크기 조정 잠금
|
||||
idx: index,
|
||||
})
|
||||
|
||||
canvas?.add(trestlePoly)
|
||||
|
||||
let drawRoofCells
|
||||
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
||||
drawRoofCells = trestlePoly.fillCell({ width: 100, height: 50, padding: 10 })
|
||||
trestlePoly.direction = 'south'
|
||||
} else {
|
||||
drawRoofCells = trestlePoly.fillCell({ width: 50, height: 100, padding: 10 })
|
||||
trestlePoly.direction = 'east'
|
||||
}
|
||||
|
||||
drawRoofCells.forEach((cell) => {
|
||||
roofCells.push(cell)
|
||||
})
|
||||
})
|
||||
|
||||
setDrewRoofCells(roofCells)
|
||||
}
|
||||
|
||||
return {
|
||||
mode,
|
||||
setMode,
|
||||
@ -3406,5 +3424,6 @@ export function useMode() {
|
||||
handleOuterlinesTest2,
|
||||
makeRoofPatternPolygon,
|
||||
makeRoofTrestle,
|
||||
createRoofRack,
|
||||
}
|
||||
}
|
||||
|
||||
3
src/styles/_test.scss
Normal file
3
src/styles/_test.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.test {
|
||||
background-color: #121212;
|
||||
}
|
||||
1
src/styles/style.scss
Normal file
1
src/styles/style.scss
Normal file
@ -0,0 +1 @@
|
||||
@import '_test.scss';
|
||||
@ -3,6 +3,8 @@ import { QLine } from '@/components/fabric/QLine'
|
||||
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
|
||||
const TWO_PI = Math.PI * 2
|
||||
|
||||
export const defineQPloygon = () => {
|
||||
fabric.QPolygon.fromObject = function (object, callback) {
|
||||
fabric.Object._fromObject('QPolygon', object, callback, 'points')
|
||||
@ -251,6 +253,10 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
|
||||
name: 'hip',
|
||||
})
|
||||
|
||||
if (line.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
line.startPoint = ridgePoint
|
||||
line.endPoint = filteredCenterInterSectionPoints
|
||||
|
||||
@ -281,54 +287,74 @@ export const drawHelpLineInHexagon = (polygon, chon) => {
|
||||
const startPoint = remainingPoints.shift()
|
||||
const endPoint = remainingPoints.shift()
|
||||
|
||||
const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'connectRidge',
|
||||
})
|
||||
if (!(startPoint.x === endPoint.x && startPoint.y === endPoint.y)) {
|
||||
const line = new QLine([startPoint.x, startPoint.y, endPoint.x, endPoint.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'connectRidge',
|
||||
})
|
||||
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = endPoint
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = endPoint
|
||||
|
||||
polygon.connectRidges.push(line)
|
||||
polygon.connectRidges.push(line)
|
||||
|
||||
polygon.points.forEach((point) => {
|
||||
const degree = calculateAngle(startPoint, point)
|
||||
polygon.points.forEach((point) => {
|
||||
const degree = calculateAngle(startPoint, point)
|
||||
|
||||
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
|
||||
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'hip',
|
||||
})
|
||||
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
|
||||
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'hip',
|
||||
})
|
||||
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = point
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = point
|
||||
|
||||
polygon.hips.push(line)
|
||||
polygon.canvas.add(line)
|
||||
}
|
||||
})
|
||||
polygon.hips.push(line)
|
||||
polygon.canvas.add(line)
|
||||
}
|
||||
})
|
||||
|
||||
polygon.points.forEach((point) => {
|
||||
const degree = calculateAngle(endPoint, point)
|
||||
polygon.points.forEach((point) => {
|
||||
const degree = calculateAngle(endPoint, point)
|
||||
|
||||
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
|
||||
const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'hip',
|
||||
})
|
||||
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
|
||||
const line = new QLine([endPoint.x, endPoint.y, point.x, point.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'hip',
|
||||
})
|
||||
|
||||
line.startPoint = endPoint
|
||||
line.endPoint = point
|
||||
line.startPoint = endPoint
|
||||
line.endPoint = point
|
||||
|
||||
polygon.hips.push(line)
|
||||
polygon.canvas.add(line)
|
||||
}
|
||||
})
|
||||
polygon.hips.push(line)
|
||||
polygon.canvas.add(line)
|
||||
}
|
||||
})
|
||||
|
||||
polygon.canvas.add(line)
|
||||
polygon.canvas.add(line)
|
||||
} else {
|
||||
polygon.points.forEach((point) => {
|
||||
const degree = calculateAngle(startPoint, point)
|
||||
|
||||
if (Math.abs(degree) === 45 || Math.abs(degree) === 135) {
|
||||
const line = new QLine([startPoint.x, startPoint.y, point.x, point.y], {
|
||||
stroke: 'purple',
|
||||
fontSize: polygon.fontSize,
|
||||
name: 'hip',
|
||||
})
|
||||
|
||||
line.startPoint = startPoint
|
||||
line.endPoint = point
|
||||
|
||||
polygon.hips.push(line)
|
||||
polygon.canvas.add(line)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,9 +509,8 @@ export const dividePolygon = (polygon) => {
|
||||
}
|
||||
})
|
||||
hips = [...hips, ...connectRidges]
|
||||
polygon.setViewLengthText(false)
|
||||
|
||||
polygonLines.forEach((line) => {
|
||||
polygonLines.forEach((line, index) => {
|
||||
let ridge
|
||||
|
||||
const startPoint = line.startPoint
|
||||
@ -520,15 +545,31 @@ export const dividePolygon = (polygon) => {
|
||||
return
|
||||
}
|
||||
|
||||
let connectedRidge = ridges.find(
|
||||
(ridge) =>
|
||||
(ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
|
||||
(ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
|
||||
)
|
||||
let connectedRidge
|
||||
const restRidgeConnection = connectRidges[0]
|
||||
|
||||
if (!restRidgeConnection || restRidgeConnection.length === 0) {
|
||||
connectedRidge = ridges.find(
|
||||
(ridge) =>
|
||||
(ridge.startPoint.x === startHip.endPoint.x &&
|
||||
ridge.startPoint.y === startHip.endPoint.y &&
|
||||
ridge.endPoint.x === endHip.endPoint.x &&
|
||||
ridge.endPoint.y === endHip.endPoint.y) ||
|
||||
(ridge.startPoint.x === endHip.endPoint.x &&
|
||||
ridge.startPoint.y === endHip.endPoint.y &&
|
||||
ridge.endPoint.x === startHip.endPoint.x &&
|
||||
ridge.endPoint.y === startHip.endPoint.y),
|
||||
)
|
||||
} else {
|
||||
connectedRidge = ridges.find(
|
||||
(ridge) =>
|
||||
(ridge.startPoint.x === startHip.endPoint.x && ridge.startPoint.y === startHip.endPoint.y) ||
|
||||
(ridge.endPoint.x === startHip.endPoint.x && ridge.endPoint.y === startHip.endPoint.y),
|
||||
)
|
||||
}
|
||||
|
||||
const hipStartPoint = startHip.endPoint
|
||||
const hipEndPoint = endHip.endPoint
|
||||
const restRidgeConnection = connectRidges[0]
|
||||
|
||||
if (connectedRidge.startPoint.x === hipStartPoint.x && connectedRidge.startPoint.y === hipStartPoint.y) {
|
||||
if (connectedRidge.endPoint.x === hipEndPoint.x && connectedRidge.endPoint.y === hipEndPoint.y) {
|
||||
@ -676,3 +717,239 @@ const getOneSideLine = (line) => {
|
||||
|
||||
return newLine
|
||||
}
|
||||
|
||||
function inwardEdgeNormal(vertex1, vertex2) {
|
||||
// Assuming that polygon vertices are in clockwise order
|
||||
const dx = vertex2.x - vertex1.x
|
||||
const dy = vertex2.y - vertex1.y
|
||||
const edgeLength = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
return {
|
||||
x: -dy / edgeLength,
|
||||
y: dx / edgeLength,
|
||||
}
|
||||
}
|
||||
|
||||
function outwardEdgeNormal(vertex1, vertex2) {
|
||||
var n = inwardEdgeNormal(vertex1, vertex2)
|
||||
|
||||
return {
|
||||
x: -n.x,
|
||||
y: -n.y,
|
||||
}
|
||||
}
|
||||
|
||||
function createPolygon(vertices) {
|
||||
const edges = []
|
||||
let minX = vertices.length > 0 ? vertices[0].x : undefined
|
||||
let minY = vertices.length > 0 ? vertices[0].y : undefined
|
||||
let maxX = minX
|
||||
let maxY = minY
|
||||
|
||||
for (let i = 0; i < vertices.length; i++) {
|
||||
const vertex1 = vertices[i]
|
||||
const vertex2 = vertices[(i + 1) % vertices.length]
|
||||
|
||||
const outwardNormal = outwardEdgeNormal(vertex1, vertex2)
|
||||
|
||||
const inwardNormal = inwardEdgeNormal(vertex1, vertex2)
|
||||
|
||||
const edge = {
|
||||
vertex1,
|
||||
vertex2,
|
||||
index: i,
|
||||
outwardNormal,
|
||||
inwardNormal,
|
||||
}
|
||||
|
||||
edges.push(edge)
|
||||
|
||||
const x = vertices[i].x
|
||||
const y = vertices[i].y
|
||||
minX = Math.min(x, minX)
|
||||
minY = Math.min(y, minY)
|
||||
maxX = Math.max(x, maxX)
|
||||
maxY = Math.max(y, maxY)
|
||||
}
|
||||
|
||||
return {
|
||||
vertices,
|
||||
edges,
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
}
|
||||
}
|
||||
|
||||
// based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, edgeA => "line a", edgeB => "line b"
|
||||
|
||||
function edgesIntersection(edgeA, edgeB) {
|
||||
const den =
|
||||
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
|
||||
(edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)
|
||||
|
||||
if (den == 0) {
|
||||
return null // lines are parallel or coincident
|
||||
}
|
||||
|
||||
const ua =
|
||||
((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
|
||||
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
|
||||
den
|
||||
|
||||
const ub =
|
||||
((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
|
||||
(edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
|
||||
den
|
||||
|
||||
// Edges are not intersecting but the lines defined by them are
|
||||
const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1
|
||||
|
||||
return {
|
||||
x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
|
||||
y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
|
||||
isIntersectionOutside,
|
||||
}
|
||||
}
|
||||
|
||||
function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) {
|
||||
var startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x)
|
||||
var endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x)
|
||||
|
||||
if (startAngle < 0) {
|
||||
startAngle += TWO_PI
|
||||
}
|
||||
|
||||
if (endAngle < 0) {
|
||||
endAngle += TWO_PI
|
||||
}
|
||||
|
||||
const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle
|
||||
const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments
|
||||
|
||||
vertices.push(startVertex)
|
||||
|
||||
for (let i = 1; i < arcSegments; ++i) {
|
||||
const angle = startAngle + angleStep * i
|
||||
|
||||
const vertex = {
|
||||
x: center.x + Math.cos(angle) * radius,
|
||||
y: center.y + Math.sin(angle) * radius,
|
||||
}
|
||||
|
||||
vertices.push(vertex)
|
||||
}
|
||||
|
||||
vertices.push(endVertex)
|
||||
}
|
||||
|
||||
function createOffsetEdge(edge, dx, dy) {
|
||||
return {
|
||||
vertex1: {
|
||||
x: edge.vertex1.x + dx,
|
||||
y: edge.vertex1.y + dy,
|
||||
},
|
||||
vertex2: {
|
||||
x: edge.vertex2.x + dx,
|
||||
y: edge.vertex2.y + dy,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function createMarginPolygon(polygon, offset, arcSegments = 0) {
|
||||
const offsetEdges = []
|
||||
|
||||
for (let i = 0; i < polygon.edges.length; i++) {
|
||||
const edge = polygon.edges[i]
|
||||
const dx = edge.outwardNormal.x * offset
|
||||
const dy = edge.outwardNormal.y * offset
|
||||
offsetEdges.push(createOffsetEdge(edge, dx, dy))
|
||||
}
|
||||
|
||||
const vertices = []
|
||||
|
||||
for (let i = 0; i < offsetEdges.length; i++) {
|
||||
const thisEdge = offsetEdges[i]
|
||||
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
|
||||
const vertex = edgesIntersection(prevEdge, thisEdge)
|
||||
|
||||
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
|
||||
vertices.push({
|
||||
x: vertex.x,
|
||||
y: vertex.y,
|
||||
})
|
||||
} else {
|
||||
const arcCenter = polygon.edges[i].vertex1
|
||||
|
||||
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false)
|
||||
}
|
||||
}
|
||||
|
||||
const marginPolygon = createPolygon(vertices)
|
||||
|
||||
marginPolygon.offsetEdges = offsetEdges
|
||||
|
||||
return marginPolygon
|
||||
}
|
||||
|
||||
function createPaddingPolygon(polygon, offset, arcSegments = 0) {
|
||||
const offsetEdges = []
|
||||
|
||||
for (let i = 0; i < polygon.edges.length; i++) {
|
||||
const edge = polygon.edges[i]
|
||||
const dx = edge.inwardNormal.x * offset
|
||||
const dy = edge.inwardNormal.y * offset
|
||||
offsetEdges.push(createOffsetEdge(edge, dx, dy))
|
||||
}
|
||||
|
||||
const vertices = []
|
||||
|
||||
for (let i = 0; i < offsetEdges.length; i++) {
|
||||
const thisEdge = offsetEdges[i]
|
||||
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
|
||||
const vertex = edgesIntersection(prevEdge, thisEdge)
|
||||
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
|
||||
vertices.push({
|
||||
x: vertex.x,
|
||||
y: vertex.y,
|
||||
})
|
||||
} else {
|
||||
const arcCenter = polygon.edges[i].vertex1
|
||||
|
||||
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true)
|
||||
}
|
||||
}
|
||||
|
||||
const paddingPolygon = createPolygon(vertices)
|
||||
|
||||
paddingPolygon.offsetEdges = offsetEdges
|
||||
|
||||
return paddingPolygon
|
||||
}
|
||||
|
||||
export default function offsetPolygon(vertices, offset, arcSegments = 0) {
|
||||
const polygon = createPolygon(vertices)
|
||||
|
||||
const originPolygon = new QPolygon(vertices, { fontSize: 0 })
|
||||
|
||||
if (offset > 0) {
|
||||
let result = createMarginPolygon(polygon, offset, arcSegments).vertices
|
||||
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
|
||||
|
||||
if (allPointsOutside) {
|
||||
return createMarginPolygon(polygon, offset, arcSegments).vertices
|
||||
} else {
|
||||
return createPaddingPolygon(polygon, offset, arcSegments).vertices
|
||||
}
|
||||
} else {
|
||||
let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
|
||||
const allPointsInside = result.every((point) => originPolygon.inPolygon(point))
|
||||
|
||||
if (allPointsInside) {
|
||||
return createPaddingPolygon(polygon, offset, arcSegments).vertices
|
||||
} else {
|
||||
return createMarginPolygon(polygon, offset, arcSegments).vertices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
yarn.lock
18
yarn.lock
@ -2258,7 +2258,7 @@ canvas@^2.8.0:
|
||||
nan "^2.17.0"
|
||||
simple-get "^3.0.3"
|
||||
|
||||
chokidar@^3.5.3:
|
||||
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
|
||||
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
|
||||
@ -2714,6 +2714,11 @@ iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
immutable@^4.0.0:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
|
||||
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
@ -3402,6 +3407,15 @@ safe-buffer@~5.2.0:
|
||||
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sass@^1.77.8:
|
||||
version "1.77.8"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd"
|
||||
integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==
|
||||
dependencies:
|
||||
chokidar ">=3.0.0 <4.0.0"
|
||||
immutable "^4.0.0"
|
||||
source-map-js ">=0.6.2 <2.0.0"
|
||||
|
||||
saxes@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz"
|
||||
@ -3486,7 +3500,7 @@ simple-swizzle@^0.2.2:
|
||||
dependencies:
|
||||
is-arrayish "^0.3.1"
|
||||
|
||||
source-map-js@^1.0.2, source-map-js@^1.2.0:
|
||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
|
||||
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user