| |
| import * as THREE from 'three'; |
| |
| import SpriteText from 'three-spritetext'; |
| |
| import * as TWEEN from '@tweenjs/tween.js'; |
| import type { |
| Group, |
| Scene, |
| Line, |
| Sprite, |
| Texture, |
| Vector3, |
| Vector2, |
| } from 'three'; |
| import type { GUI } from 'dat.gui'; |
| import type { IPos } from '@/threejs/modules/map/map3d'; |
| |
| import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader'; |
| import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; |
| import { ColladaLoader } from 'three/examples/jsm/loaders/ColladaLoader'; |
| import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; |
| |
| import Radar from '@/threejs/modules/effect/radar'; |
| |
| import type { IBezierPath } from '../map/type'; |
| |
| interface IPosition { |
| x: number; |
| y: number; |
| } |
| |
| class AGV3D { |
| scene: Scene; |
| agv: Group; |
| gui: GUI; |
| |
| depth: number; |
| |
| textInfo: SpriteText; |
| |
| curPosition: IPosition; |
| startPos: IPos; |
| |
| radarMesh: any; |
| |
| constructor(scene: Scene, gui: GUI, startPos: IPos) { |
| this.scene = scene; |
| this.gui = gui; |
| this.depth = 0.5; |
| this.startPos = startPos; |
| this.curPosition = { x: 0, y: 0 }; |
| this.textInfo = this.createSign(); |
| this.agv = this.createAGV(); |
| } |
| |
| createAGV() { |
| const textureLoader = new THREE.TextureLoader(); |
| const agv = new THREE.Group(); |
| |
| const geometry = new THREE.BoxGeometry(1, this.depth, 2); |
| geometry.rotateX(Math.PI * 0.5); |
| |
| const material = new THREE.MeshStandardMaterial({ |
| color: 0xffffff, |
| map: textureLoader.load('/texture/auo.png'), |
| }); |
| |
| const agvcar = new THREE.Mesh(geometry, material); |
| agvcar.material.opacity = 0.8; |
| agvcar.material.transparent = true; |
| agvcar.name = 'agv'; |
| this.gui.add(agv.position, 'x').min(-25).max(0).step(0.01); |
| agv.position.set(this.startPos.x, this.startPos.y, this.depth * 0.5); |
| |
| |
| const radarMesh = this.initRadar(); |
| |
| agv.add(agvcar); |
| this.scene.add(agv); |
| return agv; |
| } |
| |
| initRadar() { |
| const radarData = { |
| position: { |
| x: 0, |
| y: 0, |
| z: 0, |
| }, |
| radius: 3, |
| color: '#ff0062', |
| opacity: 0.5, |
| speed: 1, |
| }; |
| const radarMesh = Radar(radarData); |
| this.radarMesh = radarMesh; |
| return radarMesh; |
| } |
| |
| private createSign() { |
| |
| const textInfo = new SpriteText('AGV小车', 0.01); |
| textInfo.name = 'label'; |
| textInfo.color = 'yellow'; |
| textInfo.borderWidth = 0.2; |
| textInfo.borderRadius = 2; |
| textInfo.backgroundColor = 'rgba(0,122,204,0.5)'; |
| textInfo.padding = 0.1; |
| |
| textInfo.position.z = this.depth * 2.5; |
| |
| return textInfo; |
| } |
| |
| public move(target: IPosition) { |
| const tween = new TWEEN.Tween(this.curPosition); |
| tween.to(target, 10000).onUpdate((object) => { |
| this.agv.position.x = object.x; |
| this.agv.position.y = object.y; |
| }); |
| tween.start(); |
| this.curPosition = target; |
| } |
| |
| public moveByRail() { |
| const railPoint = { |
| className: 'BezierPath', |
| instanceName: 'LM1-LM10', |
| startPos: { |
| instanceName: 'LM1', |
| pos: { |
| x: 1.234, |
| y: 3.379, |
| }, |
| }, |
| endPos: { |
| instanceName: 'LM10', |
| pos: { |
| x: -1.876, |
| y: 0.246, |
| }, |
| }, |
| controlPos1: { |
| x: 1.322, |
| y: 0.242, |
| }, |
| controlPos2: { |
| x: -0.815, |
| y: 0.246, |
| }, |
| }; |
| const { rail, points } = this.mapDegenerateBezierPath(railPoint); |
| this.scene.add(rail); |
| |
| let i = 0; |
| let tween; |
| const agv = this.agv; |
| |
| points.unshift(new THREE.Vector2(agv.position.x, agv.position.y)); |
| |
| let tempAngle = 0; |
| let rotateAngle = 0; |
| function move() { |
| if (i + 1 >= points.length - 1) return; |
| tween = new TWEEN.Tween({ |
| x: points[i].x, |
| y: points[i].y, |
| angle: tempAngle, |
| }); |
| tween |
| .to( |
| { |
| x: points[i + 1].x, |
| y: points[i + 1].y, |
| angle: getAngle(points[i], points[i+1]), |
| }, |
| 100 |
| ) |
| .onUpdate((object) => { |
| agv.position.x = object.x; |
| agv.position.y = object.y; |
| rotateAngle = object.angle - tempAngle; |
| agv.rotateZ(-rotateAngle); |
| tempAngle = object.angle; |
| }) |
| .onComplete(() => { |
| move(); |
| }) |
| .start(); |
| i++; |
| } |
| |
| function getAngle(point1: Vector2, point2: Vector2) { |
| const point3 = new THREE.Vector2(point1.x, point2.y); |
| const v1 = point3.clone().sub(point1); |
| const v2 = point2.clone().sub(point1); |
| const angle = v1.angle() - v2.angle(); |
| return angle; |
| } |
| move(); |
| |
| } |
| |
| private mapDegenerateBezierPath(bezierPath: IBezierPath) { |
| const startPos = bezierPath['startPos']['pos']; |
| const endPos = bezierPath['endPos']['pos']; |
| const controlPos1 = bezierPath['controlPos1']; |
| const controlPos2 = bezierPath['controlPos2']; |
| |
| const curve = new THREE.CubicBezierCurve( |
| new THREE.Vector2(startPos.x, startPos.y), |
| new THREE.Vector2(controlPos1.x, controlPos1.y), |
| new THREE.Vector2(controlPos2.x, controlPos2.y), |
| new THREE.Vector2(endPos.x, endPos.y) |
| ); |
| |
| const points = curve.getPoints(50); |
| const geometry = new THREE.BufferGeometry().setFromPoints(points); |
| const material = new THREE.LineBasicMaterial({ color: 0xffffff }); |
| |
| const curveObject = new THREE.Line(geometry, material); |
| return { rail: curveObject, points }; |
| } |
| } |
| |
| export { AGV3D }; |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验