// three.js
import * as THREE from 'three';
import SpriteText from 'three-spritetext';
// 引入tween
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;
// agv小车的高度
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();
//因为需要贴图的原因,所以设置长宽高的时候,先是按照y轴向上的坐标系进行设置,然后进行旋转,转换成z轴向上的坐标系
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);
// agv.position.set(0, 0, this.depth * 0.5);
// 初始化雷达特效
const radarMesh = this.initRadar();
// agv.add(agvcar, this.textInfo, radarMesh);
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;
// textInfo.material.visible = false
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 });
// Create the final object to add to the scene
const curveObject = new THREE.Line(geometry, material);
return { rail: curveObject, points };
}
}
export { AGV3D };