import type Viewport from '@/core/engine/Viewport' import * as THREE from 'three' import { getLineId, getMatrixFromTf, setUserDataForItem, setUserDataForLine } from '@/core/ModelUtils.ts' import { Line2 } from 'three/examples/jsm/lines/Line2' import { LineWrap } from '@/core/manager/LineSegmentManager.ts' import type { LineLike, Object3DLike } from '@/types/ModelTypes.ts' import { MeshWrap } from '@/core/manager/InstanceMeshManager.ts' import { getRenderer } from '@/core/manager/ModuleManager.ts' /** * 基本渲染器基类 * 定义了点 / 线如何渲染到 Three.js 场景中. * 后期考虑调用 InstancePool 渲染 */ export default abstract class BaseRenderer { /** * 每次 beginUpdate 时记录临时 viewport, endUpdate 之后要马上删除, 因为 BaseRenderer 全局只有一个实例, 而 viewport 有多个 */ tempViewport?: Viewport = undefined isUpdating: boolean = false readonly itemTypeName: string constructor(itemTypeName: string) { this.itemTypeName = itemTypeName } async init(): Promise { return Promise.resolve() } /** * 开始更新 * @param viewport 当前视口 */ beginRendererUpdate(viewport: Viewport): void { this.tempViewport = viewport this.isUpdating = true } /** * 结束更新 */ endRendererUpdate(): void { this.tempViewport = undefined this.isUpdating = false } /** * 创建一个最基本的点对象, 不用管 item 的 name / id / 位置 / 转换 / 大小 和 userData, 除非有明确定义 */ createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { throw new Error('createPointBasic method must be implemented in derived class.') } /** * 创建测量线 */ createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): LineLike | undefined | void { throw new Error('createLineBasic method must be implemented in derived class.') } abstract get defaultScale(): THREE.Vector3 abstract get defaultRotation(): THREE.Vector3 abstract get defulePositionY(): number /** * 创建或更新线之后的回调 */ afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: LineLike) { } /** * 创建或更新点之后的回调 */ afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: Object3DLike) { } /** * 删除线之后的回调 */ afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption) { } /** * 将对象添加到当前视口的场景中 */ appendToScene(...objects: Object3DLike[]) { if (!this.tempViewport || !this.tempViewport.scene) { console.warn('No active viewport to append objects to.') return } this.tempViewport.scene.add(...objects) } removeFromScene(...objects: THREE.Object3D[]) { if (!this.tempViewport || !this.tempViewport.scene) { console.warn('No active viewport to remove objects from.') return } this.tempViewport.scene.remove(...objects) //this.tempViewport.dragManager.setDragObjects(objects, 'remove') } createPointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike { const point = this.createPoint(item, option) point.visible = ((typeof item.v !== 'undefined') ? item.v : true) if (item.dt.storeAt?.item) { this.tempViewport.runtimeManager.addStoreAt(item.dt.storeAt?.item, item.id) } setUserDataForItem(item, point) this.afterCreateOrUpdatePoint(item, option, point) this.tempViewport.entityManager.appendObject(item.id, point) this.appendToScene(point) return point } createOrUpdatePointForRuntime(item: ItemJson): Object3DLike { const option = { isRuntime: true } return this.createPointForEntity(item, option) } /** * 创建一个点 * @param item 点的定义 * @param option 渲染选项 */ createPoint(item: ItemJson, option?: RendererCudOption): Object3DLike { // 由基础类创造一个属于自己的点演示 const point = this.createPointBasic(item, option) const matrix = this.calcMeshMatrix(item, option) point.setMatrix4(matrix) point.visible = ((typeof item.v !== 'undefined') ? item.v : true) return point } calcMeshMatrix(item: ItemJson, option?: RendererCudOption) { let matrix = getMatrixFromTf(item.tf) if (item.dt.storeAt?.item) { // 如果是库存物品, 则需要将位置和角度设置为库存位置 const rack = Model.find(item.dt.storeAt?.item) if (!rack) { console.error(`Cannot find rack for item ${item.id} at ${item.dt.storeAt?.item}`) } const rackRenderer = getRenderer(rack.t) if (!rackRenderer) { console.error(`Cannot find renderer for rack type ${rack.t}`) } const { position, rotation } = rackRenderer.getStorePlacement(rack, item.dt.storeAt.bay, item.dt.storeAt.level, item.dt.storeAt.cell) if (!position || !rotation) { console.error(`无法获取物品 ${item.id} 的存储位置`) } option.position = position option.rotation = rotation } if (_.isArray(option?.position) && _.isArray(option?.rotation)) { matrix = new THREE.Matrix4() matrix.compose( new THREE.Vector3(option.position[0], option.position[1], option.position[2]), new THREE.Quaternion().setFromEuler(new THREE.Euler( THREE.MathUtils.degToRad(option?.rotation[0]), THREE.MathUtils.degToRad(option?.rotation[1]), THREE.MathUtils.degToRad(option?.rotation[2]), 'XYZ' )), new THREE.Vector3(item.tf[2][0], item.tf[2][1], item.tf[2][2]) ) // 回写 item 数据 item.tf[0] = _.cloneDeep(option.position) item.tf[1] = _.cloneDeep(option.rotation) } return matrix } /** * 删除一个点 * @param id 点的唯一标识 * @param option 渲染选项 */ deletePoint(id: string, option?: RendererCudOption) { const object = this.tempViewport.entityManager.findObjectById(id) if (object instanceof THREE.Object3D) { this.removeFromScene(object) } else if (object instanceof MeshWrap) { // 如果是 PointManageWrap, 则需要调用管理器的删除方法 object.manager.delete(id) } else { throw new Error('unkown Point type', object) } const item = this.tempViewport.entityManager.findItemById(id) if (item?.dt?.storeAt?.item) { this.tempViewport.runtimeManager.removeStoreAt(item.dt.storeAt?.item, id) } this.tempViewport.entityManager.deleteEntityOnly(id) this.tempViewport.entityManager.deleteObjectsOnly(id) } updatePointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike { const originObject = this.tempViewport.entityManager.findObjectById(item.id) const originItem = this.tempViewport.entityManager.findItemById(item.id) const newObject = this.updatePoint(item, originObject, option) newObject.visible = ((typeof item.v !== 'undefined') ? item.v : true) // 库存位置发生改变 if (originItem.dt.storeAt?.item !== item.dt.storeAt?.item) { if (originItem.dt.storeAt?.item) { this.tempViewport.runtimeManager.removeStoreAt(originItem.dt.storeAt?.item, item.id) } if (item.dt.storeAt?.item) { this.tempViewport.runtimeManager.addStoreAt(item.dt.storeAt?.item, item.id) } } if (originObject !== newObject) { // 如果更新后的对象和原来的对象不同, 则替换掉 setUserDataForItem(item, newObject) this.removeFromScene(originObject as THREE.Object3D) this.appendToScene(newObject as THREE.Object3D) this.tempViewport.entityManager.replaceObject(item.id, newObject) } this.afterCreateOrUpdatePoint(item, option, newObject) return newObject } /** * 更新一个点 * @param item 点的定义 * @param option 渲染选项 */ updatePoint(item: ItemJson, object: Object3DLike, option?: RendererCudOption): Object3DLike { const point = object point.name = item.name || point.name // point.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) // // point.rotation.set( // THREE.MathUtils.degToRad(item.tf[1][0]), // THREE.MathUtils.degToRad(item.tf[1][1]), // THREE.MathUtils.degToRad(item.tf[1][2]) // ) // // point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2]) // if (point instanceof PointManageWrap) { // point.manager.syncMeshObject3D(point) // } const matrix = this.calcMeshMatrix(item, option) point.setMatrix4(matrix) point.visible = ((typeof item.v !== 'undefined') ? item.v : true) return point } /** * 创建一根线 * @param start 起点 * @param end 终点 * @param type 线的类型 * @param option 渲染选项 */ createLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { const line = this.createLineBasic(start, end, type) const id = getLineId(start.id, end.id, type) const startPoint = this.tempViewport.entityManager.findObjectById(start.id) const endPoint = this.tempViewport.entityManager.findObjectById(end.id) this.tempViewport.entityManager.findObjectById(id) if (line instanceof Line2) { const geom = line.geometry geom.setFromPoints([startPoint.position, endPoint.position]) } setUserDataForLine(start, end, type, line) this.tempViewport.entityManager.appendLineObject(id, line) if (line instanceof THREE.Object3D) { this.appendToScene(line) } this.afterCreateOrUpdateLine(start, end, type, option, line) } updateLineForEntity(start: ItemJson, end: ItemJson, type: LinkType, option: any = {}) { const lineId = getLineId(start.id, end.id, type) const line = this.tempViewport.entityManager.findLineObjectById(lineId) option.lineId = lineId option.line = line this.updateLine(start, end, type, option) this.afterCreateOrUpdateLine(start, end, type, option, line) } /** * 获取物品存放位 * 获取物品存储到地堆货位中,返回可存放的位置和角度一个 Position 和 Rotation */ getStorePlacement(storeItem: ItemJson, bay = 0, level = 0, cell = 0) : { position: [number, number, number], rotation: [number, number, number] } { throw new Error(' 不支持库存物品的添加. t=' + this.itemTypeName) } /** * 更新一根线 * @param start 起点 * @param end 终点 * @param type 线的类型 * @param option 渲染选项 */ updateLine(start: ItemJson, end: ItemJson, type: LinkType, option: any) { const { line, lineId } = option const startPoint = this.tempViewport.entityManager.findObjectById(start.id) const endPoint = this.tempViewport.entityManager.findObjectById(end.id) if (line instanceof Line2) { const geom = line.geometry geom.setFromPoints([startPoint.position, endPoint.position]) } else if (line instanceof LineWrap) { line.manager.updateLine(lineId, startPoint.position, endPoint.position, line.color) } } deleteLineForEntity(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { this.deleteLine(start, end, type, option) this.afterDeleteLine(start, end, type, option) } /** * 删除一根线 * @param start 起点 * @param end 终点 * @param option 渲染选项 */ deleteLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { const lineId = getLineId(start.id, end.id, type) const line = this.tempViewport.entityManager.findLineObjectById(lineId) this.tempViewport.entityManager.deleteLineObjectOnly(lineId) } dispose() { // 清理资源 this.isUpdating = false } }