当前位置: 首页 > 工具软件 > Sammy.js > 使用案例 >

【ThreeJs】moving model

江阳羽
2023-12-01

沿着样条曲线运动的汽车模型,可调整相机视角

<template></template>

<script setup>
// https://threejs.org/docs/index.html#manual/zh/introduction/Loading-3D-models
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

/************************* init *************************/
const scene = new THREE.Scene();
// PerspectiveCamera 透视摄像机
// 参数:视野角度(FOV),长宽比(aspect ratio),近截面(near)和远截面(far)只能看到两个值之间的物体
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0xffffff)); // 背景白色
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

/************************** 基础配置 *************************/
// 坐标系。红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴
const axes = new THREE.AxesHelper(20);
scene.add(axes);

// 背景色,边界雾化
scene.background = new THREE.Color(0xa0a0a0); // 灰
// scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);

// 屏幕自适应
window.addEventListener("resize", onWindowResize, false);
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

// 控制相机视角
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
// 调用放在最后
function animate() {
  requestAnimationFrame(animate);

  // required if controls.enableDamping or controls.autoRotate are set to true
  controls.update();
  moveOnCurve(); // 刷新

  renderer.render(scene, camera);
}

/************************ 模型 ***********************/
// 加载纹理
const texture = new THREE.TextureLoader().load(
  "model/textures/930_wunderbaum_baseColor.png"
);
const material = new THREE.MeshBasicMaterial({ map: texture }); // MeshBasicMaterial:不受光照影响;MeshLambertMaterial:受光照影响

// 加载模型
const loader = new GLTFLoader();
loader.load(
  "model/scene.gltf",
  (gltf) => {
    scene.add(gltf.scene);
    model = gltf.scene;

    // 加贴图
    gltf.scene.traverse(function (object) {
      if (object.type === "Mesh") {
        console.log(object.material); //控制台查看 mesh 材质
        object.material = material;
      }
    });
    renderer.render(scene, camera);
  },
  undefined,
  (error) => {
    console.error(error);
  }
);

/************************* 动起来! *************************/
let curve = null,
  model = null;
// 路径曲线,样条曲线
(function makeCurve() {
  //Create a closed wavey loop
  curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(25, 0, 0),
    new THREE.Vector3(0, 0, 25),
  ]);
  curve.curveType = "catmullrom";
  curve.closed = true; //设置是否闭环
  curve.tension = 0.5; //设置线的张力,0为无弧度折线

  // 为曲线添加材质在场景中显示出来,不显示也不会影响运动轨迹,相当于一个Helper
  const points = curve.getPoints(50);
  const geometry = new THREE.BufferGeometry().setFromPoints(points);
  const material = new THREE.LineBasicMaterial({ color: 0x000000 });

  // Create the final object to add to the scene
  const curveObject = new THREE.Line(geometry, material);
  scene.add(curveObject);
})();

let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
const velocity = 0.001; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
// 物体沿线移动方法
function moveOnCurve() {
  if (curve == null || model == null) {
    console.log("Loading", curve, model);
  } else {
    if (progress <= 1 - velocity) {
      const point = curve.getPointAt(progress); //获取样条曲线指定点坐标
      const pointBox = curve.getPointAt(progress + velocity); //获取样条曲线指定点坐标

      if (point && pointBox) {
        model.position.set(point.x, point.y, point.z);
        // .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
        model.lookAt(pointBox.x, pointBox.y, pointBox.z); //因为这个模型加载进来默认面部是正对Z轴负方向的,所以直接lookAt会导致出现倒着跑的现象,这里用重新设置朝向的方法来解决。

        const offsetAngle = 0; //目标移动时的朝向偏移

        // 以下代码在多段路径时可重复执行
        const mtx = new THREE.Matrix4(); //创建一个4维矩阵
        // mtx.lookAt(model.position, pointBox, model.up); // 反向
        mtx.multiply(
          new THREE.Matrix4().makeRotationFromEuler(
            new THREE.Euler(0, offsetAngle, 0)
          )
        );
        var toRot = new THREE.Quaternion().setFromRotationMatrix(mtx); //计算出需要进行旋转的四元数值
        model.quaternion.slerp(toRot, 0.2);
      }

      progress += velocity;
    } else {
      progress = 0;
    }
  }
}

animate();
</script>
 类似资料: