ERP-node/frontend/components/admin/dashboard/widgets/yard-3d/spatialContainment.ts

167 lines
4.5 KiB
TypeScript

/**
* 공간적 종속성 검증 유틸리티
*
* 하위 영역이 상위 영역 내부에 배치되는지 검증
*/
export interface SpatialObject {
id: number;
position: { x: number; y: number; z: number };
size: { x: number; y: number; z: number };
hierarchyLevel: number;
parentId?: number;
parentKey?: string; // 외부 DB 키 (데이터 바인딩용)
}
/**
* 객체 A가 객체 B 안에 포함되는지 확인 (AABB)
*/
export function isContainedIn(child: SpatialObject, parent: SpatialObject): boolean {
// AABB (Axis-Aligned Bounding Box) 계산
const childMin = {
x: child.position.x - child.size.x / 2,
z: child.position.z - child.size.z / 2,
};
const childMax = {
x: child.position.x + child.size.x / 2,
z: child.position.z + child.size.z / 2,
};
const parentMin = {
x: parent.position.x - parent.size.x / 2,
z: parent.position.z - parent.size.z / 2,
};
const parentMax = {
x: parent.position.x + parent.size.x / 2,
z: parent.position.z + parent.size.z / 2,
};
// 자식 객체의 모든 모서리가 부모 객체 내부에 있어야 함 (XZ 평면에서)
return (
childMin.x >= parentMin.x &&
childMax.x <= parentMax.x &&
childMin.z >= parentMin.z &&
childMax.z <= parentMax.z
);
}
/**
* 드래그 시 부모 영역을 찾아서 검증
* @param child 드래그 중인 자식 객체
* @param allObjects 모든 배치된 객체들
* @param hierarchyLevels 계층 레벨 설정 (1, 2, 3, ...)
* @returns 유효한 부모 객체 또는 null
*/
export function findValidParent(
child: SpatialObject,
allObjects: SpatialObject[],
hierarchyLevels: number
): SpatialObject | null {
// 최상위 레벨(레벨 1)은 부모가 없음
if (child.hierarchyLevel === 1) {
return null;
}
// 부모 레벨 (자신보다 1단계 위)
const parentLevel = child.hierarchyLevel - 1;
// 부모 레벨의 모든 객체 중에서 포함하는 객체 찾기
const possibleParents = allObjects.filter(
(obj) => obj.hierarchyLevel === parentLevel
);
for (const parent of possibleParents) {
if (isContainedIn(child, parent)) {
return parent;
}
}
// 포함하는 부모가 없으면 null
return null;
}
/**
* 드래그 종료 시 공간적 종속성 검증
* @param child 드래그 종료된 자식 객체
* @param allObjects 모든 배치된 객체들
* @returns { valid: boolean, parent: SpatialObject | null }
*/
export function validateSpatialContainment(
child: SpatialObject,
allObjects: SpatialObject[]
): { valid: boolean; parent: SpatialObject | null } {
// 최상위 레벨은 항상 유효
if (child.hierarchyLevel === 1) {
return { valid: true, parent: null };
}
const parent = findValidParent(child, allObjects, child.hierarchyLevel);
return {
valid: parent !== null,
parent: parent,
};
}
/**
* 부모 객체 이동 시 모든 자식 객체의 위치 재계산
* @param parent 이동한 부모 객체
* @param oldPosition 이전 위치
* @param newPosition 새 위치
* @param allObjects 모든 배치된 객체들
* @returns 업데이트된 자식 객체 배열
*/
export function updateChildrenPositions(
parent: SpatialObject,
oldPosition: { x: number; y: number; z: number },
newPosition: { x: number; y: number; z: number },
allObjects: SpatialObject[]
): SpatialObject[] {
const delta = {
x: newPosition.x - oldPosition.x,
y: newPosition.y - oldPosition.y,
z: newPosition.z - oldPosition.z,
};
// 직계 자식 (부모 ID가 일치하는 객체)
const directChildren = allObjects.filter(
(obj) => obj.parentId === parent.id
);
// 자식들의 위치 업데이트
return directChildren.map((child) => ({
...child,
position: {
x: child.position.x + delta.x,
y: child.position.y + delta.y,
z: child.position.z + delta.z,
},
}));
}
/**
* 특정 객체의 모든 하위 자손 찾기 (재귀)
* @param parentId 부모 객체 ID
* @param allObjects 모든 배치된 객체들
* @returns 모든 하위 자손 객체 배열
*/
export function getAllDescendants(
parentId: number,
allObjects: SpatialObject[]
): SpatialObject[] {
const directChildren = allObjects.filter((obj) => obj.parentId === parentId);
let descendants = [...directChildren];
// 재귀적으로 손자, 증손자... 찾기
for (const child of directChildren) {
const grandChildren = getAllDescendants(child.id, allObjects);
descendants = [...descendants, ...grandChildren];
}
return descendants;
}