import * as THREE from 'three' import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' import { getAllItemTypes, getItemTypeByName } from '@/model/itemType/ItemTypeDefine.ts' import type Viewport from '@/core/engine/Viewport' import { Vector2 } from 'three/src/math/Vector2' import EventBus from '@/runtime/EventBus.ts' import Decimal from 'decimal.js' import type { Object3DLike } from '@/types/ModelTypes.ts' export function setUserDataForItem(item: ItemJson, object: Object3DLike) { if (!object.name && item.name) { object.name = item.name } object.userData = { ...object.userData, t: item.t, createType: 'point', entityId: item.id, draggable: item.dt.protected !== true, selectable: item.dt.selectable !== false } } export function setUserDataForLine(start: ItemJson, end: ItemJson, type: LinkType, object: Object3DLike) { const id = getLineId(start.id, end.id, type) if (!object.name) { object.name = id } object.userData = { ...object.userData, createType: 'line', entityId: id, startId: start.id, endId: end.id, draggable: false, selectable: false, t: start.t } } /** * 确保所有实体之间的关系满足一致性: * - center 是双向的 * - in/out 是相互对应的 * - 不允许自己指向自己 */ export function ensureEntityRelationsConsistency(items: ItemJson[]) { const itemMap = new Map() // 构建 ID -> Item 映射,便于快速查找 for (const item of items) { if (item.id) { itemMap.set(item.id, item) } } // 初始化关系集合 const centerMap = new Map>() // A <-> B const inMap = new Map>() // A <- B (B.in.push(A)) const outMap = new Map>() // A -> B (B.out.push(A)) // 初始化所有节点的关系集 for (const item of items) { const id = item.id if (!id) continue centerMap.set(id, new Set(item.dt?.center || [])) inMap.set(id, new Set(item.dt?.in || [])) outMap.set(id, new Set(item.dt?.out || [])) } // Step 1: 补全 center 双向关系 for (const [source, targets] of centerMap.entries()) { for (const target of targets) { if (!centerMap.get(target)?.has(source)) { centerMap.get(target)?.add(source) } } } // Step 2: 补全 in/out 对应关系 for (const [source, targets] of outMap.entries()) { for (const target of targets) { if (!inMap.get(target)?.has(source)) { inMap.get(target)?.add(source) } } } for (const [source, targets] of inMap.entries()) { for (const target of targets) { if (!outMap.get(target)?.has(source)) { outMap.get(target)?.add(source) } } } // Step 3: 清理自环引用(center / in / out 都不能包含自己) for (const id of itemMap.keys()) { centerMap.get(id)?.delete(id) inMap.get(id)?.delete(id) outMap.get(id)?.delete(id) } // Step 4: 将补全后的关系写回原数据 for (const item of items) { const id = item.id if (!id) continue item.dt = item.dt || {} item.dt.center = Array.from(centerMap.get(id) || []) item.dt.in = Array.from(inMap.get(id) || []) item.dt.out = Array.from(outMap.get(id) || []) } return items } /** * 获取线条的唯一 ID */ export function getLineId(startId: string, endId: string, type: LinkType): string { if (type === 'center' && startId > endId) { // 无序线, start / end 大的在前 return `${type}$${endId}$${startId}` } // 其他的线是有序线 // 线条必须加上 type, 因为 center 与 in/out 是可以并存的 return `${type}$${startId}$${endId}` } /** * 获取某个 Object3D 先上查找最近的有效业务 Object3D * @param object */ export function getClosestObject(object: THREE.Object3D) { if (!object) { return undefined } while (object) { if (object.userData && object.userData.t && object.userData.entityId) { // 找到第一个有效的业务 Object3D return object } // 向上查找父级 object = object.parent } } /** * 解析线条 ID, 返回 线条类型, 起点 ID 和终点 ID */ export function parseLineId(lineId): [LinkType, string, string] { const parts = lineId.split('$') if (parts.length !== 3) { throw new Error(`Invalid lineId format: ${lineId}`) } const type = parts[0] as LinkType const startId = parts[1] const endId = parts[2] return [type, startId, endId] } export function deletePointByKeyboard() { const viewport: Viewport = window['viewport'] if (!viewport) { system.msg('没有找到当前视图') return } // 删除当前选中的实体 const entityId = viewport.state.selectedEntityId if (!entityId) { // 删除多选实体 const multiSelectedEntityIds = viewport.state.multiSelectedEntityIds if (!multiSelectedEntityIds && multiSelectedEntityIds.length === 0) { system.msg('请选中要删除的实体', 'error') return } let deleteCount = 0 viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => { for (const entityId of multiSelectedEntityIds) { deleteEntity(entityId) && deleteCount++ } }) if (deleteCount === 0) { system.msg('没有找到要删除的实体', 'error') } else { system.msg('删除了 ' + deleteCount + ' 个实体') } viewport.selectInspect.cancelMultiSelect() return } viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => { // 删除实体 if (deleteEntity(entityId)) { system.msg('删除实体 [' + entityId + '] 成功') viewport.selectInspect.cancelSelect() } }) } export function escByKeyboard(e: Event) { // 按下 ESC 键,取消当前操作 const viewport: Viewport = window['viewport'] if (!viewport) { system.msg('没有找到当前视图') return } if (viewport.interactionManager.currentTool) { // 1.退出当前交互 viewport.interactionManager.exitInteraction() system.msg('退出新建模式') } else if (viewport.dragControl.isDragging) { // 2.取消拖拽 viewport.dragControl.cancelDrag() system.msg('取消拖拽') } else if (viewport.state.multiSelectedEntityIds?.length > 0) { // 3.取消多选 viewport.selectInspect.cancelMultiSelect() system.msg('取消多选(红选)') } else if (viewport.state.selectedEntityId) { // 4.取消单选 viewport.selectInspect.cancelSelect() system.msg('取消单选(黄选)') } } export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') { // 获取当前是否按住了 Shift const viewport: Viewport = window['viewport'] if (!viewport) { system.msg('没有找到当前视图') return } let delta = 0.25 if (CurrentMouseInfo.isShiftKey || CurrentMouseInfo.isMetaKey) { // 按住 Shift 键时,移动距离只有0.1 delta = 0.1 } const entityId = viewport.state.selectedEntityId if (!entityId) { const multiSelectedEntityIds = viewport.state.multiSelectedEntityIds if (!multiSelectedEntityIds && multiSelectedEntityIds.length === 0) { system.msg('请选中要调整坐标的实体', 'error') return } // 群体移动 viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => { for (const id of multiSelectedEntityIds) { const item = getEntity(id) switch (direct) { case '↑': item.tf[0][2] -= delta // 向上移动 break case '↓': item.tf[0][2] += delta // 向下移动 break case '←': item.tf[0][0] -= delta // 向左移动 break case '→': item.tf[0][0] += delta // 向右移动 break } putEntity(item) } }) // EventBus.dispatch('multiSelectedObjectsChanged', { // multiSelectedObjects: viewport.state.multiSelectedObjects // }) // EventBus.dispatch('selectedObjectPropertyChanged', {}) return } viewport.stateManager.update(({ getEntity, putEntity, deleteEntity }) => { const item = getEntity(entityId) if (!item) { system.msg('没有找到选中的实体', 'error') return } // 根据方向移动 switch (direct) { case '↑': item.tf[0][2] -= delta // 向上移动 break case '↓': item.tf[0][2] += delta // 向下移动 break case '←': item.tf[0][0] -= delta // 向左移动 break case '→': item.tf[0][0] += delta // 向右移动 break } putEntity(item) }) // EventBus.dispatch('multiSelectedObjectsChanged', { // multiSelectedObjects: viewport.state.multiSelectedObjects // }) // EventBus.dispatch('selectedObjectPropertyChanged', {}) } export function quickCopyByMouse() { // 获取鼠标位置,查看鼠标是否在某个 viewport 的画布上,并取得该 viewport if (!CurrentMouseInfo?.viewport || isNaN(CurrentMouseInfo.x) || isNaN(CurrentMouseInfo.z) || isNaN(CurrentMouseInfo.x) || isNaN(CurrentMouseInfo.z)) { system.msg('无法获取鼠标位置') return } const x = CurrentMouseInfo.x const z = CurrentMouseInfo.z const viewport: Viewport = CurrentMouseInfo.viewport // 如果不在线上,查找1米内的有效点 Object3D, 如果有,则以这个点为起点, 延伸同类型的点,并让他们相连 const items = viewport.stateManager.findStateItemsByDistance(new Vector2(x, z), 1) if (items[0]) { // 找到一个有效点,执行复制操作 viewport.interactionManager.startInteraction(items[0].t, { startPoint: items[0].id }) } else { system.msg('鼠标所在位置,没有可复制的对象', 'error') } } /** * 考虑吸附的情况下计算鼠标事件位置 */ export function calcPositionUseSnap(e: MouseEvent, point: THREE.Vector3) { // 按下 ctrl 键,不启用吸附,其他情况启用吸附 const gridOption = worldModel.gridOption if (!e.ctrlKey && !e.metaKey) { if (gridOption.snapEnabled && gridOption.snapDistance > 0) { // 启用吸附, 针对 point 的 x 和 z 坐标进行吸附, 吸附距离为 gridOption.snapDistance const snapDistance = gridOption.snapDistance const newPoint = new THREE.Vector3(point.x, point.y, point.z) newPoint.x = Math.round(newPoint.x / snapDistance) * snapDistance newPoint.z = Math.round(newPoint.z / snapDistance) * snapDistance return newPoint } } return point } export function getAllControlPoints(): THREE.Object3D[] { const allPoints: THREE.Object3D[] = [] getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { if (itemType.clazz && itemType.clazz.pointArray) { // 将每个 ItemType 的点添加到结果数组中 allPoints.push(...itemType.clazz.pointArray) } }) return allPoints } /** * 在给定的场景中查找具有指定 uuid 的 Object3D 对象 */ export function findObject3DById(scene: THREE.Object3D, uuid: string): THREE.Object3D | undefined { const rets = findObject3DByCondition(scene, object => object.uuid === uuid) if (rets.length > 0) { return rets[0] } return undefined } /** * 在给定场景中查找满足特定条件的 Object3D 对象集合 */ export function findObject3DByCondition(scene: THREE.Object3D, condition: (object: THREE.Object3D) => boolean): THREE.Object3D[] { const foundObjects: THREE.Object3D[] = [] // 定义一个内部递归函数来遍历每个节点及其子节点 function traverse(obj: THREE.Object3D) { if (condition(obj)) { foundObjects.push(obj) } // 遍历当前对象的所有子对象 for (let i = 0; i < obj.children.length; i++) { traverse(obj.children[i]) } } // 开始从场景根节点进行遍历 traverse(scene) return foundObjects } // export function loadSceneFromJson(viewport: Viewport, scene: THREE.Scene, items: ItemJson[]) { // console.time('loadSceneFromJson') // // const object3ds: THREE.Object3D[] = [] // // // beforeLoad 通知所有加载的对象, 模型加载开始 // getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { // const ret = itemType.clazz.beforeLoad() // Array.isArray(ret) && object3ds.push(...ret) // }) // // const loads = loadObject3DFromJson(items) // Array.isArray(loads) && object3ds.push(...loads) // // // afterLoadComplete 通知所有加载的对象, 模型加载完成 // getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { // const ret = itemType.clazz.afterLoadComplete(object3ds) // Array.isArray(ret) && object3ds.push(...ret) // }) // // scene.add(...object3ds) // // // afterAddScene 通知所有加载的对象, 模型加载完成 // getAllItemTypes().forEach(itemType => { // itemType.clazz.afterAddScene(viewport, scene, object3ds) // }) // // console.log('loadSceneFromJson:', items.length, 'items,', object3ds.length, 'objects') // console.timeEnd('loadSceneFromJson') // } // // function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] { // const result: THREE.Object3D[] = [] // // for (const item of items) { // if (!item || !item.t) { // console.error('unkown item:', item) // continue // } // // const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item) // if (object3D === undefined) { // continue // } // // if (_.isArray(item.items)) { // // 如果有子元素,递归处理 // const children = loadObject3DFromJson(item.items) // children.forEach(child => object3D.add(child)) // } // // result.push(object3D) // } // // return result // } /** * 十进制求和 * @param collection * @param iteratee */ export function decimalSumBy(collection: ArrayLike | null | undefined, iteratee?: ((value: T) => number)): number { let sum = new Decimal(0) _.forEach(collection, (t) => { if (typeof iteratee === 'function') { sum = sum.add(new Decimal(iteratee(t))) } else { sum = sum.add(new Decimal(t)) } }) return sum.toNumber() }