qcast-front/src/util/fabric-extensions.js

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