three.js 汽车行驶效果

  • three.js 汽车行驶效果已关闭评论
  • 73 次浏览
  • A+
所属分类:Web前端
摘要

实现原理是使用TWEEN.Tween实现动画效果使用Promise编写模型的异步加载方法调用:
第1个参数是初始位置,第2个参数表示汽车朝向西

实现原理是使用TWEEN.Tween实现动画效果

实现

汽车模型加载

使用Promise编写模型的异步加载方法

Car.prototype.loadCar = function (position, rotation) {     let onProgress = function (xhr) {      };      return new Promise((resolve, reject) => {         if (!this.model) {             let loader = new THREE.GLTFLoader();             loader.load(this.url, gltf => {                 const model = gltf.scene || gltf.scenes[0];                  model.position.x = position.x;                 model.position.y = position.y;                 model.position.z = position.z;                  model.scale.set(0.25, 0.25, 0.25);                  model.rotation.set(rotation.x, rotation.y, rotation.z);                  this.model = model;                 this.scene.add(model);                  resolve(model);              }, onProgress, xhr => {                 console.error(xhr);                 console.info('模型 ' + url + ' 加载失败');                 reject(xhr);             });         } else {             resolve(this.model);         }     }); } 

调用:
第1个参数是初始位置,第2个参数表示汽车朝向西

await car.loadCar(positions[0], car.WEST); 

汽车行驶

参数start是行驶起点位置,参数end是行驶终点位置,参数speed是速度
this.model是汽车模型,onUpdate事件中,不断更新它的position
this.label是汽车车牌号标签,onUpdate事件中,不断更新它的position

Car.prototype.moveCar = function (start, end, speed) {     let distance = this.distance(start, end);     let time = distance / speed * 1000;      return new Promise((resolve, reject) => {         this.tween = new TWEEN.Tween({             x: start.x,             y: start.y,             z: start.z         }).to({             x: end.x,             y: end.y,             z: end.z         }, time).start().onUpdate(e => {             if (this.model) {                 this.model.position.x = e.x;                 this.model.position.y = e.y;                 this.model.position.z = e.z;             }             if (this.label) {                 this.label.position.x = e.x;                 this.label.position.y = e.y + 1.2;                 this.label.position.z = e.z;             }         }).onComplete(() => {             TWEEN.remove(this.tween);             resolve();         });     }); } 

汽车转弯

参数start是动画开始时的汽车朝向,end是动画结束时的汽车朝向

Car.prototype.rotateCar = function (start, end) {     return new Promise((resolve, reject) => {         this.tween = new TWEEN.Tween({             x: start.x,             y: start.y,             z: start.z         }).to({             x: end.x,             y: end.y,             z: end.z         }, 300).start().onUpdate(e => {             if (this.model) {                 this.model.rotation.set(e.x, e.y, e.z);             }         }).onComplete(() => {             TWEEN.remove(this.tween);             resolve();         });     }); } 

汽车行驶一段路线

上述汽车行驶和汽车转弯方法都是异步方法,所以避免了回调地狱,不然下面的多段行驶及转弯就不好写了

Cars.prototype.carLine1 = function () {     if (!this.run) return;      let car = new Car(this.scene, this.renderer, './models/车红.glb');     this.cars.push(car);      let positions = [         { x: -121, y: 1.5, z: -16 },         { x: -130.5, y: 1.5, z: -16 },         { x: -130.5, y: 1.5, z: 4 },         { x: -82, y: 1.5, z: 4 },         { x: -82, y: 1.5, z: 14.7 },         { x: -18.8, y: 1.5, z: 14.7 },         { x: -18.8, y: 1.5, z: 70 },     ];      let speed = 5;      setTimeout(async () => {         await car.loadCar(             positions[0],             car.WEST);          car.showLabel(positions[0], "皖A67893");          await car.moveCar(             positions[0],             positions[1],             speed);          await car.rotateCar(             car.WEST,             car.SOUTH);          await car.moveCar(             positions[1],             positions[2],             speed);          await car.rotateCar(             car.SOUTH,             car.EAST);          await car.moveCar(             positions[2],             positions[3],             speed);          await car.rotateCar(             car.EAST,             car.SOUTH);          await car.moveCar(             positions[3],             positions[4],             speed);          await car.rotateCar(             car.SOUTH,             car.EAST);          await car.moveCar(             positions[4],             positions[5],             speed);          await car.rotateCar(             car.EAST,             car.SOUTH);          await car.moveCar(             positions[5],             positions[6],             speed);          car.unloadCar();          this.carLine1(2000);     }, 5000); }  Cars.prototype.carLine2 = function () {     if (!this.run) return;      let car = new Car(this.scene, this.renderer, './models/车蓝.glb');     this.cars.push(car);      let positions = [         { x: -5, y: 1.5, z: 70 },         { x: -5, y: 1.5, z: 14.7 },         { x: 70, y: 1.5, z: 14.7 }     ];      let speed = 5;      setTimeout(async () => {         await car.loadCar(             positions[0],             car.NORTH);          car.showLabel(positions[0], "皖AD887U");          await car.moveCar(             positions[0],             positions[1],             speed);          await car.rotateCar(             car.NORTH,             car.EAST);          await car.moveCar(             positions[1],             positions[2],             speed);          car.unloadCar();          this.carLine2(3000);     }, 6000); } 

调用

let cars = new Cars(app.scene, app.renderer); cars.carLine1(); cars.carLine2(); 

显示车牌号

Car.prototype.showLabel = function (position, text) {     let canvasDraw = new CanvasDraw();     let canvasTexture = canvasDraw.drawCarLabel(THREE, this.renderer, text, '#006688'); //标签      let spriteMaterial = new THREE.SpriteMaterial({         map: canvasTexture,         color: 0xffffff,         depthTest: false,         side: THREE.DoubleSide,         sizeAttenuation: false,         transparent: true,         opacity: 0.8     });      let sprite = new THREE.Sprite(spriteMaterial);     sprite.scale.set(0.2, 0.1, 0.2)     sprite.position.x = position.x;     sprite.position.y = position.y + 1.2;     sprite.position.z = position.z;      this.label = sprite;     this.scene.add(sprite);      return sprite; } 

完整代码

car.js

// 汽车  let Car = (function () {      // 汽车朝向     Car.prototype.EAST = { x: 0, y: 1.5707963, z: 0 };     Car.prototype.SOUTH = { x: 0, y: 0, z: 0 };     Car.prototype.WEST = { x: 0, y: -1.5707963, z: 0 };     Car.prototype.NORTH = { x: 0, y: 3.1415926, z: 0 };      function Car(scene, renderer, url) {         this.scene = scene;         this.renderer = renderer;         this.url = url;         this.clock = new THREE.Clock();     }      Car.prototype.loadCar = function (position, rotation) {         let onProgress = function (xhr) {          };          return new Promise((resolve, reject) => {             if (!this.model) {                 let loader = new THREE.GLTFLoader();                 loader.load(this.url, gltf => {                     const model = gltf.scene || gltf.scenes[0];                      model.position.x = position.x;                     model.position.y = position.y;                     model.position.z = position.z;                      model.scale.set(0.25, 0.25, 0.25);                      model.rotation.set(rotation.x, rotation.y, rotation.z);                      this.model = model;                     this.scene.add(model);                      resolve(model);                  }, onProgress, xhr => {                     console.error(xhr);                     console.info('模型 ' + url + ' 加载失败');                     reject(xhr);                 });             } else {                 resolve(this.model);             }         });     }      Car.prototype.unloadCar = function () {         this.stopTween();         this.removeModel();         this.removeLabel();     }      Car.prototype.stopTween = function () {         if (this.tween) {             TWEEN.remove(this.tween);         } else {             setTimeout(() => {                 this.stopTween();             }, 100);         }     }      Car.prototype.removeModel = function () {         if (this.model) {             this.scene.remove(this.model);         } else {             setTimeout(() => {                 this.removeModel();             }, 100);         }     }      Car.prototype.removeLabel = function () {         if (this.label) {             this.scene.remove(this.label);         } else {             setTimeout(() => {                 this.removeLabel();             }, 100);         }     }      Car.prototype.moveCar = function (start, end, speed) {         let distance = this.distance(start, end);         let time = distance / speed * 1000;          return new Promise((resolve, reject) => {             this.tween = new TWEEN.Tween({                 x: start.x,                 y: start.y,                 z: start.z             }).to({                 x: end.x,                 y: end.y,                 z: end.z             }, time).start().onUpdate(e => {                 if (this.model) {                     this.model.position.x = e.x;                     this.model.position.y = e.y;                     this.model.position.z = e.z;                 }                 if (this.label) {                     this.label.position.x = e.x;                     this.label.position.y = e.y + 1.2;                     this.label.position.z = e.z;                 }             }).onComplete(() => {                 TWEEN.remove(this.tween);                 resolve();             });         });     }      Car.prototype.rotateCar = function (start, end) {         return new Promise((resolve, reject) => {             this.tween = new TWEEN.Tween({                 x: start.x,                 y: start.y,                 z: start.z             }).to({                 x: end.x,                 y: end.y,                 z: end.z             }, 300).start().onUpdate(e => {                 if (this.model) {                     this.model.rotation.set(e.x, e.y, e.z);                 }             }).onComplete(() => {                 TWEEN.remove(this.tween);                 resolve();             });         });     }      Car.prototype.showLabel = function (position, text) {         let canvasDraw = new CanvasDraw();         let canvasTexture = canvasDraw.drawCarLabel(THREE, this.renderer, text, '#006688'); //标签          let spriteMaterial = new THREE.SpriteMaterial({             map: canvasTexture,             color: 0xffffff,             depthTest: false,             side: THREE.DoubleSide,             sizeAttenuation: false,             transparent: true,             opacity: 0.8         });          let sprite = new THREE.Sprite(spriteMaterial);         sprite.scale.set(0.2, 0.1, 0.2)         sprite.position.x = position.x;         sprite.position.y = position.y + 1.2;         sprite.position.z = position.z;          this.label = sprite;         this.scene.add(sprite);          return sprite;     }      Car.prototype.distance = function (p1, p2) {         return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2) + Math.pow(p1.z - p2.z, 2));     }      return Car;  })(); 

cars.js

// 多个车辆  let Cars = (function () {      function Cars(scene, renderer) {         this.scene = scene;         this.renderer = renderer;         this.cars = [];         this.run = true;     }      Cars.prototype.carLine1 = function () {         if (!this.run) return;          let car = new Car(this.scene, this.renderer, './models/车红.glb');         this.cars.push(car);          let positions = [             { x: -121, y: 1.5, z: -16 },             { x: -130.5, y: 1.5, z: -16 },             { x: -130.5, y: 1.5, z: 4 },             { x: -82, y: 1.5, z: 4 },             { x: -82, y: 1.5, z: 14.7 },             { x: -18.8, y: 1.5, z: 14.7 },             { x: -18.8, y: 1.5, z: 70 },         ];          let speed = 5;          setTimeout(async () => {             await car.loadCar(                 positions[0],                 car.WEST);              car.showLabel(positions[0], "皖A67893");              await car.moveCar(                 positions[0],                 positions[1],                 speed);              await car.rotateCar(                 car.WEST,                 car.SOUTH);              await car.moveCar(                 positions[1],                 positions[2],                 speed);              await car.rotateCar(                 car.SOUTH,                 car.EAST);              await car.moveCar(                 positions[2],                 positions[3],                 speed);              await car.rotateCar(                 car.EAST,                 car.SOUTH);              await car.moveCar(                 positions[3],                 positions[4],                 speed);              await car.rotateCar(                 car.SOUTH,                 car.EAST);              await car.moveCar(                 positions[4],                 positions[5],                 speed);              await car.rotateCar(                 car.EAST,                 car.SOUTH);              await car.moveCar(                 positions[5],                 positions[6],                 speed);              car.unloadCar();              this.carLine1(2000);         }, 5000);     }      Cars.prototype.carLine2 = function () {         if (!this.run) return;          let car = new Car(this.scene, this.renderer, './models/车蓝.glb');         this.cars.push(car);          let positions = [             { x: -5, y: 1.5, z: 70 },             { x: -5, y: 1.5, z: 14.7 },             { x: 70, y: 1.5, z: 14.7 }         ];          let speed = 5;          setTimeout(async () => {             await car.loadCar(                 positions[0],                 car.NORTH);              car.showLabel(positions[0], "皖AD887U");              await car.moveCar(                 positions[0],                 positions[1],                 speed);              await car.rotateCar(                 car.NORTH,                 car.EAST);              await car.moveCar(                 positions[1],                 positions[2],                 speed);              car.unloadCar();              this.carLine2(3000);         }, 6000);     }      Cars.prototype.clear = function () {         this.run = false;         this.cars.forEach(car => {             car.unloadCar();         });     }      return Cars;  })(); 

调用

// 显示汽车 function showCars() { 	cars = new Cars(app.scene, app.renderer); 	cars.carLine1(); 	cars.carLine2(); }  // 清除汽车 function clearCars() { 	cars.clear(); }  // 显示汽车 showCars(); 

总结

  1. 解耦:依赖的scene, renderer参数是通过构造函数传到Car和Cars对象中的
  2. 汽车行驶和转向等方法都是异步方法,可以避免回调地狱,这样汽车多段行驶的代码会写的比较清晰

改进

运行效果

three.js 汽车行驶效果