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