翼度科技»论坛 编程开发 JavaScript 查看内容

three.js 汽车行驶动画效果

7

主题

7

帖子

21

积分

新手上路

Rank: 1

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

汽车模型加载

使用Promise编写模型的异步加载方法
参数position是汽车初始位置,参数rotation是汽车初始朝向
  1. Car.prototype.loadCar = function (position, rotation) {
  2.     let onProgress = function (xhr) {
  3.     };
  4.     return new Promise((resolve, reject) => {
  5.         if (!this.model) {
  6.             let loader = new THREE.GLTFLoader();
  7.             loader.load(this.url, gltf => {
  8.                 const model = gltf.scene || gltf.scenes[0];
  9.                 model.position.x = position.x;
  10.                 model.position.y = position.y;
  11.                 model.position.z = position.z;
  12.                 model.scale.set(0.25, 0.25, 0.25);
  13.                 model.rotation.set(rotation.x, rotation.y, rotation.z);
  14.                 this.model = model;
  15.                 this.scene.add(model);
  16.                 resolve(model);
  17.             }, onProgress, xhr => {
  18.                 console.error(xhr);
  19.                 console.info('模型 ' + url + ' 加载失败');
  20.                 reject(xhr);
  21.             });
  22.         } else {
  23.             resolve(this.model);
  24.         }
  25.     });
  26. }
复制代码
调用:
第1个参数是汽车初始位置,第2个参数表示汽车初始朝向:西
  1. await car.loadCar(positions[0], car.WEST);
复制代码
汽车行驶

参数start是行驶起点位置,参数end是行驶终点位置,参数speed是速度
this.model是汽车模型,onUpdate事件中,不断更新它的position
this.label是汽车车牌号标签,onUpdate事件中,不断更新它的position
  1. Car.prototype.moveCar = function (start, end, speed) {
  2.     let distance = this.distance(start, end);
  3.     let time = distance / speed * 1000;
  4.     return new Promise((resolve, reject) => {
  5.         this.tween = new TWEEN.Tween({
  6.             x: start.x,
  7.             y: start.y,
  8.             z: start.z
  9.         }).to({
  10.             x: end.x,
  11.             y: end.y,
  12.             z: end.z
  13.         }, time).start().onUpdate(e => {
  14.             if (this.model) {
  15.                 this.model.position.x = e.x;
  16.                 this.model.position.y = e.y;
  17.                 this.model.position.z = e.z;
  18.             }
  19.             if (this.label) {
  20.                 this.label.position.x = e.x;
  21.                 this.label.position.y = e.y + 1.2;
  22.                 this.label.position.z = e.z;
  23.             }
  24.         }).onComplete(() => {
  25.             TWEEN.remove(this.tween);
  26.             resolve();
  27.         });
  28.     });
  29. }
复制代码
汽车转弯

参数start是动画开始时的汽车朝向,end是动画结束时的汽车朝向
  1. Car.prototype.rotateCar = function (start, end) {
  2.     return new Promise((resolve, reject) => {
  3.         this.tween = new TWEEN.Tween({
  4.             x: start.x,
  5.             y: start.y,
  6.             z: start.z
  7.         }).to({
  8.             x: end.x,
  9.             y: end.y,
  10.             z: end.z
  11.         }, 300).start().onUpdate(e => {
  12.             if (this.model) {
  13.                 this.model.rotation.set(e.x, e.y, e.z);
  14.             }
  15.         }).onComplete(() => {
  16.             TWEEN.remove(this.tween);
  17.             resolve();
  18.         });
  19.     });
  20. }
复制代码
汽车行驶多段路线

上述汽车行驶和汽车转弯方法都是异步方法,所以避免了回调地狱,不然下面的多段行驶及转弯就不好写了
  1. Cars.prototype.carLine1 = function () {
  2.     if (!this.run) return;
  3.     let car = new Car(this.scene, this.renderer, './models/车红.glb');
  4.     this.cars.push(car);
  5.     let positions = [
  6.         { x: -121, y: 1.5, z: -16 },
  7.         { x: -130.5, y: 1.5, z: -16 },
  8.         { x: -130.5, y: 1.5, z: 4 },
  9.         { x: -82, y: 1.5, z: 4 },
  10.         { x: -82, y: 1.5, z: 14.7 },
  11.         { x: -18.8, y: 1.5, z: 14.7 },
  12.         { x: -18.8, y: 1.5, z: 70 },
  13.     ];
  14.     let speed = 5;
  15.     setTimeout(async () => {
  16.         await car.loadCar(
  17.             positions[0],
  18.             car.WEST);
  19.         car.showLabel(positions[0], "皖A67893");
  20.         await car.moveCar(
  21.             positions[0],
  22.             positions[1],
  23.             speed);
  24.         await car.rotateCar(
  25.             car.WEST,
  26.             car.SOUTH);
  27.         await car.moveCar(
  28.             positions[1],
  29.             positions[2],
  30.             speed);
  31.         await car.rotateCar(
  32.             car.SOUTH,
  33.             car.EAST);
  34.         await car.moveCar(
  35.             positions[2],
  36.             positions[3],
  37.             speed);
  38.         await car.rotateCar(
  39.             car.EAST,
  40.             car.SOUTH);
  41.         await car.moveCar(
  42.             positions[3],
  43.             positions[4],
  44.             speed);
  45.         await car.rotateCar(
  46.             car.SOUTH,
  47.             car.EAST);
  48.         await car.moveCar(
  49.             positions[4],
  50.             positions[5],
  51.             speed);
  52.         await car.rotateCar(
  53.             car.EAST,
  54.             car.SOUTH);
  55.         await car.moveCar(
  56.             positions[5],
  57.             positions[6],
  58.             speed);
  59.         car.unloadCar();
  60.         this.carLine1(2000);
  61.     }, 5000);
  62. }
  63. Cars.prototype.carLine2 = function () {
  64.     if (!this.run) return;
  65.     let car = new Car(this.scene, this.renderer, './models/车蓝.glb');
  66.     this.cars.push(car);
  67.     let positions = [
  68.         { x: -5, y: 1.5, z: 70 },
  69.         { x: -5, y: 1.5, z: 14.7 },
  70.         { x: 70, y: 1.5, z: 14.7 }
  71.     ];
  72.     let speed = 5;
  73.     setTimeout(async () => {
  74.         await car.loadCar(
  75.             positions[0],
  76.             car.NORTH);
  77.         car.showLabel(positions[0], "皖AD887U");
  78.         await car.moveCar(
  79.             positions[0],
  80.             positions[1],
  81.             speed);
  82.         await car.rotateCar(
  83.             car.NORTH,
  84.             car.EAST);
  85.         await car.moveCar(
  86.             positions[1],
  87.             positions[2],
  88.             speed);
  89.         car.unloadCar();
  90.         this.carLine2(3000);
  91.     }, 6000);
  92. }
复制代码
汽车行驶多段路线改进

上述汽车行驶多段路线的代码可以改进:
  1. // 汽车朝向
  2. let EAST = { x: 0, y: 1.5707963, z: 0 };
  3. let SOUTH = { x: 0, y: 0, z: 0 };
  4. let WEST = { x: 0, y: -1.5707963, z: 0 };
  5. let NORTH = { x: 0, y: 3.1415926, z: 0 };
  6. Cars.prototype.carLine1 = function () {
  7.     if (!this.run) return;
  8.     let car = new Car(this.scene, this.renderer, './models/车红.glb');
  9.     this.cars.push(car);
  10.     let positions = [
  11.         { x: -121, y: 1.5, z: -16 },
  12.         { x: -130.5, y: 1.5, z: -16 },
  13.         [WEST, SOUTH],
  14.         { x: -130.5, y: 1.5, z: 4 },
  15.         [SOUTH, EAST],
  16.         { x: -82, y: 1.5, z: 4 },
  17.         [EAST, SOUTH],
  18.         { x: -82, y: 1.5, z: 14.7 },
  19.         [SOUTH, EAST],
  20.         { x: -18.8, y: 1.5, z: 14.7 },
  21.         [EAST, SOUTH],
  22.         { x: -18.8, y: 1.5, z: 70 },
  23.     ];
  24.     let speed = 5;
  25.     setTimeout(async () => {
  26.         await car.loadCar(
  27.             positions[0],
  28.             WEST);
  29.         car.showLabel(positions[0], "皖A67893");
  30.         for (let i = 1; i < positions.length; i++) {
  31.             if (positions[i].length) {
  32.                 await car.rotateCar(positions[i][0], positions[i][1]);
  33.             } else {
  34.                 let start = positions[i - 1].length ? positions[i - 2] : positions[i - 1];
  35.                 await car.moveCar(start, positions[i], speed);
  36.             }
  37.         }
  38.         car.unloadCar();
  39.         this.carLine1(2000);
  40.     }, 5000);
  41. }
复制代码
调用
  1. let cars = new Cars(app.scene, app.renderer);
  2. cars.carLine1();
  3. cars.carLine2();
复制代码
显示车牌号
  1. Car.prototype.showLabel = function (position, text) {
  2.     let canvasDraw = new CanvasDraw();
  3.     let canvasTexture = canvasDraw.drawCarLabel(THREE, this.renderer, text, '#006688'); //标签
  4.     let spriteMaterial = new THREE.SpriteMaterial({
  5.         map: canvasTexture,
  6.         color: 0xffffff,
  7.         depthTest: false,
  8.         side: THREE.DoubleSide,
  9.         sizeAttenuation: false,
  10.         transparent: true,
  11.         opacity: 0.8
  12.     });
  13.     let sprite = new THREE.Sprite(spriteMaterial);
  14.     sprite.scale.set(0.2, 0.1, 0.2)
  15.     sprite.position.x = position.x;
  16.     sprite.position.y = position.y + 1.2;
  17.     sprite.position.z = position.z;
  18.     this.label = sprite;
  19.     this.scene.add(sprite);
  20.     return sprite;
  21. }
复制代码
完整代码

car.js
  1. // 汽车
  2. let Car = (function () {
  3.     function Car(scene, renderer, url) {
  4.         this.scene = scene;
  5.         this.renderer = renderer;
  6.         this.url = url;
  7.         this.clock = new THREE.Clock();
  8.     }
  9.     Car.prototype.loadCar = function (position, rotation) {
  10.         let onProgress = function (xhr) {
  11.         };
  12.         return new Promise((resolve, reject) => {
  13.             if (!this.model) {
  14.                 let loader = new THREE.GLTFLoader();
  15.                 loader.load(this.url, gltf => {
  16.                     const model = gltf.scene || gltf.scenes[0];
  17.                     model.position.x = position.x;
  18.                     model.position.y = position.y;
  19.                     model.position.z = position.z;
  20.                     model.scale.set(0.25, 0.25, 0.25);
  21.                     model.rotation.set(rotation.x, rotation.y, rotation.z);
  22.                     this.model = model;
  23.                     this.scene.add(model);
  24.                     resolve(model);
  25.                 }, onProgress, xhr => {
  26.                     console.error(xhr);
  27.                     console.info('模型 ' + url + ' 加载失败');
  28.                     reject(xhr);
  29.                 });
  30.             } else {
  31.                 resolve(this.model);
  32.             }
  33.         });
  34.     }
  35.     Car.prototype.unloadCar = function () {
  36.         this.stopTween();
  37.         this.removeModel();
  38.         this.removeLabel();
  39.     }
  40.     Car.prototype.stopTween = function () {
  41.         if (this.tween) {
  42.             TWEEN.remove(this.tween);
  43.         } else {
  44.             setTimeout(() => {
  45.                 this.stopTween();
  46.             }, 100);
  47.         }
  48.     }
  49.     Car.prototype.removeModel = function () {
  50.         if (this.model) {
  51.             this.scene.remove(this.model);
  52.         } else {
  53.             setTimeout(() => {
  54.                 this.removeModel();
  55.             }, 100);
  56.         }
  57.     }
  58.     Car.prototype.removeLabel = function () {
  59.         if (this.label) {
  60.             this.scene.remove(this.label);
  61.         } else {
  62.             setTimeout(() => {
  63.                 this.removeLabel();
  64.             }, 100);
  65.         }
  66.     }
  67.     Car.prototype.moveCar = function (start, end, speed) {
  68.         let distance = this.distance(start, end);
  69.         let time = distance / speed * 1000;
  70.         return new Promise((resolve, reject) => {
  71.             this.tween = new TWEEN.Tween({
  72.                 x: start.x,
  73.                 y: start.y,
  74.                 z: start.z
  75.             }).to({
  76.                 x: end.x,
  77.                 y: end.y,
  78.                 z: end.z
  79.             }, time).start().onUpdate(e => {
  80.                 if (this.model) {
  81.                     this.model.position.x = e.x;
  82.                     this.model.position.y = e.y;
  83.                     this.model.position.z = e.z;
  84.                 }
  85.                 if (this.label) {
  86.                     this.label.position.x = e.x;
  87.                     this.label.position.y = e.y + 1.2;
  88.                     this.label.position.z = e.z;
  89.                 }
  90.             }).onComplete(() => {
  91.                 TWEEN.remove(this.tween);
  92.                 resolve();
  93.             });
  94.         });
  95.     }
  96.     Car.prototype.rotateCar = function (start, end) {
  97.         return new Promise((resolve, reject) => {
  98.             this.tween = new TWEEN.Tween({
  99.                 x: start.x,
  100.                 y: start.y,
  101.                 z: start.z
  102.             }).to({
  103.                 x: end.x,
  104.                 y: end.y,
  105.                 z: end.z
  106.             }, 300).start().onUpdate(e => {
  107.                 if (this.model) {
  108.                     this.model.rotation.set(e.x, e.y, e.z);
  109.                 }
  110.             }).onComplete(() => {
  111.                 TWEEN.remove(this.tween);
  112.                 resolve();
  113.             });
  114.         });
  115.     }
  116.     Car.prototype.showLabel = function (position, text) {
  117.         let canvasDraw = new CanvasDraw();
  118.         let canvasTexture = canvasDraw.drawCarLabel(THREE, this.renderer, text, '#006688'); //标签
  119.         let spriteMaterial = new THREE.SpriteMaterial({
  120.             map: canvasTexture,
  121.             color: 0xffffff,
  122.             depthTest: false,
  123.             side: THREE.DoubleSide,
  124.             sizeAttenuation: false,
  125.             transparent: true,
  126.             opacity: 0.8
  127.         });
  128.         let sprite = new THREE.Sprite(spriteMaterial);
  129.         sprite.scale.set(0.2, 0.1, 0.2)
  130.         sprite.position.x = position.x;
  131.         sprite.position.y = position.y + 1.2;
  132.         sprite.position.z = position.z;
  133.         this.label = sprite;
  134.         this.scene.add(sprite);
  135.         return sprite;
  136.     }
  137.     Car.prototype.distance = function (p1, p2) {
  138.         return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2) + Math.pow(p1.z - p2.z, 2));
  139.     }
  140.     return Car;
  141. })();
复制代码
cars.js
  1. // 多个车辆
  2. let Cars = (function () {
  3.     // 汽车朝向
  4.     let EAST = { x: 0, y: 1.5707963, z: 0 };
  5.     let SOUTH = { x: 0, y: 0, z: 0 };
  6.     let WEST = { x: 0, y: -1.5707963, z: 0 };
  7.     let NORTH = { x: 0, y: 3.1415926, z: 0 };
  8.     function Cars(scene, renderer) {
  9.         this.scene = scene;
  10.         this.renderer = renderer;
  11.         this.cars = [];
  12.         this.run = true;
  13.     }
  14.     Cars.prototype.carLine1 = function () {
  15.         if (!this.run) return;
  16.         let car = new Car(this.scene, this.renderer, './models/车红.glb');
  17.         this.cars.push(car);
  18.         let positions = [
  19.             { x: -121, y: 1.5, z: -16 },
  20.             { x: -130.5, y: 1.5, z: -16 },
  21.             [WEST, SOUTH],
  22.             { x: -130.5, y: 1.5, z: 4 },
  23.             [SOUTH, EAST],
  24.             { x: -82, y: 1.5, z: 4 },
  25.             [EAST, SOUTH],
  26.             { x: -82, y: 1.5, z: 14.7 },
  27.             [SOUTH, EAST],
  28.             { x: -18.8, y: 1.5, z: 14.7 },
  29.             [EAST, SOUTH],
  30.             { x: -18.8, y: 1.5, z: 70 },
  31.         ];
  32.         let speed = 5;
  33.         setTimeout(async () => {
  34.             await car.loadCar(
  35.                 positions[0],
  36.                 WEST);
  37.             car.showLabel(positions[0], "皖A67893");
  38.             for (let i = 1; i < positions.length; i++) {
  39.                 if (positions[i].length) {
  40.                     await car.rotateCar(positions[i][0], positions[i][1]);
  41.                 } else {
  42.                     let start = positions[i - 1].length ? positions[i - 2] : positions[i - 1];
  43.                     await car.moveCar(start, positions[i], speed);
  44.                 }
  45.             }
  46.             car.unloadCar();
  47.             this.carLine1(2000);
  48.         }, 5000);
  49.     }
  50.     Cars.prototype.carLine2 = function () {
  51.         if (!this.run) return;
  52.         let car = new Car(this.scene, this.renderer, './models/车蓝.glb');
  53.         this.cars.push(car);
  54.         let positions = [
  55.             { x: -5, y: 1.5, z: 70 },
  56.             { x: -5, y: 1.5, z: 14.7 },
  57.             { x: 70, y: 1.5, z: 14.7 }
  58.         ];
  59.         let speed = 5;
  60.         setTimeout(async () => {
  61.             await car.loadCar(
  62.                 positions[0],
  63.                 NORTH);
  64.             car.showLabel(positions[0], "皖AD887U");
  65.             await car.moveCar(
  66.                 positions[0],
  67.                 positions[1],
  68.                 speed);
  69.             await car.rotateCar(
  70.                 NORTH,
  71.                 EAST);
  72.             await car.moveCar(
  73.                 positions[1],
  74.                 positions[2],
  75.                 speed);
  76.             car.unloadCar();
  77.             this.carLine2(3000);
  78.         }, 6000);
  79.     }
  80.     Cars.prototype.clear = function () {
  81.         this.run = false;
  82.         this.cars.forEach(car => {
  83.             car.unloadCar();
  84.         });
  85.     }
  86.     return Cars;
  87. })();
复制代码
调用
  1. // 显示汽车
  2. function showCars() {
  3.         cars = new Cars(app.scene, app.renderer);
  4.         cars.carLine1();
  5.         cars.carLine2();
  6. }
  7. // 清除汽车
  8. function clearCars() {
  9.         cars.clear();
  10. }
  11. // 显示汽车
  12. showCars();
复制代码
总结


  • 解耦:依赖的scene, renderer参数是通过构造函数传到Car和Cars对象中的
  • 汽车行驶和转向等方法都是异步方法,可以避免回调地狱,这样汽车多段行驶的代码会写的比较清晰
  • 在实现并完善功能的过程中不断重构:回调地狱的实现方式-->调用moveCar和rotateCar时直接传递坐标,很多坐标及转向数据和方法调用掺和在一起,看着眼花-->坐标和转向数据和方法调用逻辑分离,看着很清晰
运行效果



来源:https://www.cnblogs.com/s0611163/p/17879849.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具