沿着样条曲线运动的汽车模型,可调整相机视角
<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>