import * as THREE from 'three' import { BufferGeometry } from 'three' import BaseRenderer from '@/core/base/BaseRenderer.ts' import { decimalSumBy } from '@/core/ModelUtils' import Constract from '@/core/Constract.ts' import Plastic_Rough_JPG from '@/assets/Models/Plastic_Rough.jpg' import storageBar_PNG from '@/assets/Models/storageBar.png' import { Material } from 'three/src/materials/Material' import { InstancedMesh } from 'three/src/objects/InstancedMesh' //@ts-ignore import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js' import InstancePointManager from '@/core/manager/InstancePointManager.ts' import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' /** * 货架货位渲染器 */ export default class RackRenderer extends BaseRenderer { static POINT_NAME = 'rack' pointMaterial: THREE.Material /** * 默认点的高度, 防止和地面重合 */ readonly defulePositionY: number = Constract.HEIGHT_WAY readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1, 1, 1) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) readonly defaultLineWidth: number = 0.05 bottomBarHeight = 0.2 bottomLinkHeight = 0.2 barSectionPoints = [ { x: -0.05, y: -0.05 }, { x: -0.025, y: -0.05 }, { x: -0.01, y: -0.045 }, { x: 0.05, y: -0.045 }, { x: 0.025, y: -0.05 }, { x: 0.05, y: -0.05 }, { x: 0.05, y: 0.042 }, { x: 0.042, y: 0.05 }, { x: 0.025, y: 0.05 }, { x: 0.025, y: 0.042 }, { x: 0.042, y: 0.042 }, { x: 0.042, y: -0.042 }, { x: -0.042, y: -0.042 }, { x: -0.042, y: 0.042 }, { x: -0.025, y: 0.042 }, { x: -0.025, y: 0.05 }, { x: -0.042, y: 0.05 }, { x: -0.05, y: 0.042 }, { x: -0.05, y: -0.05 } ] linkSectionPoints = [ { x: -0.05, y: -0.05 }, { x: -0.05, y: 0.05 }, { x: 0, y: 0.05 }, { x: 0, y: 0.06 }, { x: -0.06, y: 0.06 }, { x: -0.06, y: -0.05 }, { x: -0.05, y: -0.05 } ] linkBarSectionPoints = [ { x: -0.025, y: -0.025 }, { x: 0.025, y: -0.025 }, { x: 0.025, y: 0.025 }, { x: -0.025, y: 0.025 }, { x: -0.025, y: -0.025 } ] rackVerticalBarWidth = 0.1 rackVerticalBarDepth = 0.08 rackVerticalBarColor = 0xFF35499C rackVerticalBarMaterial: Material = this.createVerticalBarMaterial() rackLinkBarColor = 0xFF35499C rackLinkBarGeometry: BufferGeometry = null rackLinkBarMaterial: Material = this.createLinkBarMaterial() rackHorizontalBarWidth = 0.1 rackHorizontalBarDepth = 0.08 rackHorizontalBarColor = 0xFFF97F27 rackHorizontalBarMaterial: Material = this.createHorizontalBarMaterial() constructor(itemTypeName: string) { super(itemTypeName) } /** * 所有的点,必须使用 storeWidth/storeDepth, 改TF无效 */ override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: THREE.Object3D) { super.afterCreateOrUpdatePoint(item, option, object) const point = object point.position.y = this.defulePositionY //point.scale.set(_.sumBy(item.dt.bays, b=>b.bayWidth), this.defaultScale.y, item.dt.rackDepth) point.rotation.set( THREE.MathUtils.degToRad(item.tf[1][0]), THREE.MathUtils.degToRad(item.tf[1][1]), THREE.MathUtils.degToRad(item.tf[1][2]) ) } createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { throw new Error('not allow store line.') } updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { throw new Error('not allow store line.') } createPoint(item: ItemJson, option?: RendererCudOption): Object3DLike { // 创建平面几何体 if (!item.dt.bays || !item.dt.rackDepth) { system.showErrorDialog('RackRenderer field bays / rackDepth is null!') return null } const group = new THREE.Group() group.name = RackRenderer.POINT_NAME // const rackWidth = decimalSumBy(item.dt.bays, (b: any) => b.bayWidth) // const heights = [] // for (let i = 0; i < item.dt.bays.length; i++) { // const bay = item.dt.bays[i] // const bayHeight = decimalSumBy(bay.levelHeight) // heights.push(bayHeight) // } // const rackHeight = _.max(heights) // // 绘制背景矩形框 // const planeGeometry = new THREE.PlaneGeometry(rackWidth, item.dt.rackDepth) // // planeGeometry.rotateX(Math.PI / 2) // // const planeMaterial = new THREE.MeshBasicMaterial({ // color: '#9a9090', // transparent: true, // 启用透明 // opacity: 0.5, // 50%透明度 // depthWrite: false, // 防止深度冲突 // side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"} // }) // const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial) // group.add(planeMesh) // // // // 绘制边框 // const lineXLen = rackWidth - this.defaultLineWidth // const lineYLen = item.dt.rackDepth - this.defaultLineWidth // // const lineGeometry = new LineGeometry().setPositions([ // -(lineXLen / 2), 0, -(lineYLen / 2), // lineXLen / 2, 0, -(lineYLen / 2), // lineXLen / 2, 0, lineYLen / 2, // -(lineXLen / 2), 0, lineYLen / 2, // -(lineXLen / 2), 0, -(lineYLen / 2) // ]) // const lineMaterial = new LineMaterial({ // color: '#0d89a5', // linewidth: this.defaultLineWidth, // worldUnits: true, // resolution: new THREE.Vector2(window.innerWidth, window.innerHeight), // side: THREE.DoubleSide // }) // const line = new Line2(lineGeometry, lineMaterial) // group.add(line as THREE.Object3D) // // let lineDistanceX = 0 // // for (let i = 0; item.dt.bays.length > 1 && i < item.dt.bays.length - 1; i++) { // const bay = item.dt.bays[i] // lineDistanceX += bay.bayWidth // const lineGeometryT = new LineGeometry().setPositions([ // -(lineDistanceX) + (lineXLen / 2), 0, lineYLen / 2, // -(lineDistanceX) + (lineXLen / 2), 0, -(lineYLen / 2) // ]) // const lineT = new Line2(lineGeometryT, lineMaterial) // group.add(lineT as THREE.Object3D) // } const meshes = this.createRack(item, option) // 设置位置 // group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) // // item.dt.rackWidth = rackWidth // item.dt.rackHeight = rackHeight return meshes } dispose() { super.dispose() this.pointMaterial?.dispose() } createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D { throw new Error('Rack createPointBasic not allow!') } createVerticalBar(length: number): THREE.BufferGeometry { // 创建一个形状 柱子的截面形状 const shape = new THREE.Shape() shape.moveTo(this.barSectionPoints[0].x, this.barSectionPoints[0].y) for (let i = 1; i < this.barSectionPoints.length; i++) { shape.lineTo(this.barSectionPoints[i].x, this.barSectionPoints[i].y) } // 拉伸轨迹线 const curve = new THREE.CatmullRomCurve3( [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, length, 0)], false, // 闭合曲线 'catmullrom', 0 ) // 挤出几何图形 参数 const options = { steps: 1, bevelEnabled: false, extrudePath: curve // 设置挤出轨迹 } // 创建挤出几何体 const geometry = new THREE.ExtrudeGeometry(shape, options) // 调整uv方便正确贴图 this.resetUVs(geometry) return geometry } createVerticalBarMaterial(): THREE.Material { let textureLoader = new THREE.TextureLoader() // 加载纹理 const textureHole = textureLoader.load(storageBar_PNG) // 孔洞 const textureMaterial = textureLoader.load(Plastic_Rough_JPG) // 表面材质 textureHole.repeat.set(10, 18) // X轴重复,Y轴重复 textureMaterial.repeat.set(2, 2) // X轴重复,Y轴重复 // textureHole.offset.set(0.5, 0) // textureHole.center.set(0.5, 0) // 必须设置包裹模式为重复 textureHole.wrapS = THREE.RepeatWrapping textureHole.wrapT = THREE.RepeatWrapping textureMaterial.wrapS = THREE.RepeatWrapping textureMaterial.wrapT = THREE.RepeatWrapping const material = new THREE.MeshPhongMaterial() material.alphaMap = textureHole material.normalMap = textureMaterial material.color.setHex(this.rackVerticalBarColor, 'srgb') material.specular.setHex(0xff6d6d6d, 'srgb') material.transparent = true material.needsUpdate = true return material } createLinkBar(vBarLength: number, depth: number, bottomDistance: number, topDistance: number): THREE.BufferGeometry { const bgs: BufferGeometry[] = [] const top = vBarLength - topDistance // 创建一个形状 柱子的截面形状 const shape = new THREE.Shape() shape.moveTo(this.linkBarSectionPoints[0].x, this.linkBarSectionPoints[0].y) for (let i = 1; i < this.linkBarSectionPoints.length; i++) { shape.lineTo(this.linkBarSectionPoints[i].x, this.linkBarSectionPoints[i].y) } // 拉伸轨迹线 横向 底部 const curveHBottom = new THREE.CatmullRomCurve3( [new THREE.Vector3(0, bottomDistance, 0), new THREE.Vector3(0, bottomDistance, depth)], false, // 闭合曲线 'catmullrom', 0 ) // 挤出几何图形 参数 const optionsHBottom = { steps: 1, bevelEnabled: false, extrudePath: curveHBottom // 设置挤出轨迹 } // 拉伸轨迹线 横向 底部 const curveHTop = new THREE.CatmullRomCurve3( [new THREE.Vector3(0, top, 0), new THREE.Vector3(0, top, depth)], false, // 闭合曲线 'catmullrom', 0 ) // 挤出几何图形 参数 const optionsHTop = { steps: 1, bevelEnabled: false, extrudePath: curveHTop // 设置挤出轨迹 } // 创建挤出几何体 const geometryHBottom = new THREE.ExtrudeGeometry(shape, optionsHBottom) const geometryHTop = new THREE.ExtrudeGeometry(shape, optionsHTop) bgs.push(geometryHBottom, geometryHTop) let remainingHeight = vBarLength - bottomDistance - topDistance // 需要创建斜杆 for (let i = 0; i < Math.floor(remainingHeight / depth); i++) { // 拉伸轨迹线 斜向 const curveD = new THREE.CatmullRomCurve3( (i % 2 == 0) ? [new THREE.Vector3(0, bottomDistance + depth * i, 0), new THREE.Vector3(0, bottomDistance + depth * (i + 1), depth)] : [new THREE.Vector3(0, bottomDistance + depth * (i + 1), 0), new THREE.Vector3(0, bottomDistance + depth * (i), depth)], false, // 闭合曲线 'catmullrom', 0 ) const optionsD = { steps: 1, bevelEnabled: false, extrudePath: curveD // 设置挤出轨迹 } const geometryD = new THREE.ExtrudeGeometry(shape, optionsD) bgs.push(geometryD) } if (vBarLength - bottomDistance - topDistance > depth) { } // 调整uv方便正确贴图 // this.resetUVs(geometry); return mergeGeometries(bgs) } createLinkBarMaterial(): THREE.Material { const material = new THREE.MeshPhongMaterial() material.color.setHex(this.rackLinkBarColor, 'srgb') material.specular.setHex(0xff6d6d6d, 'srgb') material.transparent = true material.needsUpdate = true return material } createHorizontalBar(length: number): THREE.BufferGeometry { // 创建一个形状 柱子的截面形状 const shape = new THREE.Shape() shape.moveTo(this.barSectionPoints[0].x, this.barSectionPoints[0].y) for (let i = 1; i < this.barSectionPoints.length; i++) { shape.lineTo(this.barSectionPoints[i].x, this.barSectionPoints[i].y) } // 拉伸轨迹线 const curve = new THREE.CatmullRomCurve3( [new THREE.Vector3(0.05, 0, 0), new THREE.Vector3(length - 0.05, 0, 0)], false, // 闭合曲线 'catmullrom', 0 ) // 挤出几何图形 参数 const options = { steps: 1, bevelEnabled: false, extrudePath: curve // 设置挤出轨迹 } // 创建挤出几何体 const geometry = new THREE.ExtrudeGeometry(shape, options) const linkShapeL = new THREE.Shape() const linkShapeR = new THREE.Shape() linkShapeL.moveTo(this.linkSectionPoints[0].x, this.linkSectionPoints[0].y) linkShapeR.moveTo(this.linkSectionPoints[0].x + (length), this.linkSectionPoints[0].y) for (let i = 1; i < this.linkSectionPoints.length; i++) { linkShapeL.lineTo(this.linkSectionPoints[i].x, this.linkSectionPoints[i].y) linkShapeR.lineTo(this.linkSectionPoints[i].x + (length), this.linkSectionPoints[i].y) } // 拉伸轨迹线 const linkCurve = new THREE.CatmullRomCurve3( [new THREE.Vector3(0, 0, -0.08), new THREE.Vector3(0, 0, 0.08)], false, // 闭合曲线 'catmullrom', 0 ) // 挤出几何图形 参数 const linkOptions = { steps: 1, bevelEnabled: false, extrudePath: linkCurve // 设置挤出轨迹 } // 创建挤出几何体 const linkGeometryL = new THREE.ExtrudeGeometry(linkShapeL, linkOptions) linkGeometryL.rotateZ(-Math.PI / 2) const linkGeometryR = new THREE.ExtrudeGeometry(linkShapeR, linkOptions) linkGeometryR.rotateX(-Math.PI) linkGeometryR.rotateZ(-Math.PI / 2) // 调整uv方便正确贴图 // this.resetUVs(geometry); return mergeGeometries([geometry, linkGeometryL, linkGeometryR]) } createHorizontalBarMaterial(): THREE.Material { const material = new THREE.MeshPhongMaterial() material.color.setHex(this.rackHorizontalBarColor, 'srgb') material.specular.setHex(0xff6d6d6d, 'srgb') material.transparent = true material.needsUpdate = true return material } createRack(item: ItemJson, option?: RendererCudOption): Object3DLike { if (!item.dt.bays || !item.dt.rackDepth) { system.showErrorDialog('RackRenderer field bays / rackDepth is null!') return null } const rackPoint = { x: item.tf[0][0], y: item.tf[0][1], z: item.tf[0][2] } const heights = [] for (let i = 0; i < item.dt.bays.length; i++) { const bay = item.dt.bays[i] const bayHeight = decimalSumBy(bay.levelHeight) heights.push(bayHeight) } const rackHeight = _.max(heights) // 计算立住坐标点和长度 const vBarMatrix: { x: number, y: number, z: number, sx: number, sy: number, sz: number, rx: number, ry: number, rz: number, l: number }[] = [] // 计算 const linkBarMatrix: { x: number, y: number, z: number, sx: number, sy: number, sz: number, rx: number, ry: number, rz: number, l: number }[] = [] let distanceX = 0, distanceY = 0 for (let i = -1; i < item.dt.bays.length; i++) { if (i >= 0) { const bay = item.dt.bays[i] distanceX += bay.bayWidth } vBarMatrix.push({ x: rackPoint.x + distanceX, y: rackPoint.y, z: rackPoint.z, sx: 0.8, sy: 1, sz: 1, rx: 0, ry: Math.PI / 2, rz: 0, l: rackHeight }) vBarMatrix.push({ x: rackPoint.x + distanceX, y: rackPoint.y, z: rackPoint.z + item.dt.rackDepth, sx: 0.8, sy: 1, sz: 1, rx: 0, ry: -Math.PI / 2, rz: 0, l: rackHeight }) linkBarMatrix.push({ x: rackPoint.x + distanceX, y: rackPoint.y, z: i % 2 == 0 ? (rackPoint.z + item.dt.rackDepth) : rackPoint.z, sx: 1, sy: 1, sz: 1, rx: 0, ry: i % 2 == 0 ? Math.PI : 0, rz: 0, l: rackHeight }) } // 计算横梁数量 const hBarMatrix: { x: number, y: number, z: number, sx: number, sy: number, sz: number, rx: number, ry: number, rz: number, l: number }[] = [] distanceX = 0 for (let i = 0; i < item.dt.bays.length; i++) { distanceY = this.bottomBarHeight const bay = item.dt.bays[i] for (let j = 0; j < bay.levelHeight.length; j++) { const levelHeight = bay.levelHeight[j] if (distanceY <= 0) { continue } hBarMatrix.push({ x: rackPoint.x + distanceX, y: rackPoint.y + distanceY, z: rackPoint.z, sx: 1, sy: 0.8, sz: 1, rx: Math.PI / 2, ry: 0, rz: 0, l: bay.bayWidth }) hBarMatrix.push({ x: rackPoint.x + distanceX, y: rackPoint.y + distanceY, z: rackPoint.z + item.dt.rackDepth, sx: 1, sy: 0.8, sz: 1, rx: -Math.PI / 2, ry: 0, rz: 0, l: bay.bayWidth }) distanceY += levelHeight } distanceX += bay.bayWidth } if (vBarMatrix.length > 0) { const dummy = new THREE.Object3D() // const vBarMesh = new THREE.InstancedMesh(this.rackVerticalBarGeometry, this.rackVerticalBarMaterial, vBarMatrix.length) for (let i = 0; i < vBarMatrix.length; i++) { const vp = vBarMatrix[i] const wrap = this.getVBarMesh(vp.l).create(item.id) dummy.position.set(vp.x, vp.y, vp.z) dummy.rotation.set(vp.rx, vp.ry, vp.rz) dummy.scale.set(vp.sx, vp.sy, vp.sz) dummy.updateMatrix() wrap.setMatrix4(dummy.matrix) } } if (linkBarMatrix.length > 0) { // if (!this.rackLinkBarGeometry) { // this.rackLinkBarGeometry = this.createLinkBar(rackHeight, item.dt.rackDepth, this.bottomLinkHeight, 0.2) // } const meshInstance = this.getLinkBarMesh(rackHeight, item.dt.rackDepth, this.bottomLinkHeight, 0.2) const dummy = new THREE.Object3D() const linkBarMesh = new THREE.InstancedMesh(this.rackLinkBarGeometry, this.rackLinkBarMaterial, linkBarMatrix.length) for (let i = 0; i < linkBarMatrix.length; i++) { const wrap = meshInstance.create(item.id) const lp = linkBarMatrix[i] dummy.position.set(lp.x, lp.y, lp.z) dummy.rotation.set(lp.rx, lp.ry, lp.rz) dummy.scale.set(lp.sx, lp.sy, lp.sz) dummy.updateMatrix() // linkBarMesh.setMatrixAt(i, dummy.matrix) wrap.setMatrix4(dummy.matrix) } } if (hBarMatrix.length > 0) { const dummy = new THREE.Object3D() // const hBarMesh = new THREE.InstancedMesh(this.rackHorizontalBarGeometry, this.rackHorizontalBarMaterial, hBarMatrix.length) for (let i = 0; i < hBarMatrix.length; i++) { const hp = hBarMatrix[i] const wrap = this.getHBarMesh(hp.l).create(item.id) dummy.position.set(hp.x, hp.y, hp.z) dummy.rotation.set(hp.rx, hp.ry, hp.rz) dummy.scale.set(hp.sx, hp.sy, hp.sz) dummy.updateMatrix() wrap.setMatrix4(dummy.matrix) } } return new THREE.Group() } resetUVs(geometry: THREE.ExtrudeGeometry) { if (geometry == undefined) return const pos = geometry.getAttribute('position'), nor = geometry.getAttribute('normal'), uvs = geometry.getAttribute('uv') for (let i = 0; i < pos.count; i++) { let x = 0, y = 0 const nx = Math.abs(nor.getX(i)), ny = Math.abs(nor.getY(i)), nz = Math.abs(nor.getZ(i)) // if facing X if (nx >= ny && nx >= nz) { x = pos.getZ(i) y = pos.getY(i) } // if facing Y if (ny >= nx && ny >= nz) { x = pos.getX(i) y = pos.getZ(i) } // if facing Z if (nz >= nx && nz >= ny) { x = pos.getX(i) y = pos.getY(i) } uvs.setXY(i, x, y) } } getLinkBarMesh(vBarLength: number, depth: number, bottomDistance: number, topDistance: number): InstanceMeshManager { if (!this.tempViewport) { throw new Error('tempViewport is not set.') } // rackHeight, item.dt.rackDepth, this.bottomLinkHeight, 0.2 const geometry = this.createLinkBar(vBarLength, depth, bottomDistance, topDistance) const name = 'rack_linkbar_' + vBarLength + '_' + depth + '_' + bottomDistance + '_' + topDistance return this.tempViewport.getOrCreateMeshManager(name, () => // 构建 InstanceMesh 代理对象 new InstanceMeshManager(name, this.tempViewport, geometry, this.rackLinkBarMaterial, false, false, 100000) ) } getVBarMesh(length: number): InstanceMeshManager { if (!this.tempViewport) { throw new Error('tempViewport is not set.') } const name = 'rack_vbar' + length return this.tempViewport.getOrCreateMeshManager(name, () => { const geometry = this.createVerticalBar(length) return new InstanceMeshManager(name, this.tempViewport, geometry, this.rackVerticalBarMaterial, false, false, 100000) }) } getHBarMesh(length: number): InstanceMeshManager { if (!this.tempViewport) { throw new Error('tempViewport is not set.') } const name = 'rack_hbar' + length return this.tempViewport.getOrCreateMeshManager(name, () => { const geometry = this.createHorizontalBar(length) return new InstanceMeshManager(name, this.tempViewport, geometry, this.rackHorizontalBarMaterial, false, false, 100000) }) } }