From faeaa553865819c8a771d24523ae22463db94180 Mon Sep 17 00:00:00 2001 From: luoyifan Date: Wed, 11 Jun 2025 14:23:26 +0800 Subject: [PATCH] =?UTF-8?q?carton=20=E7=BA=B8=E7=AE=B1=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/Models/carton.glb | Bin 0 -> 3436 bytes src/assets/Models/carton.jpg | Bin 0 -> 40215 bytes src/components/Model3DView.vue | 333 +++++++++++++++-------------- src/core/ModelUtils.ts | 48 +++++ src/core/manager/WorldModel.ts | 2 + src/example/ExampleUtil.js | 2 +- src/example/example1.js | 8 +- src/modules/carton/CartonEntity.ts | 5 + src/modules/carton/CartonInteraction.ts | 22 ++ src/modules/carton/CartonPropertySetter.ts | 20 ++ src/modules/carton/CartonRenderer.ts | 83 +++++++ src/modules/carton/index.ts | 15 ++ src/types/ModelTypes.ts | 9 + 13 files changed, 384 insertions(+), 163 deletions(-) create mode 100644 src/assets/Models/carton.glb create mode 100644 src/assets/Models/carton.jpg create mode 100644 src/modules/carton/CartonEntity.ts create mode 100644 src/modules/carton/CartonInteraction.ts create mode 100644 src/modules/carton/CartonPropertySetter.ts create mode 100644 src/modules/carton/CartonRenderer.ts create mode 100644 src/modules/carton/index.ts diff --git a/src/assets/Models/carton.glb b/src/assets/Models/carton.glb new file mode 100644 index 0000000000000000000000000000000000000000..1f5efa749a0aed919d812f2e1458871f504f9388 GIT binary patch literal 3436 zcmb7EU2GIp6h1(K@>gtyQYb&`+|iH{@_@Ii@B{%btvG6M_SZObP2&UerG z&N<&XcV>H2?R)0~KxzfRzJ&lUw#8cpnV!iQHftDUy++!wblbF81KZzcnQ1fQ>BSIF zEM=s-4a?KCGwwMS^vnKWmOqo|QMmK9X%=YhV?daES z!%FI@Oh;zGNMuvG#f}OrlfYuEL3B_C*@G;t_oI!qai74_X175O@#qjnxTitZZ)Ey- zsKam6t?n)pH&4tw?m=algr34ghef|ADq={C$PqJ~bhJ`sBdDRE%xxj8S+m$lf zlHJsvZDozZrfGyF8b*)X{9Zl5CoB%(#GD2WI3x0GmK2mf&}AJkY(14qCR)vGZ(rKT z;1~v3m!2^i&6H`mIj}(`60YYzMUn#{DT3|I#T3AST5fJPo#>A!5hWZ6Ns=541eDMa z&7G#rui?S{9La`zl)#Z}C?Y3Fu9fUh+R0-MNw;k)*_E~FILKa#w>7ml#asA&wZvNw z>~3Za0fDveIn)@Bw?5w?vIYq!kxX|d6BHt2MQ0osGsG`JPoOJfTJ$pD7_vP*hIKG$ zyvCCz%>Ds0ZKUn?69ahZNtkDWckZ8P9l0cSSM>pFbGZvG^Ph;)-{OK_HC>HZ^-Ygh(J>VB%%70&bb!-~U^E-CpxtgiR zQaJDQMVyk@>5Fp;FxP;3d4AVUXQxNfKN}{^E@wvU_C@?@Ft6p<^Y?C#Pkp?#^HY4P zCGb>%ov`ZgOJU06atKp?jchYu;=7#Luv?e-gx&l_xP)&mIv%gVH8~om^*LHx?L}G& zVqLv+(Lo${QQVW*`y@m1fVmYph&J<5)s=FRBoqxMLrvBL~Xmln%>1zIbTrH~|(cZ2_ z4SgfL{u?)a8oY3d_puNCWT!e+U;4Cf{uotH*VOUz$uE4`y2UCti*E?D=(s_pz%Jq95i*v}^Nm=9riK)F=7rOnKhB zJgRPacU)ZnWv~bq-~w0%6;O$LB`kqePz^P>*FY6)1P?IW8PvkF-~}JoC3?Ics1AW_t!!VjcmW!VF=o2q57u!A?}L0Jh`49k!s3 zGT4Co23U)XMX(z8)$lYdhoz`xIj(9{xeT>b;PRl>3e-}G%ZsWjQOgosL1e8$wbi&7 cDysnncLf5l3D%?XO}Kop1Gb^^9k@d9KPZzqjsO4v literal 0 HcmV?d00001 diff --git a/src/assets/Models/carton.jpg b/src/assets/Models/carton.jpg new file mode 100644 index 0000000000000000000000000000000000000000..681f48c7adaf594d1a407661738bd10a7532e191 GIT binary patch literal 40215 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 literal 0 HcmV?d00001 diff --git a/src/components/Model3DView.vue b/src/components/Model3DView.vue index e87bbcd..a93385d 100644 --- a/src/components/Model3DView.vue +++ b/src/components/Model3DView.vue @@ -2,7 +2,7 @@
+ :show-file-list="false" accept=".fbx,.obj,.mtl,.3ds,.glb,.gltf" action="" :auto-upload="false"> 打开模型 restate.mode, (newVal) => { tcontrols.setMode(newVal) } }) -function addConveyor(){ - const conveyorLength = 10; // 长度 - const conveyorWidth = 0.8; // 宽度 - const conveyorHeight = 0.08; // 厚度 - const wallHeight = conveyorHeight + 0.15; // 挡板高出输送线 0.2 米 - const legWidth = 0.1; // 柱子宽度(X) - const legDepth = 0.1; // 柱子深度(Z) - const legHeight = 0.8; // 柱子高度(Y) +function addConveyor() { + const conveyorLength = 10 // 长度 + const conveyorWidth = 0.8 // 宽度 + const conveyorHeight = 0.08 // 厚度 + const wallHeight = conveyorHeight + 0.15 // 挡板高出输送线 0.2 米 + + const legWidth = 0.1 // 柱子宽度(X) + const legDepth = 0.1 // 柱子深度(Z) + const legHeight = 0.8 // 柱子高度(Y) // 创建输送线底部板子(灰色) - const conveyorGeometry = new THREE.BoxGeometry(conveyorWidth, conveyorHeight, conveyorLength); - const conveyorMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); - const conveyor = new THREE.Mesh(conveyorGeometry, conveyorMaterial); - conveyor.position.y = conveyorHeight / 2; + const conveyorGeometry = new THREE.BoxGeometry(conveyorWidth, conveyorHeight, conveyorLength) + const conveyorMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }) + const conveyor = new THREE.Mesh(conveyorGeometry, conveyorMaterial) + conveyor.position.y = conveyorHeight / 2 // 添加两侧挡板(灰色) - const wallWidth = 0.05; - const wallGeometry = new THREE.BoxGeometry(wallWidth, wallHeight, conveyorLength); - const wallMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); + const wallWidth = 0.05 + const wallGeometry = new THREE.BoxGeometry(wallWidth, wallHeight, conveyorLength) + const wallMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }) - const leftWall = new THREE.Mesh(wallGeometry, wallMaterial); - leftWall.position.set(-(conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0); + const leftWall = new THREE.Mesh(wallGeometry, wallMaterial) + leftWall.position.set(-(conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0) - const rightWall = new THREE.Mesh(wallGeometry, wallMaterial); - rightWall.position.set((conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0); + const rightWall = new THREE.Mesh(wallGeometry, wallMaterial) + rightWall.position.set((conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0) // 创建柱子(灰色,类似桌腿) - const legGeometry = new THREE.BoxGeometry(legWidth, legHeight, legDepth); - const legMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); // 灰色 + const legGeometry = new THREE.BoxGeometry(legWidth, legHeight, legDepth) + const legMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }) // 灰色 const legPositions = [ [conveyorWidth / 2 - legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], // 右前 [-conveyorWidth / 2 + legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], // 左前 [conveyorWidth / 2 - legWidth / 2, +(conveyorLength / 2 - legDepth / 2)], // 右后 [-conveyorWidth / 2 + legWidth / 2, +(conveyorLength / 2 - legDepth / 2)] // 左后 - ]; + ] - const legs = []; + const legs = [] legPositions.forEach(pos => { - const leg = new THREE.Mesh(legGeometry, legMaterial); + const leg = new THREE.Mesh(legGeometry, legMaterial) leg.position.set( pos[0], -legHeight / 2, pos[1] - ); - legs.push(leg); - }); + ) + legs.push(leg) + }) // ====== 新增部分:顶部带圆角的长方体(胶囊型)====== - const beamWidth = conveyorWidth; // 和输送线一样宽(0.8米) - const beamLength = conveyorLength-conveyorHeight/2;// 和输送线一样长(10米) - const beamHeight = 0.08; // 高度(0.1米) + const beamWidth = conveyorWidth // 和输送线一样宽(0.8米) + const beamLength = conveyorLength - conveyorHeight / 2// 和输送线一样长(10米) + const beamHeight = 0.08 // 高度(0.1米) // 创建 Shape(二维路径) - const shape = new THREE.Shape(); + const shape = new THREE.Shape() - const radius = beamHeight / 2; // 圆角半径 = 一半宽度 + const radius = beamHeight / 2 // 圆角半径 = 一半宽度 // 左侧半圆(从顶部开始,顺时针) - shape.absarc(-beamLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2, false); + shape.absarc(-beamLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2, false) // 右侧半圆(从底部开始,顺时针) - shape.absarc(beamLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2, false); + shape.absarc(beamLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2, false) - shape.closePath(); // 封闭路径 + shape.closePath() // 封闭路径 // 拉伸成三维几何体 const extrudeSettings = { depth: beamWidth, steps: 16, bevelEnabled: false - }; + } - const beamGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); - const beamMaterial = new THREE.MeshBasicMaterial({ color: 0x032702 }); // 粉色 + const beamGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings) + const beamMaterial = new THREE.MeshBasicMaterial({ color: 0x032702 }) // 粉色 - const beam = new THREE.Mesh(beamGeometry, beamMaterial); - beam.rotation.x = Math.PI / 2; + const beam = new THREE.Mesh(beamGeometry, beamMaterial) + beam.rotation.x = Math.PI / 2 beam.rotation.z = Math.PI / 2 beam.rotation.y = Math.PI / 2 - beam.position.y = beamHeight+conveyorHeight/2; // 底部贴着输送线顶部 - beam.position.x = -beamWidth/2; - addMarkerAt(0, legHeight+beamHeight+conveyorHeight,0,triangleUrl,1,false); - addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,0,triangleUrl,.5,true); - addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,conveyorLength/4,triangleUrl,.5,true); - addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,-conveyorLength/4,triangleUrl,.5,true); - - addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,0,triangleUrl,.5,true); - addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,conveyorLength/4,triangleUrl,.5,true); - addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,-conveyorLength/4,triangleUrl,.5,true); + beam.position.y = beamHeight + conveyorHeight / 2 // 底部贴着输送线顶部 + beam.position.x = -beamWidth / 2 + addMarkerAt(0, legHeight + beamHeight + conveyorHeight, 0, triangleUrl, 1, false) + addMarkerAt(beamWidth / 2 + wallWidth + 0.01, legHeight + wallHeight / 2, 0, triangleUrl, .5, true) + addMarkerAt(beamWidth / 2 + wallWidth + 0.01, legHeight + wallHeight / 2, conveyorLength / 4, triangleUrl, .5, true) + addMarkerAt(beamWidth / 2 + wallWidth + 0.01, legHeight + wallHeight / 2, -conveyorLength / 4, triangleUrl, .5, true) + + addMarkerAt(-(beamWidth / 2 + wallWidth + 0.01), legHeight + wallHeight / 2, 0, triangleUrl, .5, true) + addMarkerAt(-(beamWidth / 2 + wallWidth + 0.01), legHeight + wallHeight / 2, conveyorLength / 4, triangleUrl, .5, true) + addMarkerAt(-(beamWidth / 2 + wallWidth + 0.01), legHeight + wallHeight / 2, -conveyorLength / 4, triangleUrl, .5, true) // 将所有元素组合成一个组 - const group = new THREE.Group(); - group.add(conveyor); - group.add(leftWall); - group.add(rightWall); - legs.forEach(leg => group.add(leg)); - group.add(beam); // 添加新长方体 + const group = new THREE.Group() + group.add(conveyor) + group.add(leftWall) + group.add(rightWall) + legs.forEach(leg => group.add(leg)) + group.add(beam) // 添加新长方体 // 设置组沿Z轴向上移动的距离,例如 1 米 - group.position.y = legHeight; // 修改这里的值来改变提升的高度 - scene.add(group); + group.position.y = legHeight // 修改这里的值来改变提升的高度 + scene.add(group) } -function addMarkerAt(x, y,z,textUrl, scale = 1,lip) { - const loader = new THREE.TextureLoader(); + +function addMarkerAt(x, y, z, textUrl, scale = 1, lip) { + const loader = new THREE.TextureLoader() loader.load(textUrl, (texture) => { // 创建一个平面作为标记(大小可以调整) let markerGeometry - if(lip){ - markerGeometry=new THREE.PlaneGeometry(.25 * scale, .25 * scale); - }else{ - markerGeometry = new THREE.PlaneGeometry(.45 * scale, .3 * scale); // 宽高比例保持一致 + if (lip) { + markerGeometry = new THREE.PlaneGeometry(.25 * scale, .25 * scale) + } else { + markerGeometry = new THREE.PlaneGeometry(.45 * scale, .3 * scale) // 宽高比例保持一致 } const markerMaterial = new THREE.MeshBasicMaterial({ @@ -288,193 +291,193 @@ function addMarkerAt(x, y,z,textUrl, scale = 1,lip) { opacity: 1, depthWrite: false, side: THREE.DoubleSide - }); + }) - const marker = new THREE.Mesh(markerGeometry, markerMaterial); - let yHeight=y+0.01 + const marker = new THREE.Mesh(markerGeometry, markerMaterial) + let yHeight = y + 0.01 // 设置位置:位于输送线表面(Y轴略高于输送线一点) - marker.position.set(x, yHeight, z); // Y = 输送线高度 + 一点间距 + marker.position.set(x, yHeight, z) // Y = 输送线高度 + 一点间距 // 旋转为 X/Z 平面方向(与输送线一致) - marker.rotation.x = -Math.PI / 2; // 和 conveyorBelt 一样旋转角度 - marker.rotation.z = Math.PI / 2; // 在平面上旋转 90 度 - if(lip){ - marker.rotation.y = Math.PI / 2; // 在平面上旋转 90 度 - }else{ - marker.rotation.y = Math.PI; + marker.rotation.x = -Math.PI / 2 // 和 conveyorBelt 一样旋转角度 + marker.rotation.z = Math.PI / 2 // 在平面上旋转 90 度 + if (lip) { + marker.rotation.y = Math.PI / 2 // 在平面上旋转 90 度 + } else { + marker.rotation.y = Math.PI } - scene.add(marker); - }); + scene.add(marker) + }) } -function createShelf(){//创建货架 - const shelfLength = 5; - const shelfWidth = 0.8; - const shelfThickness = 0.02; - const rows = 3; - const columns = 4; - const spacingY = 1; // 每行之间的间距 - const gapBetweenPlanks = 0.1; // 层板之间的间隙宽度 - const baseHeight = 0; // 地面高度设为0 +function createShelf() {//创建货架 + const shelfLength = 5 + const shelfWidth = 0.8 + const shelfThickness = 0.02 + const rows = 3 + const columns = 4 + const spacingY = 1 // 每行之间的间距 + const gapBetweenPlanks = 0.1 // 层板之间的间隙宽度 + const baseHeight = 0 // 地面高度设为0 - const unitLength = (shelfLength - (columns - 1) * gapBetweenPlanks) / columns; + const unitLength = (shelfLength - (columns - 1) * gapBetweenPlanks) / columns - const textureLoader = new THREE.TextureLoader(); + const textureLoader = new THREE.TextureLoader() const shelfTexture = textureLoader.load(rackPlatUrl, () => { - renderer.render(scene, camera); - }); + renderer.render(scene, camera) + }) const redPoleTexture = textureLoader.load(rackBlue, () => { - renderer.render(scene, camera); // 确保贴图加载后重新渲染 - }); + renderer.render(scene, camera) // 确保贴图加载后重新渲染 + }) - shelfTexture.wrapS = THREE.RepeatWrapping; - shelfTexture.wrapT = THREE.RepeatWrapping; - shelfTexture.repeat.set(1, 20); - shelfTexture.rotation = Math.PI / 2; + shelfTexture.wrapS = THREE.RepeatWrapping + shelfTexture.wrapT = THREE.RepeatWrapping + shelfTexture.repeat.set(1, 20) + shelfTexture.rotation = Math.PI / 2 const shelfMaterial = new THREE.MeshPhongMaterial({ map: shelfTexture, color: 0x999999, shininess: 60 - }); + }) // 垂直柱子材质 const poleMaterial = new THREE.MeshPhongMaterial({ map: shelfTexture, color: 0x333333, shininess: 60 - }); + }) // 柱子尺寸 - const poleSize = gapBetweenPlanks; // 柱子的宽度和深度都等于缝隙宽度 - const totalHeight = rows * spacingY; // 柱子总高度 + const poleSize = gapBetweenPlanks // 柱子的宽度和深度都等于缝隙宽度 + const totalHeight = rows * spacingY // 柱子总高度 for (let row = 0; row < rows; row++) { - let currentXOffset = -shelfLength / 2; + let currentXOffset = -shelfLength / 2 for (let col = 0; col < columns; col++) { - const geometry = new THREE.BoxGeometry(unitLength, shelfThickness, shelfWidth); - const mesh = new THREE.Mesh(geometry, shelfMaterial); + const geometry = new THREE.BoxGeometry(unitLength, shelfThickness, shelfWidth) + const mesh = new THREE.Mesh(geometry, shelfMaterial) - const x = currentXOffset + unitLength / 2; + const x = currentXOffset + unitLength / 2 // 计算层板的 Y 坐标,使其基于地面高度 - const y = baseHeight + row * spacingY + shelfThickness / 2; - const z = 0; + const y = baseHeight + row * spacingY + shelfThickness / 2 + const z = 0 - mesh.position.set(x, y, z); - scene.add(mesh); + mesh.position.set(x, y, z) + scene.add(mesh) if (row === 0) { // 只在第一层添加垂直柱子 // 最左侧柱子 if (col === 0) { - const leftPoleX = currentXOffset - poleSize / 2; - const leftPoleY = baseHeight + totalHeight / 2; - const leftPoleZ = z - shelfWidth / 2 + poleSize / 2; + const leftPoleX = currentXOffset - poleSize / 2 + const leftPoleY = baseHeight + totalHeight / 2 + const leftPoleZ = z - shelfWidth / 2 + poleSize / 2 // 黑色柱子 const leftPole = new THREE.Mesh( new THREE.BoxGeometry(poleSize, totalHeight, poleSize), poleMaterial - ); - leftPole.position.set(leftPoleX, leftPoleY, leftPoleZ); - scene.add(leftPole); + ) + leftPole.position.set(leftPoleX, leftPoleY, leftPoleZ) + scene.add(leftPole) // // 红色柱子 - const redLeftPoleZ = z + shelfWidth / 2 - poleSize / 2; + const redLeftPoleZ = z + shelfWidth / 2 - poleSize / 2 const redLeftPole = new THREE.Mesh( new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.MeshPhongMaterial({ map: redPoleTexture, color: 0xffffff, // 颜色可保留作为叠加色 shininess: 60, - transparent: true, + transparent: true }) // new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 }) - ); - redLeftPole.position.set(leftPoleX, leftPoleY, redLeftPoleZ); - scene.add(redLeftPole); + ) + redLeftPole.position.set(leftPoleX, leftPoleY, redLeftPoleZ) + scene.add(redLeftPole) } // 层板间隙中的柱子 if (col < columns - 1) { - const gapStartX = currentXOffset + unitLength; + const gapStartX = currentXOffset + unitLength // 原有灰色柱子(居中) - const poleX = gapStartX + poleSize / 2; - const poleY = baseHeight + totalHeight / 2; - const poleZ = z- shelfWidth / 2+poleSize/2; + const poleX = gapStartX + poleSize / 2 + const poleY = baseHeight + totalHeight / 2 + const poleZ = z - shelfWidth / 2 + poleSize / 2 const pole = new THREE.Mesh( new THREE.BoxGeometry(poleSize, totalHeight, poleSize), poleMaterial - ); - pole.position.set(poleX, poleY, poleZ); - scene.add(pole); + ) + pole.position.set(poleX, poleY, poleZ) + scene.add(pole) // 新增红色柱子,在原有柱子的前面或后面并排 - const redPoleX = poleX; // 和灰柱 X 相同 - const redPoleY = poleY; // 和灰柱 Y 相同 - const redPoleZ = z + shelfWidth / 2-poleSize/2; // 在前方 + const redPoleX = poleX // 和灰柱 X 相同 + const redPoleY = poleY // 和灰柱 Y 相同 + const redPoleZ = z + shelfWidth / 2 - poleSize / 2 // 在前方 const redPoleMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 - }); + }) const redPole = new THREE.Mesh( new THREE.BoxGeometry(poleSize, totalHeight, poleSize), redPoleMaterial - ); - redPole.position.set(redPoleX, redPoleY, redPoleZ); - scene.add(redPole); + ) + redPole.position.set(redPoleX, redPoleY, redPoleZ) + scene.add(redPole) } // 最右侧柱子 if (col === columns - 1) { - const rightPoleX = currentXOffset + unitLength + gapBetweenPlanks - poleSize / 2; - const rightPoleY = baseHeight + totalHeight / 2; - const rightPoleZ = z - shelfWidth / 2 + poleSize / 2; + const rightPoleX = currentXOffset + unitLength + gapBetweenPlanks - poleSize / 2 + const rightPoleY = baseHeight + totalHeight / 2 + const rightPoleZ = z - shelfWidth / 2 + poleSize / 2 // 黑色柱子 const rightPole = new THREE.Mesh( new THREE.BoxGeometry(poleSize, totalHeight, poleSize), poleMaterial - ); - rightPole.position.set(rightPoleX, rightPoleY, rightPoleZ); - scene.add(rightPole); + ) + rightPole.position.set(rightPoleX, rightPoleY, rightPoleZ) + scene.add(rightPole) // 红色柱子 - const redRightPoleZ = z + shelfWidth / 2 - poleSize / 2; + const redRightPoleZ = z + shelfWidth / 2 - poleSize / 2 const redRightPole = new THREE.Mesh( new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 }) - ); - redRightPole.position.set(rightPoleX, rightPoleY, redRightPoleZ); - scene.add(redRightPole); + ) + redRightPole.position.set(rightPoleX, rightPoleY, redRightPoleZ) + scene.add(redRightPole) } } - currentXOffset += unitLength + gapBetweenPlanks; + currentXOffset += unitLength + gapBetweenPlanks } } // 光源 - const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); - scene.add(ambientLight); - const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); - directionalLight.position.set(5, 10, 7); - scene.add(directionalLight); + const ambientLight = new THREE.AmbientLight(0xffffff, 0.5) + scene.add(ambientLight) + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8) + directionalLight.position.set(5, 10, 7) + scene.add(directionalLight) } function createGroundStore() { - const planeGeometry = new THREE.PlaneGeometry(1, 1); + const planeGeometry = new THREE.PlaneGeometry(1, 1) const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"} - }); - const planeMesh = new THREE.Mesh(planeGeometry, material); + }) + const planeMesh = new THREE.Mesh(planeGeometry, material) planeMesh.rotateX(Math.PI / 2) - scene.add(planeMesh); + scene.add(planeMesh) } function initThree() { @@ -837,7 +840,6 @@ function handleFileChange(file) { system.clearLoading() } - reader.readAsArrayBuffer(file) } else if (fileName.endsWith('.obj')) { reader.readAsText(file) @@ -861,11 +863,26 @@ function handleFileChange(file) { system.clearLoading() } - reader.readAsArrayBuffer(file) + + } else if (fileName.endsWith('.glb') || fileName.endsWith('.gltf')) { + reader.onload = () => { + const loader = new GLTFLoader() + const arrayBuffer = reader.result + //@ts-ignore + loader.parseAsync(arrayBuffer, '').then((content) => { + debugger + addGroupToScene(content.scene) + }) + + system.clearLoading() + } } else { alert('不支持的文件类型!') + return } + + reader.readAsArrayBuffer(file) } diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index ecd4081..32b988e 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -10,6 +10,7 @@ import axios from 'axios' import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' export function setUserDataForItem(item: ItemJson, object: Object3DLike) { if (!object.name && item.name) { @@ -494,6 +495,14 @@ export async function loadByUrl(url): Promise { }) } +export async function loadGlbModule(url: string): Promise { + const response = await axios.get(url, { + responseType: 'arraybuffer' + }) + const rt = await new GLTFLoader().parseAsync(response.data, '') + return rt.scene +} + export function loadTexture(url: string) { return new THREE.TextureLoader().loadAsync(url) } @@ -519,3 +528,42 @@ export function load3DModule(arrayBuffer: ArrayBuffer | string, ext: string) { return null } } + +export async function generateMaterialWithTexture(color: number, quality: number, info: any, textureInfos: any) { + let materialClone + + let url = color?.toString() + ' ' + JSON.stringify(textureInfos) + ' ' + quality + ' ' + (info == undefined ? '' : JSON.stringify(info)) + + if (!this.materialsWithTextureMap[url]) { + let meterialTmp = await this.generateMaterial(color, quality, info) + for (let textureInfo of textureInfos) { + if (textureInfo.path != undefined && textureInfo.repeatX != undefined && textureInfo.repeatY != undefined) { + let textureTmp = await this.generateTexture(textureInfo.path, textureInfo.repeatX, textureInfo.repeatY) + + if (textureInfo.rotation != undefined) { + textureTmp.rotation = textureInfo.rotation + } + if (textureInfo.normalMapType != undefined) { + textureTmp.normalMapType = textureInfo.normalMapType + } + + if (textureInfo.type == Model.TextureTypeEnum.NormalMap) { + if (quality != Model.MaterialQualityEnum.Low) { + meterialTmp.normalMap = textureTmp + } + } else if (textureInfo.type == Model.TextureTypeEnum.AlphaMap) { + meterialTmp.alphaMap = textureTmp + } else if (textureInfo.type == Model.TextureTypeEnum.Map) { + meterialTmp.map = textureTmp + } else { + meterialTmp.map = textureTmp + } + meterialTmp.needsUpdate = true + } + } + this.materialsWithTextureMap[url] = meterialTmp + } + materialClone = this.materialsWithTextureMap[url] + + return materialClone +} diff --git a/src/core/manager/WorldModel.ts b/src/core/manager/WorldModel.ts index 35a8bbb..3378f36 100644 --- a/src/core/manager/WorldModel.ts +++ b/src/core/manager/WorldModel.ts @@ -7,6 +7,7 @@ import Gstore from '@/modules/gstore' import Rack from '@/modules/rack' import Pallet from "@/modules/pallet" import Tote from "@/modules/tote" +import Carton from "@/modules/carton" import Ptr from "@/modules/ptr" import Clx from "@/modules/clx" import Charger from "@/modules/charger" @@ -74,6 +75,7 @@ export default class WorldModel { Rack, Pallet, Tote, + Carton, Ptr, Clx, Charger diff --git a/src/example/ExampleUtil.js b/src/example/ExampleUtil.js index 1017118..7c61487 100644 --- a/src/example/ExampleUtil.js +++ b/src/example/ExampleUtil.js @@ -18,7 +18,7 @@ export function buildPointPerformanceData(t, rows, cols) { node.tf[0][1] = 0.01 node.tf[0][2] = col * spacingZ node.tf[1] = [0, 0, 0] - node.tf[2] = [1, 0.01, 1] + node.tf[2] = [1, 0.4, 1] data.set(node.id, node) } } diff --git a/src/example/example1.js b/src/example/example1.js index f699de4..91774a7 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -326,10 +326,10 @@ export default { catalogCode: 'f3', t: 'floor', items: [ { - id: 'tote1', - t: 'pallet', + id: 'carton1', + t: 'carton', v: true, - tf: [[0, 0.1, 0], [0, 0, 0], [1.0, 0.1, 1.0]], + tf: [[0, 0.1, 0], [0, 0, 0], [1.0, 1.0, 1.0]], dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } } ] @@ -340,7 +340,7 @@ export default { }, { catalogCode: '__f2', t: 'floor', - items: buildPointPerformanceData('gstore', 100, 100) + items: buildPointPerformanceData('carton', 100, 100) } ], elevator: [], // 电梯 diff --git a/src/modules/carton/CartonEntity.ts b/src/modules/carton/CartonEntity.ts new file mode 100644 index 0000000..9cf671f --- /dev/null +++ b/src/modules/carton/CartonEntity.ts @@ -0,0 +1,5 @@ +import BaseEntity from '@/core/base/BaseItemEntity.ts' + +export default class PalletEntity extends BaseEntity { + +} diff --git a/src/modules/carton/CartonInteraction.ts b/src/modules/carton/CartonInteraction.ts new file mode 100644 index 0000000..c01c91b --- /dev/null +++ b/src/modules/carton/CartonInteraction.ts @@ -0,0 +1,22 @@ +import BaseInteraction from '@/core/base/BaseInteraction.ts' +import * as THREE from 'three' + +export default class PalletInteraction extends BaseInteraction { + + get isSinglePointMode(): boolean { + return true + } + + constructor(itemTypeName: string) { + super(itemTypeName) + } + + createPointOfItem(item: ItemJson, point: THREE.Vector3): ItemJson { + item = super.createPointOfItem(item, point) + + // 创建一个地堆货架 + item.dt.palletWidth = 1 // 宽度 + item.dt.palletDepth = 1.2 // 深度 + return item + } +} diff --git a/src/modules/carton/CartonPropertySetter.ts b/src/modules/carton/CartonPropertySetter.ts new file mode 100644 index 0000000..4a5cfc9 --- /dev/null +++ b/src/modules/carton/CartonPropertySetter.ts @@ -0,0 +1,20 @@ +import type { PropertySetter } from "@/core/base/PropertyTypes.ts"; +import { basicFieldsSetter } from "@/editor/widgets/property/PropertyPanelConstant.ts"; + +const propertySetter: PropertySetter = { + flatten: { + fields: [ + ...basicFieldsSetter, + { + dataPath: 'dt.palletWidth', label: '托盘宽度', input: 'InputNumber', + inputProps: {}, + }, + { + dataPath: 'dt.palletDepth', label: '托盘深度', input: 'InputNumber', + inputProps: {}, + }, + ], + }, +}; + +export default propertySetter; diff --git a/src/modules/carton/CartonRenderer.ts b/src/modules/carton/CartonRenderer.ts new file mode 100644 index 0000000..c8c6dd8 --- /dev/null +++ b/src/modules/carton/CartonRenderer.ts @@ -0,0 +1,83 @@ +import * as THREE from 'three' +import BaseRenderer from '@/core/base/BaseRenderer.ts' +import Constract from '@/core/Constract.ts' +import InstancePointManager from '@/core/manager/InstancePointManager.ts' +import type { Object3DLike } from '@/types/ModelTypes.ts' +import MODULE_GLB_File from '@/assets/Models/carton.glb?url' +import MODULE_3DS_TEX from '@/assets/Models/carton.jpg?url' +import { load3DModule, loadByUrl, loadGlbModule, loadTexture } from '@/core/ModelUtils.ts' + +/** + * 货架货位渲染器 + */ +export default class PalletRenderer extends BaseRenderer { + static POINT_NAME = 'carton_point' + + /** + * 默认点的高度, 防止和地面重合 + */ + 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 defaultUserData = { + color: 0xc29a70 + } + + cartonGeometry: THREE.BufferGeometry + cartonMaterial: THREE.Material + + init() { + return Promise.all([ + super.init(), + loadGlbModule(MODULE_GLB_File), + loadTexture(MODULE_3DS_TEX) + + ]).then(([_, glbGroup, cartonTexture]) => { + const mesh = glbGroup.children[0] as THREE.Mesh + this.cartonGeometry = mesh.geometry + this.cartonMaterial = new THREE.MeshPhongMaterial({ color: 0xc29a70 }) // mesh.material as THREE.Material + this.cartonGeometry.scale(0.01, 0.01, 0.01) + this.cartonGeometry.rotateX(Math.PI / 2) + + cartonTexture.wrapS = THREE.RepeatWrapping + cartonTexture.wrapT = THREE.RepeatWrapping + cartonTexture.repeat.set(1, 1) + //@ts-ignore + this.cartonMaterial.map = cartonTexture + //@ts-ignore + this.cartonMaterial.color.set(this.defaultUserData.color) + + this.cartonMaterial.needsUpdate = true + }) + } + + createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { + return this.pointManager.createPoint(item) + } + + get pointManager(): InstancePointManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set.') + } + return this.tempViewport.getOrCreatePointManager(this.itemTypeName, () => + // 构建 InstanceMesh 代理对象 + InstancePointManager.create(this.itemTypeName, + this.tempViewport, + this.cartonGeometry, + this.cartonMaterial, + Constract.MAX_PALLET_INSTANCES) + ) + } + + dispose() { + super.dispose() + if (this.cartonGeometry) { + this.cartonGeometry.dispose() + this.cartonGeometry = undefined + } + if (this.cartonMaterial) { + this.cartonMaterial.dispose() + this.cartonMaterial = undefined + } + } +} diff --git a/src/modules/carton/index.ts b/src/modules/carton/index.ts new file mode 100644 index 0000000..f38fc5d --- /dev/null +++ b/src/modules/carton/index.ts @@ -0,0 +1,15 @@ +import { defineModule } from '@/core/manager/ModuleManager.ts' +import CartonRenderer from './CartonRenderer.ts' +import CartonEntity from './CartonEntity.ts' +import CartonInteraction from './CartonInteraction.ts' +import propertySetter from "./CartonPropertySetter.ts"; + +export const ITEM_TYPE_NAME = 'carton' + +export default defineModule({ + name: ITEM_TYPE_NAME, + renderer: new CartonRenderer(ITEM_TYPE_NAME), + interaction: new CartonInteraction(ITEM_TYPE_NAME), + setter: propertySetter, + entity: CartonEntity, +}) diff --git a/src/types/ModelTypes.ts b/src/types/ModelTypes.ts index 3238fc4..8669692 100644 --- a/src/types/ModelTypes.ts +++ b/src/types/ModelTypes.ts @@ -72,3 +72,12 @@ export type Object3DLike = Object3D | LineManageWrap | PointManageWrap * 坐标的范指型, 可以是 THREE.Vector3 或者三元数组 */ export type Vector3Like = THREE.Vector3 | number[] + +/** + * 材质质量枚举 + */ +export const MaterialQualityEnum = { + High: 0, + Middle: 1, + Low: 2, +};