offset 추가

This commit is contained in:
hyojun.choi 2024-08-02 15:13:13 +09:00
parent d6f732e46c
commit 0cb293e24e
5 changed files with 271 additions and 67 deletions

View File

@ -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 })
} }
}) })
} }

View File

@ -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) {

View File

@ -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,
} }
} }

View File

@ -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)

View File

@ -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
}
}
}