经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » three.js » 查看文章
three.js 汽车行驶动画效果
来源:cnblogs  作者:0611163  时间:2023/12/8 11:48:44  对本文有异议

实现原理是使用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();

总结

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

运行效果

原文链接:https://www.cnblogs.com/s0611163/p/17879849.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号