Merge pull request '지붕 회전 시 내부 오브젝트(개구, 도머, 그림자) 회전 반영' (#347) from dev into dev-deploy
Reviewed-on: #347
This commit is contained in:
commit
f04436c420
@ -714,7 +714,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
},
|
||||
|
||||
inPolygonImproved(point) {
|
||||
const vertices = this.points
|
||||
const vertices = this.getCurrentPoints()
|
||||
let inside = false
|
||||
const testX = Number(point.x.toFixed(this.toFixed))
|
||||
const testY = Number(point.y.toFixed(this.toFixed))
|
||||
@ -726,7 +726,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
||||
const yj = Number(vertices[j].y.toFixed(this.toFixed))
|
||||
|
||||
// 점이 정점 위에 있는지 확인
|
||||
if (Math.abs(xi - testX) < 0.01 && Math.abs(yi - testY) < 0.01) {
|
||||
if (Math.abs(xi - testX) <= 0.01 && Math.abs(yi - testY) <= 0.01) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
} from '@/store/canvasAtom'
|
||||
|
||||
import { calculateVisibleModuleHeight, getDegreeByChon, polygonToTurfPolygon, rectToPolygon, toFixedWithoutRounding } from '@/util/canvas-util'
|
||||
import '@/util/fabric-extensions' // fabric 객체들에 getCurrentPoints 메서드 추가
|
||||
import { basicSettingState, roofDisplaySelector } from '@/store/settingAtom'
|
||||
import offsetPolygon, { calculateAngle, createLinesFromPolygon } from '@/util/qpolygon-utils'
|
||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||
@ -265,14 +266,14 @@ export function useModuleBasicSetting(tabNum) {
|
||||
batchObjects.forEach((obj) => {
|
||||
//도머일때
|
||||
if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) {
|
||||
const groupPoints = obj.groupPoints
|
||||
const groupPoints = obj.getCurrentPoints()
|
||||
const offsetObjects = offsetPolygon(groupPoints, 10)
|
||||
const dormerOffset = new QPolygon(offsetObjects, batchObjectOptions)
|
||||
dormerOffset.setViewLengthText(false)
|
||||
canvas.add(dormerOffset) //모듈설치면 만들기
|
||||
} else {
|
||||
//개구, 그림자일때
|
||||
const points = obj.points
|
||||
const points = obj.getCurrentPoints()
|
||||
const offsetObjects = offsetPolygon(points, 10)
|
||||
const offset = new QPolygon(offsetObjects, batchObjectOptions)
|
||||
offset.setViewLengthText(false)
|
||||
@ -319,7 +320,7 @@ export function useModuleBasicSetting(tabNum) {
|
||||
const margin = moduleSelectionData.common.margin ? moduleSelectionData.common.margin : 200
|
||||
|
||||
//육지붕일때는 그냥 하드코딩
|
||||
offsetPoints = offsetPolygon(roof.points, -Number(margin) / 10) //육지붕일때
|
||||
offsetPoints = offsetPolygon(roof.getCurrentPoints(), -Number(margin) / 10) //육지붕일때
|
||||
} else {
|
||||
//육지붕이 아닐때
|
||||
if (allPointsOutside) {
|
||||
|
||||
@ -675,6 +675,8 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
}
|
||||
})
|
||||
|
||||
objectGroup.recalculateGroupPoints()
|
||||
|
||||
isDown = false
|
||||
initEvent()
|
||||
// dbClickEvent()
|
||||
@ -1426,7 +1428,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
|
||||
//그림자는 아무데나 설치 할 수 있게 해달라고 함
|
||||
if (obj.name === BATCH_TYPE.OPENING) {
|
||||
const turfObject = pointsToTurfPolygon(obj.points)
|
||||
const turfObject = pointsToTurfPolygon(obj.getCurrentPoints())
|
||||
|
||||
if (turf.booleanWithin(turfObject, turfSurface)) {
|
||||
obj.set({
|
||||
@ -1459,7 +1461,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
||||
const calcLeft = obj.left - originLeft
|
||||
const calcTop = obj.top - originTop
|
||||
|
||||
const currentDormerPoints = obj.groupPoints.map((item) => {
|
||||
const currentDormerPoints = obj.getCurrentPoints().map((item) => {
|
||||
return {
|
||||
x: item.x + calcLeft,
|
||||
y: item.y + calcTop,
|
||||
|
||||
@ -1439,44 +1439,89 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
||||
|
||||
const rotateSurfaceShapeBatch = () => {
|
||||
if (currentObject) {
|
||||
// 기존 관련 객체들 제거
|
||||
const relatedObjects = canvas
|
||||
.getObjects()
|
||||
.filter(
|
||||
(obj) =>
|
||||
obj.parentId === currentObject.id ||
|
||||
(obj.name === 'lengthText' && obj.parentId === currentObject.id) ||
|
||||
(obj.name === 'arrow' && obj.parentId === currentObject.id),
|
||||
)
|
||||
relatedObjects.forEach((obj) => canvas.remove(obj))
|
||||
|
||||
// 현재 회전값에 90도 추가
|
||||
const currentAngle = currentObject.angle || 0
|
||||
const newAngle = (currentAngle + 90) % 360
|
||||
const originWidth = currentObject.originWidth
|
||||
const originHeight = currentObject.originHeight
|
||||
// 회전 적용 (width/height 교체 제거로 도형 깨짐 방지)
|
||||
currentObject.rotate(newAngle)
|
||||
|
||||
// QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결)
|
||||
if (currentObject.type === 'QPolygon' && currentObject.lines) {
|
||||
currentObject.initLines()
|
||||
// 관련 객체들 찾기
|
||||
// arrow는 제거
|
||||
const arrow = canvas.getObjects().find((obj) => obj.parentId === currentObject.id && obj.name === 'arrow')
|
||||
if (arrow) {
|
||||
canvas.remove(arrow)
|
||||
}
|
||||
|
||||
currentObject.set({
|
||||
originWidth: originHeight,
|
||||
originHeight: originWidth,
|
||||
const relatedObjects = canvas.getObjects().filter((obj) => obj.parentId === currentObject.id)
|
||||
|
||||
// 그룹화할 객체들 배열 (currentObject + relatedObjects)
|
||||
const objectsToGroup = [currentObject, ...relatedObjects]
|
||||
|
||||
// 기존 객체들을 캔버스에서 제거
|
||||
objectsToGroup.forEach((obj) => canvas.remove(obj))
|
||||
|
||||
// fabric.Group 생성
|
||||
const group = new fabric.Group(objectsToGroup, {
|
||||
originX: 'center',
|
||||
originY: 'center',
|
||||
})
|
||||
|
||||
currentObject.setCoords()
|
||||
currentObject.fire('modified')
|
||||
// 그룹을 캔버스에 추가
|
||||
canvas.add(group)
|
||||
|
||||
// 현재 회전값에 90도 추가
|
||||
const currentAngle = group.angle || 0
|
||||
const newAngle = (currentAngle + 90) % 360
|
||||
|
||||
// 그룹 전체를 회전
|
||||
group.rotate(newAngle)
|
||||
group.setCoords()
|
||||
|
||||
// 그룹을 해제하고 개별 객체로 복원
|
||||
group._restoreObjectsState()
|
||||
canvas.remove(group)
|
||||
|
||||
// 개별 객체들을 다시 캔버스에 추가하고 처리
|
||||
group.getObjects().forEach((obj) => {
|
||||
canvas.add(obj)
|
||||
obj.setCoords()
|
||||
|
||||
// currentObject인 경우 추가 처리
|
||||
if (obj.id === currentObject.id) {
|
||||
const originWidth = obj.originWidth
|
||||
const originHeight = obj.originHeight
|
||||
|
||||
// QPolygon 내부 구조 재구성 (선이 깨지는 문제 해결)
|
||||
if (obj.type === 'QPolygon' && obj.lines) {
|
||||
obj.initLines()
|
||||
}
|
||||
|
||||
obj.set({
|
||||
originWidth: originHeight,
|
||||
originHeight: originWidth,
|
||||
})
|
||||
} else {
|
||||
// relatedObject인 경우에도 필요한 처리
|
||||
if (obj.type === 'QPolygon' && obj.lines) {
|
||||
obj.initLines()
|
||||
}
|
||||
if (obj.type === 'group') {
|
||||
// 회전 후의 points를 groupPoints로 업데이트
|
||||
// getCurrentPoints를 직접 호출하지 말고 recalculateGroupPoints만 실행
|
||||
|
||||
obj.recalculateGroupPoints()
|
||||
|
||||
obj._objects?.forEach((obj) => {
|
||||
obj.initLines()
|
||||
obj.fire('modified')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
currentObject.fire('modified')
|
||||
// 화살표와 선 다시 그리기
|
||||
drawDirectionArrow(currentObject)
|
||||
setTimeout(() => {
|
||||
setPolygonLinesActualSize(currentObject)
|
||||
changeSurfaceLineType(currentObject)
|
||||
}, 200)
|
||||
}, 500)
|
||||
|
||||
// currentObject를 다시 선택 상태로 설정
|
||||
canvas.setActiveObject(currentObject)
|
||||
canvas.renderAll()
|
||||
}
|
||||
}
|
||||
|
||||
232
src/util/fabric-extensions.js
Normal file
232
src/util/fabric-extensions.js
Normal file
@ -0,0 +1,232 @@
|
||||
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 메서드를 추가 (도머 그룹용)
|
||||
* 그룹의 groupPoints를 다시 계산하여 반환
|
||||
*/
|
||||
fabric.Group.prototype.getCurrentPoints = function () {
|
||||
// groupPoints를 다시 계산
|
||||
|
||||
// 그룹에 groupPoints가 있으면 해당 점들을 사용 (도머의 경우)
|
||||
if (this.groupPoints && Array.isArray(this.groupPoints)) {
|
||||
const matrix = this.calcTransformMatrix()
|
||||
console.log('this.groupPoints', this.groupPoints)
|
||||
return this.groupPoints.map(function (p) {
|
||||
const point = new fabric.Point(p.x, p.y)
|
||||
return fabric.util.transformPoint(point, matrix)
|
||||
})
|
||||
}
|
||||
|
||||
// groupPoints가 없으면 바운딩 박스를 사용
|
||||
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 {}
|
||||
Loading…
x
Reference in New Issue
Block a user