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 { 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')
|
||||
@ -455,10 +456,23 @@ export default function Roof2() {
|
||||
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') {
|
||||
roof.fillCell({ width: 50, height: 100, padding: 0 })
|
||||
newPoly.fillCell({ width: 100, height: 50, padding: 10 })
|
||||
} 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: [],
|
||||
ridges: [],
|
||||
connectRidges: [],
|
||||
cells: [],
|
||||
initialize: function (points, options, canvas) {
|
||||
// 소수점 전부 제거
|
||||
points.forEach((point) => {
|
||||
@ -251,13 +252,20 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
lockScalingX: true, // X 축 크기 조정 잠금
|
||||
lockScalingY: true, // Y 축 크기 조정 잠금
|
||||
opacity: 0.8,
|
||||
name: 'cell',
|
||||
})
|
||||
|
||||
rect.on('mousedown', () => {
|
||||
rect.set({ fill: 'red' })
|
||||
})
|
||||
|
||||
drawCellsArray.push(rect) //배열에 넣어서 반환한다
|
||||
this.canvas.add(rect)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.canvas?.renderAll()
|
||||
this.cells = drawCellsArray
|
||||
return drawCellsArray
|
||||
},
|
||||
inPolygon(point) {
|
||||
|
||||
@ -558,6 +558,7 @@ export function useCanvas(id) {
|
||||
'lockScalingX',
|
||||
'lockScalingY',
|
||||
'opacity',
|
||||
'cells',
|
||||
])
|
||||
|
||||
const str = JSON.stringify(objs)
|
||||
@ -568,16 +569,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,
|
||||
@ -593,6 +590,5 @@ export function useCanvas(id) {
|
||||
handleFlip,
|
||||
setCanvasBackgroundWithDots,
|
||||
addCanvas,
|
||||
changeCanvas,
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,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', // 기준선 긋기모드`
|
||||
@ -1113,66 +1114,13 @@ export function useMode() {
|
||||
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
|
||||
*/
|
||||
const handleOuterlinesTest2 = (polygon, offset = 71) => {
|
||||
const offsetPoints = []
|
||||
const sortedIndex = getStartIndex(polygon.lines)
|
||||
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
|
||||
const offsetPoints = offsetPolygon(polygon.points, 50)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ -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')
|
||||
@ -676,3 +678,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user