250 lines
8.1 KiB
JavaScript
250 lines
8.1 KiB
JavaScript
import { fabric } from 'fabric'
|
|
|
|
/**
|
|
* fabric.Rect에 getCurrentPoints 메서드를 추가
|
|
* QPolygon의 getCurrentPoints와 동일한 방식으로 변형된 현재 점들을 반환
|
|
*/
|
|
fabric.Rect.prototype.getCurrentPoints = function () {
|
|
// 사각형의 네 모서리 점들을 계산
|
|
const width = this.width
|
|
const height = this.height
|
|
|
|
// 사각형의 로컬 좌표계에서의 네 모서리 점
|
|
const points = [
|
|
{ x: -width / 2, y: -height / 2 }, // 좌상단
|
|
{ x: width / 2, y: -height / 2 }, // 우상단
|
|
{ x: width / 2, y: height / 2 }, // 우하단
|
|
{ x: -width / 2, y: height / 2 }, // 좌하단
|
|
]
|
|
|
|
// 변형 매트릭스 계산
|
|
const matrix = this.calcTransformMatrix()
|
|
|
|
// 각 점을 변형 매트릭스로 변환
|
|
return points.map(function (p) {
|
|
const point = new fabric.Point(p.x, p.y)
|
|
return fabric.util.transformPoint(point, matrix)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* fabric.Group에 getCurrentPoints 메서드를 추가 (도머 그룹용)
|
|
* 그룹 내 객체들의 점들을 수집하여 현재 월드 좌표를 반환
|
|
*/
|
|
fabric.Group.prototype.getCurrentPoints = function () {
|
|
// 그룹 내 객체들로부터 실시간으로 점들을 계산
|
|
if (this._objects && this._objects.length > 0) {
|
|
let allPoints = []
|
|
|
|
// 그룹 내 모든 객체의 점들을 수집
|
|
this._objects.forEach(function (obj) {
|
|
if (obj.getCurrentPoints && typeof obj.getCurrentPoints === 'function') {
|
|
const objPoints = obj.getCurrentPoints()
|
|
allPoints = allPoints.concat(objPoints)
|
|
} else if (obj.points && Array.isArray(obj.points)) {
|
|
const pathOffset = obj.pathOffset || { x: 0, y: 0 }
|
|
const matrix = obj.calcTransformMatrix()
|
|
const transformedPoints = obj.points
|
|
.map(function (p) {
|
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
|
})
|
|
.map(function (p) {
|
|
return fabric.util.transformPoint(p, matrix)
|
|
})
|
|
allPoints = allPoints.concat(transformedPoints)
|
|
}
|
|
})
|
|
|
|
if (allPoints.length > 0) {
|
|
// Convex Hull 알고리즘을 사용하여 외곽 점들만 반환
|
|
return this.getConvexHull(allPoints)
|
|
}
|
|
}
|
|
|
|
// 객체가 없으면 바운딩 박스를 사용
|
|
const bounds = this.getBoundingRect()
|
|
const points = [
|
|
{ x: bounds.left, y: bounds.top },
|
|
{ x: bounds.left + bounds.width, y: bounds.top },
|
|
{ x: bounds.left + bounds.width, y: bounds.top + bounds.height },
|
|
{ x: bounds.left, y: bounds.top + bounds.height },
|
|
]
|
|
|
|
return points.map(function (p) {
|
|
return new fabric.Point(p.x, p.y)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* fabric.Group에 groupPoints 재계산 메서드 추가
|
|
* 그룹 내 모든 객체의 점들을 기반으로 groupPoints를 새로 계산
|
|
* Convex Hull 알고리즘을 사용하여 가장 외곽의 점들만 반환
|
|
*/
|
|
fabric.Group.prototype.recalculateGroupPoints = function () {
|
|
if (!this._objects || this._objects.length === 0) {
|
|
return
|
|
}
|
|
|
|
let allPoints = []
|
|
|
|
// 그룹 내 모든 객체의 점들을 수집
|
|
this._objects.forEach(function (obj) {
|
|
if (obj.getCurrentPoints && typeof obj.getCurrentPoints === 'function') {
|
|
// getCurrentPoints가 있는 객체는 해당 메서드 사용
|
|
const objPoints = obj.getCurrentPoints()
|
|
allPoints = allPoints.concat(objPoints)
|
|
} else if (obj.points && Array.isArray(obj.points)) {
|
|
// QPolygon과 같이 points 배열이 있는 경우
|
|
const pathOffset = obj.pathOffset || { x: 0, y: 0 }
|
|
const matrix = obj.calcTransformMatrix()
|
|
const transformedPoints = obj.points
|
|
.map(function (p) {
|
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
|
})
|
|
.map(function (p) {
|
|
return fabric.util.transformPoint(p, matrix)
|
|
})
|
|
allPoints = allPoints.concat(transformedPoints)
|
|
} else {
|
|
// 일반 객체는 바운딩 박스의 네 모서리 점 사용
|
|
const bounds = obj.getBoundingRect()
|
|
const cornerPoints = [
|
|
{ x: bounds.left, y: bounds.top },
|
|
{ x: bounds.left + bounds.width, y: bounds.top },
|
|
{ x: bounds.left + bounds.width, y: bounds.top + bounds.height },
|
|
{ x: bounds.left, y: bounds.top + bounds.height },
|
|
]
|
|
allPoints = allPoints.concat(
|
|
cornerPoints.map(function (p) {
|
|
return new fabric.Point(p.x, p.y)
|
|
}),
|
|
)
|
|
}
|
|
})
|
|
|
|
if (allPoints.length > 0) {
|
|
// Convex Hull 알고리즘을 사용하여 외곽 점들만 추출
|
|
const convexHullPoints = this.getConvexHull(allPoints)
|
|
|
|
// 그룹의 로컬 좌표계로 변환하기 위해 그룹의 역변환 적용
|
|
const groupMatrix = this.calcTransformMatrix()
|
|
const invertedMatrix = fabric.util.invertTransform(groupMatrix)
|
|
|
|
this.groupPoints = convexHullPoints.map(function (p) {
|
|
const localPoint = fabric.util.transformPoint(p, invertedMatrix)
|
|
return { x: localPoint.x, y: localPoint.y }
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Graham Scan 알고리즘을 사용한 Convex Hull 계산
|
|
* 점들의 집합에서 가장 외곽의 점들만 반환
|
|
*/
|
|
fabric.Group.prototype.getConvexHull = function (points) {
|
|
if (points.length < 3) return points
|
|
|
|
// 중복 점 제거
|
|
const uniquePoints = []
|
|
const seen = new Set()
|
|
|
|
points.forEach(function (p) {
|
|
const key = `${Math.round(p.x * 10) / 10},${Math.round(p.y * 10) / 10}`
|
|
if (!seen.has(key)) {
|
|
seen.add(key)
|
|
uniquePoints.push({ x: p.x, y: p.y })
|
|
}
|
|
})
|
|
|
|
if (uniquePoints.length < 3) return uniquePoints
|
|
|
|
// 가장 아래쪽 점을 찾기 (y가 가장 작고, 같으면 x가 가장 작은 점)
|
|
let pivot = uniquePoints[0]
|
|
for (let i = 1; i < uniquePoints.length; i++) {
|
|
if (uniquePoints[i].y < pivot.y || (uniquePoints[i].y === pivot.y && uniquePoints[i].x < pivot.x)) {
|
|
pivot = uniquePoints[i]
|
|
}
|
|
}
|
|
|
|
// 극각에 따라 정렬
|
|
const sortedPoints = uniquePoints
|
|
.filter(function (p) { return p !== pivot })
|
|
.sort(function (a, b) {
|
|
const angleA = Math.atan2(a.y - pivot.y, a.x - pivot.x)
|
|
const angleB = Math.atan2(b.y - pivot.y, b.x - pivot.x)
|
|
if (angleA !== angleB) return angleA - angleB
|
|
|
|
// 각도가 같으면 거리로 정렬
|
|
const distA = Math.pow(a.x - pivot.x, 2) + Math.pow(a.y - pivot.y, 2)
|
|
const distB = Math.pow(b.x - pivot.x, 2) + Math.pow(b.y - pivot.y, 2)
|
|
return distA - distB
|
|
})
|
|
|
|
// Graham Scan 실행
|
|
const hull = [pivot]
|
|
|
|
for (let i = 0; i < sortedPoints.length; i++) {
|
|
const current = sortedPoints[i]
|
|
|
|
// 반시계방향이 아닌 점들 제거
|
|
while (hull.length > 1) {
|
|
const p1 = hull[hull.length - 2]
|
|
const p2 = hull[hull.length - 1]
|
|
const cross = (p2.x - p1.x) * (current.y - p1.y) - (p2.y - p1.y) * (current.x - p1.x)
|
|
|
|
if (cross > 0) break // 반시계방향이면 유지
|
|
hull.pop() // 시계방향이면 제거
|
|
}
|
|
|
|
hull.push(current)
|
|
}
|
|
|
|
return hull
|
|
}
|
|
|
|
/**
|
|
* fabric.Triangle에 getCurrentPoints 메서드를 추가
|
|
* 삼각형의 세 꼭짓점을 반환
|
|
*/
|
|
fabric.Triangle.prototype.getCurrentPoints = function () {
|
|
const width = this.width
|
|
const height = this.height
|
|
|
|
// 삼각형의 로컬 좌표계에서의 세 꼭짓점
|
|
const points = [
|
|
{ x: 0, y: -height / 2 }, // 상단 중앙
|
|
{ x: -width / 2, y: height / 2 }, // 좌하단
|
|
{ x: width / 2, y: height / 2 }, // 우하단
|
|
]
|
|
|
|
// 변형 매트릭스 계산
|
|
const matrix = this.calcTransformMatrix()
|
|
|
|
// 각 점을 변형 매트릭스로 변환
|
|
return points.map(function (p) {
|
|
const point = new fabric.Point(p.x, p.y)
|
|
return fabric.util.transformPoint(point, matrix)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* fabric.Polygon에 getCurrentPoints 메서드를 추가 (QPolygon이 아닌 일반 Polygon용)
|
|
* QPolygon과 동일한 방식으로 구현
|
|
*/
|
|
if (!fabric.Polygon.prototype.getCurrentPoints) {
|
|
fabric.Polygon.prototype.getCurrentPoints = function () {
|
|
const pathOffset = this.get('pathOffset') || { x: 0, y: 0 }
|
|
const matrix = this.calcTransformMatrix()
|
|
|
|
return this.get('points')
|
|
.map(function (p) {
|
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
|
})
|
|
.map(function (p) {
|
|
return fabric.util.transformPoint(p, matrix)
|
|
})
|
|
}
|
|
}
|
|
|
|
export default {}
|