offset 추가
This commit is contained in:
parent
d6f732e46c
commit
0cb293e24e
@ -11,6 +11,7 @@ import { QLine } from '@/components/fabric/QLine'
|
|||||||
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
import { getCanvasState, insertCanvasState } from '@/lib/canvas'
|
||||||
import { calculateIntersection } from '@/util/canvas-util'
|
import { calculateIntersection } from '@/util/canvas-util'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
import offsetPolygon from '@/util/qpolygon-utils'
|
||||||
|
|
||||||
export default function Roof2() {
|
export default function Roof2() {
|
||||||
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas')
|
||||||
@ -455,10 +456,23 @@ export default function Roof2() {
|
|||||||
return acc.length > cur.length ? acc : cur
|
return acc.length > cur.length ? acc : cur
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const offsetPolygonPoint = offsetPolygon(roof.points, -20, 0)
|
||||||
|
|
||||||
|
const newPoly = new QPolygon(offsetPolygonPoint, {
|
||||||
|
fill: 'transparent',
|
||||||
|
stroke: 'red',
|
||||||
|
strokeWidth: 1,
|
||||||
|
selectable: true,
|
||||||
|
fontSize: fontSize,
|
||||||
|
name: 'wall',
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas?.add(newPoly)
|
||||||
|
|
||||||
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
||||||
roof.fillCell({ width: 50, height: 100, padding: 0 })
|
newPoly.fillCell({ width: 100, height: 50, padding: 10 })
|
||||||
} else {
|
} else {
|
||||||
roof.fillCell({ width: 100, height: 50, padding: 0 })
|
newPoly.fillCell({ width: 50, height: 100, padding: 10 })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
hips: [],
|
hips: [],
|
||||||
ridges: [],
|
ridges: [],
|
||||||
connectRidges: [],
|
connectRidges: [],
|
||||||
|
cells: [],
|
||||||
initialize: function (points, options, canvas) {
|
initialize: function (points, options, canvas) {
|
||||||
// 소수점 전부 제거
|
// 소수점 전부 제거
|
||||||
points.forEach((point) => {
|
points.forEach((point) => {
|
||||||
@ -251,13 +252,20 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
lockScalingX: true, // X 축 크기 조정 잠금
|
lockScalingX: true, // X 축 크기 조정 잠금
|
||||||
lockScalingY: true, // Y 축 크기 조정 잠금
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
|
name: 'cell',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
rect.on('mousedown', () => {
|
||||||
|
rect.set({ fill: 'red' })
|
||||||
|
})
|
||||||
|
|
||||||
drawCellsArray.push(rect) //배열에 넣어서 반환한다
|
drawCellsArray.push(rect) //배열에 넣어서 반환한다
|
||||||
this.canvas.add(rect)
|
this.canvas.add(rect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.canvas?.renderAll()
|
this.canvas?.renderAll()
|
||||||
|
this.cells = drawCellsArray
|
||||||
return drawCellsArray
|
return drawCellsArray
|
||||||
},
|
},
|
||||||
inPolygon(point) {
|
inPolygon(point) {
|
||||||
|
|||||||
@ -558,6 +558,7 @@ export function useCanvas(id) {
|
|||||||
'lockScalingX',
|
'lockScalingX',
|
||||||
'lockScalingY',
|
'lockScalingY',
|
||||||
'opacity',
|
'opacity',
|
||||||
|
'cells',
|
||||||
])
|
])
|
||||||
|
|
||||||
const str = JSON.stringify(objs)
|
const str = JSON.stringify(objs)
|
||||||
@ -568,16 +569,12 @@ export function useCanvas(id) {
|
|||||||
// 역직렬화하여 캔버스에 객체를 다시 추가합니다.
|
// 역직렬화하여 캔버스에 객체를 다시 추가합니다.
|
||||||
canvas?.loadFromJSON(JSON.parse(str), function () {
|
canvas?.loadFromJSON(JSON.parse(str), function () {
|
||||||
// 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
|
// 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
|
||||||
|
console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
|
||||||
canvas?.renderAll() // 캔버스를 다시 그립니다.
|
canvas?.renderAll() // 캔버스를 다시 그립니다.
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeCanvas = (idx) => {
|
|
||||||
canvas?.clear()
|
|
||||||
const canvasState = JSON.parse(canvasList[idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canvas,
|
canvas,
|
||||||
addShape,
|
addShape,
|
||||||
@ -593,6 +590,5 @@ export function useCanvas(id) {
|
|||||||
handleFlip,
|
handleFlip,
|
||||||
setCanvasBackgroundWithDots,
|
setCanvasBackgroundWithDots,
|
||||||
addCanvas,
|
addCanvas,
|
||||||
changeCanvas,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { fabric } from 'fabric'
|
import { fabric } from 'fabric'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
import offsetPolygon from '@/util/qpolygon-utils'
|
||||||
|
|
||||||
export const Mode = {
|
export const Mode = {
|
||||||
DRAW_LINE: 'drawLine', // 기준선 긋기모드`
|
DRAW_LINE: 'drawLine', // 기준선 긋기모드`
|
||||||
@ -1113,66 +1114,13 @@ export function useMode() {
|
|||||||
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
|
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
|
||||||
*/
|
*/
|
||||||
const handleOuterlinesTest2 = (polygon, offset = 71) => {
|
const handleOuterlinesTest2 = (polygon, offset = 71) => {
|
||||||
const offsetPoints = []
|
const offsetPoints = offsetPolygon(polygon.points, 50)
|
||||||
const sortedIndex = getStartIndex(polygon.lines)
|
|
||||||
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
|
|
||||||
|
|
||||||
if (tmpArraySorted[0].direction === 'right') {
|
const roof = makePolygon(
|
||||||
//시계방향
|
offsetPoints.map((point) => {
|
||||||
tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정
|
return { x1: point.x, y1: point.y }
|
||||||
}
|
}),
|
||||||
|
)
|
||||||
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)
|
|
||||||
roof.setWall(polygon)
|
roof.setWall(polygon)
|
||||||
setRoof(roof)
|
setRoof(roof)
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { QLine } from '@/components/fabric/QLine'
|
|||||||
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util'
|
import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
|
|
||||||
|
const TWO_PI = Math.PI * 2
|
||||||
|
|
||||||
export const defineQPloygon = () => {
|
export const defineQPloygon = () => {
|
||||||
fabric.QPolygon.fromObject = function (object, callback) {
|
fabric.QPolygon.fromObject = function (object, callback) {
|
||||||
fabric.Object._fromObject('QPolygon', object, callback, 'points')
|
fabric.Object._fromObject('QPolygon', object, callback, 'points')
|
||||||
@ -676,3 +678,239 @@ const getOneSideLine = (line) => {
|
|||||||
|
|
||||||
return newLine
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user