目录
- 效果描述
- 实现流程
- 基本流程
- 工程文件
- 搭建场景
- 添加模型和播放动画
- 添加路径和模型移动
- 完整代码和实现效果
效果描述
在场景中放置多个轨道路线,并在轨道上放置模型,在模型动画播放的同时让模型沿着预定轨迹移动。
实现流程
基本流程
1、搭建场景
2、添加模型和播放动画
3、添加路径和模型移动
工程文件
工程文件结构如下图:
static:存放静态资源文件
three.js-master:为官网下载的代码包,包含所有需要用到的资源包,链接:https://github.com/mrdoob/three.js/archive/master.zip
index.html:页面代码
模型使用的是官方示例中的Soldier模型,文件位置:three.js-master\examples\models\gltf\Soldier.glb
为了方便操作我们将文件拷出来放在上图static\3dmod\gltf文件夹下,static与three.js-master同级
index.html单页代码组成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "./three.js-master/build/three.module.js"
}
}
</script>
<script type="module">
// 下文JS代码位置
// ...
</script>
</body>
</html>
参照官网例子:https://threejs.org/examples/#webgl_animation_skinning_blending中的场景和模型
搭建场景
搭建场景环境
import * as THREE from "three";
import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
let scene, camera, renderer;
// 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
// 灯光需要开启“引起阴影”:light.castShadow = true;
// 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
// position and point the camera to the center of the scene
camera.position.set(5, 5, 5);
camera.lookAt(scene.position);
// 增加坐标系红色代表 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);
// 半球形光源
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 10, 0);
scene.add(hemiLight);
// 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
scene.add(hemiLighthelper);
// 地面
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
mesh.rotation.x = - Math.PI / 2;
mesh.receiveShadow = true;
scene.add(mesh);
// 平行光
const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
directionalLight.position.set(0, 5, 5);
scene.add(directionalLight);
// 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
scene.add(directionalLightHelper);
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
}
// 渲染
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
添加模型和播放动画
导入模型,在《Three.js学习四——模型导入》中有相对详细的介绍。
动画实现基本流程:
1、使用加载器导入模型后,在加载成功后调用的函数中设置动画
2、新建一个AnimationMixer(动画混合器)
3、获取AnimationClip(动画)实例列表
4、设置播放动画,并在每一帧中更新mixer
因为需要用到多个模型,所以将不同模型的配置单独拿出来写
import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";
import * as SkeletonUtils from "./three.js-master/examples/jsm/utils/SkeletonUtils.js";
let clock = new THREE.Clock();
let mods = [];
let mixers = [];
let numLoadedModels = 0;
let MODELS = [
{ name: "Soldier" },
];
// 定义要放置在场景中的模型实例、它们的位置、比例以及必须播放的动画。
let UNITS = [
{
modelName: "Soldier", // 使用文件models/gltf/Soldier.glb中的3D模型
meshName: "Scene", // 要设置动画的网格的名称,这个值在模型加载后的对象中找到
position: { x: 0, y: 0, z: 0 }, // 在场景中放置单元的位置
scale: 1, // 单位的缩放比例。1.0表示:使用原始尺寸,0.1表示“小10倍”等。
animationName: "Run", // 要运行的动画的名称
},
{
modelName: "Soldier", // 使用文件models/gltf/Soldier.glb中的3D模型
meshName: "Scene", // 要设置动画的网格的名称,这个值在模型加载后的对象中找到
position: { x: 0, y: 0, z: 0 }, // 在场景中放置单元的位置
scale: 1, // 单位的缩放比例。1.0表示:使用原始尺寸,0.1表示“小10倍”等。
animationName: "Walk", // 要运行的动画的名称
},
{
modelName: "Soldier", // 使用文件models/gltf/Soldier.glb中的3D模型
meshName: "Scene", // 要设置动画的网格的名称,这个值在模型加载后的对象中找到
position: { x: 0, y: 0, z: 0 }, // 在场景中放置单元的位置
scale: 1, // 单位的缩放比例。1.0表示:使用原始尺寸,0.1表示“小10倍”等。
animationName: "Run", // 要运行的动画的名称
},
];
/**
* 函数用于启动队列中下一个模型的加载过程。加载过程为异步的:它发生在“后台”。
* 所以这里不一次加载所有模型。我们加载一个,等待完成,然后加载下一个。
* 当所有模型都被加载时,我们调用loadUnits()。
*/
function loadModels() {
// 在预定模型(需要加载的)MODELS数组中拿模型对象(只包含少部分信息用作加载相关)
for (let i = 0; i < MODELS.length; ++i) {
const m = MODELS[i];
// 加载模型,加载成功后对模型进行复制,具体的类别个数在UNITS单元组中
loadGltfModel(m, function () {
++numLoadedModels;
// 所有模型加载完成后开始进行对应的模型单元初始化(复制)
if (numLoadedModels === MODELS.length) {
console.log("All models loaded, time to instantiate units...");
//
instantiateUnits();
}
});
}
};
/**
* 从GLTF文件加载三维模型。使用GLTFLoader。
* @param model {object} 模型配置,模型数组中的一项。它将在函数内部更新
* @param onLoaded {function} 加载模型时将调用的回调函数
*/
function loadGltfModel(model, onLoaded) {
const loader = new GLTFLoader(); //加载器
const modelName =
"./static/3dmod/gltf/" + model.name + ".glb";
loader.load(modelName, function (gltf) {
const scene = gltf.scene;
model.animations = gltf.animations;
model.scene = scene;
// 启用阴影
gltf.scene.traverse(function (object) {
if (object.isMesh) {
object.castShadow = true;
}
});
console.log("Done loading model", model.name);
onLoaded();
});
};
/**
* 按名称查找模型对象
* @param name
* @returns {object|null}
*/
function getModelByName(name) {
for (let i = 0; i < MODELS.length; ++i) {
if (MODELS[i].name === name) {
return MODELS[i];
}
}
return null;
};
/**
* 查看UNITS配置,克隆必要的3D模型场景,在场景中放置电枢和网格,并启动必要的动画
*/
function instantiateUnits() {
let numSuccess = 0;
for (let i = 0; i < UNITS.length; ++i) {
const u = UNITS[i];
const model = getModelByName(u.modelName);
if (model) {
const clonedScene = SkeletonUtils.clone(model.scene);
if (clonedScene) {
// 当THREE.Scene 场景已正确克隆时,找到一个模型网格并为其启动动画
// 根据meshName拿到模型网格
const clonedMesh = clonedScene.getObjectByName(u.meshName);
if (clonedMesh) {
const mixer = startAnimation(
clonedMesh,
model.animations,
u.animationName
);
// 将动画混合器保存在列表中,在动画循环中需要使用
mixers.push(mixer);
numSuccess++;
}
// 不同的模型可以具有不同的电枢和网格配置,我们无法为单个网格对象设置位置、比例或旋转。
// 所以,我们将其设置为整个克隆场景,然后将整个场景添加到游戏世界
// 注意:如果GLTF文件的场景中有灯光或其他项目,这可能会对其产生影响
mods.push(clonedScene);
scene.add(clonedScene);
if (u.position) {
clonedScene.position.set(
u.position.x,
u.position.y,
u.position.z
);
}
if (u.scale) {
clonedScene.scale.set(u.scale, u.scale, u.scale);
}
}
} else {
console.error("Can not find model", u.modelName);
}
}
console.log(`Successfully instantiated ${numSuccess} units`);
};
/**
* 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
* @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
* @param animations {Array} 数组,包含此模型的所有动画
* @param animationName {string} 要启动的动画的名称
* @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
*/
function startAnimation(skinnedMesh, animations, animationName) {
const mixer = new THREE.AnimationMixer(skinnedMesh);
const clip = THREE.AnimationClip.findByName(animations, animationName);
if (clip) {
const action = mixer.clipAction(clip);
action.play();
}
return mixer;
};
function animate() {
const UpdateDelta = clock.getDelta();
//执行渲染操作
renderer.render(scene, camera);
// 更新动画帧
moveOnCurve();
for (let i = 0; i < mixers.length; ++i) {
mixers[i].update(UpdateDelta);
}
requestAnimationFrame(animate);
};
添加路径和模型移动
路径:用到了Three.js提供的CatmullRomCurve3:使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。
移动:在每一帧中按照一定步长更新模型位置。
因为设置加载了三个模型,这里直接和模型一一对应起来进行路径生成
let curves = [];
const velocity = 0.002; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
let progress = [1, 0.3, 0.6];// 物体运动时在运动路径的初始位置,范围0~1
function makeCurve() {
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// 创建闭合的曲线
const curve = [
[new THREE.Vector3(0, 2, 0),new THREE.Vector3(-5, 2, 2),new THREE.Vector3(2, 2, -5)],
[new THREE.Vector3(2, 0, 2),new THREE.Vector3(5, 0, 2),new THREE.Vector3(2, 0, 5)],
[new THREE.Vector3(0, 0, -2),new THREE.Vector3(5, 0, -2),new THREE.Vector3(0, 0, 3)]
]
curve.forEach((item, i) => {
const cur = new THREE.CatmullRomCurve3(item);
cur.curveType = "catmullrom";
cur.closed = true;//设置是否闭环
cur.tension = 0.5; //设置线的张力,0为无弧度折线
curves.push(cur);
// 为曲线添加材质在场景中显示出来,不添加到场景显示也不会影响运动轨迹,相当于一个Helper
const points = cur.getPoints(50);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0x000000 });
const curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);
});
};
// 物体沿线移动方法
function moveOnCurve() {
let velocity = 0.001;
let move = function (i, velocity, obj) {
if (progress[i] <= 1 - velocity) {
const point = curves[i].getPointAt(progress[i]); //获取样条曲线指定点坐标,作为相机的位置
const pointBox = curves[i].getPointAt(progress[i] + velocity); //获取样条曲线指定点坐标
if (point.x && pointBox.x && mods !== []) {
obj.position.set(point.x, point.y, point.z);
// obj.lookAt(pointBox.x, pointBox.y, pointBox.z);
let targetPos = pointBox //目标位置点
let offsetAngle = 0 //目标移动时的朝向偏移
// //以下代码在多段路径时可重复执行
let mtx = new THREE.Matrix4() //创建一个4维矩阵
// .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
mtx.lookAt(obj.position, targetPos, obj.up) //设置朝向
mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
let toRot = new THREE.Quaternion().setFromRotationMatrix(mtx) //计算出需要进行旋转的四元数值
obj.quaternion.slerp(toRot, 0.2)
}
progress[i] += velocity;
} else {
progress[i] = 0;
}
};
mods.forEach((item, i) => {
move(i, velocity, item);
});
};
// moveOnCurve()需要在渲染中一直调用更新,以达到物体移动效果
function animate() {
requestAnimationFrame(animate);
moveOnCurve();
renderer.render(scene, camera);
};
完整代码和实现效果
完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js app</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "./three.js-master/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";
import * as SkeletonUtils from "./three.js-master/examples/jsm/utils/SkeletonUtils.js";
let scene, camera, renderer;
let curves = [];
const velocity = 0.002; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
let progress = [1, 0.3, 0.6];// 物体运动时在运动路径的初始位置,范围0~1
let clock = new THREE.Clock();
let mods = [];
let mixers = [];
let numLoadedModels = 0;
let MODELS = [
{ name: "Soldier" },
];
// 定义要放置在场景中的模型实例、它们的位置、比例以及必须播放的动画。
let UNITS = [
{
modelName: "Soldier", // 使用文件models/gltf/Soldier.glb中的3D模型
meshName: "Scene", // 要设置动画的网格的名称,这个值在模型加载后的对象中找到
position: { x: 0, y: 0, z: 0 }, // 在场景中放置单元的位置
scale: 1, // 单位的缩放比例。1.0表示:使用原始尺寸,0.1表示“小10倍”等。
animationName: "Run", // 要运行的动画的名称
},
{
modelName: "Soldier", // 使用文件models/gltf/Soldier.glb中的3D模型
meshName: "Scene", // 要设置动画的网格的名称,这个值在模型加载后的对象中找到
position: { x: 0, y: 0, z: 0 }, // 在场景中放置单元的位置
scale: 1, // 单位的缩放比例。1.0表示:使用原始尺寸,0.1表示“小10倍”等。
animationName: "Walk", // 要运行的动画的名称
},
{
modelName: "Soldier", // 使用文件models/gltf/Soldier.glb中的3D模型
meshName: "Scene", // 要设置动画的网格的名称,这个值在模型加载后的对象中找到
position: { x: 0, y: 0, z: 0 }, // 在场景中放置单元的位置
scale: 1, // 单位的缩放比例。1.0表示:使用原始尺寸,0.1表示“小10倍”等。
animationName: "Run", // 要运行的动画的名称
},
];
// 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
// 灯光需要开启“引起阴影”:light.castShadow = true;
// 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
// position and point the camera to the center of the scene
camera.position.set(5, 5, 5);
camera.lookAt(scene.position);
// 增加坐标系红色代表 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);
// 半球形光源
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 10, 0);
scene.add(hemiLight);
// 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
scene.add(hemiLighthelper);
// 地面
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
mesh.rotation.x = - Math.PI / 2;
mesh.receiveShadow = true;
scene.add(mesh);
// 平行光
const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
directionalLight.castShadow = true;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
directionalLight.position.set(0, 5, 5);
scene.add(directionalLight);
// 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
scene.add(directionalLightHelper);
renderer.shadowMap.enabled = true;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
};
function makeCurve() {
// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
// 创建闭合的曲线
const curve = [
[new THREE.Vector3(0, 2, 0),new THREE.Vector3(-5, 2, 2),new THREE.Vector3(2, 2, -5)],
[new THREE.Vector3(2, 0, 2),new THREE.Vector3(5, 0, 2),new THREE.Vector3(2, 0, 5)],
[new THREE.Vector3(0, 0, -2),new THREE.Vector3(5, 0, -2),new THREE.Vector3(0, 0, 3)]
]
curve.forEach((item, i) => {
const cur = new THREE.CatmullRomCurve3(item);
cur.curveType = "catmullrom";
cur.closed = true;//设置是否闭环
cur.tension = 0.5; //设置线的张力,0为无弧度折线
curves.push(cur);
// 为曲线添加材质在场景中显示出来,不添加到场景显示也不会影响运动轨迹,相当于一个Helper
const points = cur.getPoints(50);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0x000000 });
const curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);
});
};
// 物体沿线移动方法
function moveOnCurve() {
let velocity = 0.001;
let move = function (i, velocity, obj) {
if (progress[i] <= 1 - velocity) {
const point = curves[i].getPointAt(progress[i]); //获取样条曲线指定点坐标,作为相机的位置
const pointBox = curves[i].getPointAt(progress[i] + velocity); //获取样条曲线指定点坐标
if (point.x && pointBox.x && mods !== []) {
obj.position.set(point.x, point.y, point.z);
// obj.lookAt(pointBox.x, pointBox.y, pointBox.z);
let targetPos = pointBox //目标位置点
let offsetAngle = 0 //目标移动时的朝向偏移
// //以下代码在多段路径时可重复执行
let mtx = new THREE.Matrix4() //创建一个4维矩阵
// .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
mtx.lookAt(obj.position, targetPos, obj.up) //设置朝向
mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
let toRot = new THREE.Quaternion().setFromRotationMatrix(mtx) //计算出需要进行旋转的四元数值
obj.quaternion.slerp(toRot, 0.2)
}
progress[i] += velocity;
} else {
progress[i] = 0;
}
};
mods.forEach((item, i) => {
move(i, velocity, item);
});
};
/**
* 函数用于启动队列中下一个模型的加载过程。加载过程为异步的:它发生在“后台”。
* 所以这里不一次加载所有模型。我们加载一个,等待完成,然后加载下一个。
* 当所有模型都被加载时,我们调用loadUnits()。
*/
function loadModels() {
// 在预定模型(需要加载的)MODELS数组中拿模型对象(只包含少部分信息用作加载相关)
for (let i = 0; i < MODELS.length; ++i) {
const m = MODELS[i];
// 加载模型,加载成功后对模型进行复制,具体的类别个数在UNITS单元组中
loadGltfModel(m, function () {
++numLoadedModels;
// 所有模型加载完成后开始进行对应的模型单元初始化(复制)
if (numLoadedModels === MODELS.length) {
console.log("All models loaded, time to instantiate units...");
//
instantiateUnits();
}
});
}
};
/**
* 从GLTF文件加载三维模型。使用GLTFLoader。
* @param model {object} 模型配置,模型数组中的一项。它将在函数内部更新
* @param onLoaded {function} 加载模型时将调用的回调函数
*/
function loadGltfModel(model, onLoaded) {
const loader = new GLTFLoader(); //加载器
const modelName =
"./static/3dmod/gltf/" + model.name + ".glb";
loader.load(modelName, function (gltf) {
const scene = gltf.scene;
model.animations = gltf.animations;
model.scene = scene;
// 启用阴影
gltf.scene.traverse(function (object) {
if (object.isMesh) {
object.castShadow = true;
}
});
console.log("Done loading model", model.name);
onLoaded();
});
};
/**
* 按名称查找模型对象
* @param name
* @returns {object|null}
*/
function getModelByName(name) {
for (let i = 0; i < MODELS.length; ++i) {
if (MODELS[i].name === name) {
return MODELS[i];
}
}
return null;
};
/**
* 查看UNITS配置,克隆必要的3D模型场景,在场景中放置电枢和网格,并启动必要的动画
*/
function instantiateUnits() {
let numSuccess = 0;
for (let i = 0; i < UNITS.length; ++i) {
const u = UNITS[i];
const model = getModelByName(u.modelName);
if (model) {
const clonedScene = SkeletonUtils.clone(model.scene);
if (clonedScene) {
// 当THREE.Scene 场景已正确克隆时,找到一个模型网格并为其启动动画
// 根据meshName拿到模型网格
const clonedMesh = clonedScene.getObjectByName(u.meshName);
if (clonedMesh) {
const mixer = startAnimation(
clonedMesh,
model.animations,
u.animationName
);
// 将动画混合器保存在列表中,在动画循环中需要使用
mixers.push(mixer);
numSuccess++;
}
// 不同的模型可以具有不同的电枢和网格配置,我们无法为单个网格对象设置位置、比例或旋转。
// 所以,我们将其设置为整个克隆场景,然后将整个场景添加到游戏世界
// 注意:如果GLTF文件的场景中有灯光或其他项目,这可能会对其产生影响
mods.push(clonedScene);
scene.add(clonedScene);
if (u.position) {
clonedScene.position.set(
u.position.x,
u.position.y,
u.position.z
);
}
if (u.scale) {
clonedScene.scale.set(u.scale, u.scale, u.scale);
}
}
} else {
console.error("Can not find model", u.modelName);
}
}
console.log(`Successfully instantiated ${numSuccess} units`);
};
/**
* 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
* @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
* @param animations {Array} 数组,包含此模型的所有动画
* @param animationName {string} 要启动的动画的名称
* @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
*/
function startAnimation(skinnedMesh, animations, animationName) {
const mixer = new THREE.AnimationMixer(skinnedMesh);
const clip = THREE.AnimationClip.findByName(animations, animationName);
if (clip) {
const action = mixer.clipAction(clip);
action.play();
}
return mixer;
};
function animate() {
const UpdateDelta = clock.getDelta();
//执行渲染操作
renderer.render(scene, camera);
// 更新动画帧
moveOnCurve();
for (let i = 0; i < mixers.length; ++i) {
mixers[i].update(UpdateDelta);
}
requestAnimationFrame(animate);
};
init();
loadModels()
makeCurve();
animate();
</script>
</body>
</html>
实现效果