引言
我們在做項目的時候唧垦,有時候會遇到物體或者相機需要做復(fù)雜軌跡運動的情況头滔,往往沒法簡單的通過修改位置來達成我們想要的運動效果。
這時候可以通過引入多段曲線去擬合我們想要的運動軌跡己沛,再獲取曲線的參數(shù)去控制物體做相應(yīng)軌跡的運動臂拓。
目錄
- 1、創(chuàng)建關(guān)鍵空間點數(shù)組
- 2押逼、根據(jù)點數(shù)組繪制曲線
- 3步藕、獲取曲線上特定位置的點,修改物體位置
- 4挑格、獲取曲線上特定位置的切線咙冗,修改物體朝向
- 5、隨時間實時改變物體位置和朝向
- 6漂彤、添加修改曲線功能
- 7雾消、引入模型模擬應(yīng)用場景
1、創(chuàng)建關(guān)鍵空間點數(shù)組
首先我們可以先找出運動軌跡上幾個特定的點挫望。
假設(shè)給定的點是(1,1,-1),(1,0,1),(-1,0,1),(-1,0,-1)
這里在每個點放了一個實體方塊用于示意點的位置立润,同時為后面的調(diào)整功能做準(zhǔn)備
const initialPoints = [
{ x: 1, y: 1, z: -1 },
{ x: 1, y: 0, z: 1 },
{ x: -1, y: 0, z: 1 },
{ x: -1, y: 0, z: -1 }
];
const addCube = (pos) => {
const geometry = new THREE.BoxBufferGeometry(0.1, 0.1, 0.1);
const material = new THREE.MeshBasicMaterial(0xffffff);
const cube = new THREE.Mesh(geometry, material);
cube.position.copy(pos);
scene.add(cube);
}
const cubeList = initialPoints.map(pos => {
return this.addCube(pos);
});
2、根據(jù)點數(shù)組繪制曲線
three.js 提供了好幾種方法繪制曲線媳板,這里采用的是 CatmullRom 插值的方法繪制曲線桑腮。
CatmullRom 插值的曲線一定會經(jīng)過所有給定的點,所以這種方法會更適合用作軌跡曲線的繪制蛉幸。
const curve = new THREE.CatmullRomCurve3(
cubeList.map((cube) => cube.position) // 直接綁定方塊的position以便后續(xù)用方塊調(diào)整曲線
);
curve.curveType = 'chordal'; // 曲線類型
curve.closed = true; // 曲線是否閉合
const points = curve.getPoints(50); // 50等分獲取曲線點數(shù)組
const line = new THREE.LineLoop(
new THREE.BufferGeometry().setFromPoints(points),
new THREE.LineBasicMaterial({ color: 0x00ff00 })
); // 繪制實體線條破讨,僅用于示意曲線,后面的向量線條同理奕纫,相關(guān)代碼就省略了
scene.add(line);
3提陶、獲取曲線上特定位置的點,修改物體位置
有了曲線之后若锁,可以通過 getPointAt 函數(shù)獲取曲線上特定位置的點向量搁骑,然后復(fù)制給物體的 position
function changePosition (t) {
const position = curve.getPointAt(t); // t: 當(dāng)前點在線條上的位置百分比,后面計算
mesh.position.copy(position);
}
為了直觀表現(xiàn)下圖采用 30 等分取點把位置向量繪制出來了,后面的圖片也采用一樣的方式展現(xiàn)向量
4仲器、獲取曲線上特定位置的切線煤率,修改物體朝向
現(xiàn)在物體的位置對上了,但是朝向卻是固定的乏冀,不符合生活經(jīng)驗蝶糯。一般來說物體在運動的時候,正面總是朝向軌跡的切線方向的辆沦。
現(xiàn)在我們通過 getTangentAt 函數(shù)獲取曲線上特定位置的切線向量昼捍,根據(jù)該切線向量和點的位置向量計算物體朝向的點向量,傳入物體的 lookAt 函數(shù)
function changeLookAt (t) {
const tangent = curve.getTangentAt(t);
const lookAtVec = tangent.add(position); // 位置向量和切線向量相加即為所需朝向的點向量
mesh.lookAt(lookAtVec);
}
注意上圖示的切線(黃線)實際起點為原點(0,0,0)肢扯,這里為了示意切線在曲線上的位置妒茬,平移到了點所在位置上
因為 lookAt 實際上是指向某個點向量,如果直接傳切線向量會導(dǎo)致物體朝向下圖 A 點蔚晨,需要和位置向量相加后才能得到所需的點向量(藍線)即 C 點
5乍钻、隨時間實時改變物體位置和朝向
現(xiàn)在軌跡上單一點的位置和朝向都可以獲取到了,剩下的就是在渲染函數(shù)中實時修改了铭腕。
根據(jù)時間計算當(dāng)前點在曲線上的位置百分比银择,傳入第 3、4 步中
const loopTime = 10 * 1000; // loopTime: 循環(huán)一圈的時間
// 在渲染函數(shù)中獲取當(dāng)前時間
const render = () => {
let time = Date.now();
let t = (time % loopTime) / loopTime; // 計算當(dāng)前時間進度百分比
changePosition(t);
changeLookAt(t);
requestAnimationFrame(render);
renderer.render(scene, camera);
}
requestAnimationFrame(render);
6累舷、添加修改曲線功能
到這里曲線運動的效果是做出來了浩考,但是如果我們想調(diào)整曲線,就得修改最初的點數(shù)組被盈,既不直觀也很繁瑣析孽。
參考 three.js 官網(wǎng)的 demo 發(fā)現(xiàn)可以通過 TransformControls 控制方塊位置,實時修改曲線害捕。同時因為前面的 curve 是通過方塊的 position 生成的绿淋,所以方塊位置的修改可以直接反映到 curve 上
import { TransformControls } from 'TransformControls.js'; // 引入模塊
const control = new TransformControls(camera, renderer.domElement);
// 獲取點擊位置
const mouse = new THREE.Vector2();
renderer.domElement.addEventListener(
'click',
(event) => {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
},
false
);
// 方塊點擊檢測
const rayCaster = new THREE.Raycaster();
rayCaster.setFromCamera(mouse, camera);
const intersects = rayCaster.intersectObjects(cubeList);
if (intersects.length) {
const target = intersects[0].object;
control.attach(target); // 綁定controls和方塊
scene.add(control);
}
// 修改曲線后同步修改實體線條
control.addEventListener('dragging-changed', (event) => {
if (!event.value) {
const points = curve.getPoints(50);
line.geometry.setFromPoints(points);
}
});
7、引入模型模擬應(yīng)用場景
經(jīng)過前面的步驟現(xiàn)在有了一個比較抽象的場景尝盼,現(xiàn)在可以考慮通過模型讓應(yīng)用場景更具象化。這里采用和場景契合度較高的過山車模型佑菩。
車模型的處理方式和方塊基本沒區(qū)別這里就不放相關(guān)代碼了盾沫,軌道是通過一小段的軌道模型不斷重復(fù)的方式去模擬。
// 軌道分段數(shù)
let railNum = 50;
// 導(dǎo)入模型
const loader = new GLTFLoader().setPath('model/');
loader.load('scene.gltf', (gltf) => {
// 軌道容器
const railway = new THREE.Object3D();
let position = new THREE.Vector3();
let tangent = new THREE.Vector3();
for (let i = 0; i < railNum; i++) {
// 復(fù)制多段軌道模型
let model = gltf.scene.clone();
railway.add(model);
// 這里和前面一樣通過獲取位置和切線向量去計算每段軌道的朝向
position = curve.getPointAt(i / railNum);
tangent = curve.getTangentAt(i / railNum);
model.position.copy(position);
model.lookAt(tangent.add(position));
}
scene.add(railway);
});
不過這樣的方式相當(dāng)于用多段直線拼出來的曲線殿漠,整體會比較生硬赴精。如果把曲線調(diào)整的過長也會出現(xiàn)軌道接不上的問題。
three.js 官網(wǎng)的 examples 里有一個過山車 demo绞幌,軌道不是使用模型而是通過代碼建模去模擬軌道蕾哟,效果會自然很多。詳見:https://threejs.org/examples/?q=roller#webxr_vr_rollercoaster
Demo 地址
http://demo.treedom.cn/threejs.curve_animation.wyl/
參考
https://threejs.org/examples/?q=curve#webgl_modifier_curve
https://threejs.org/examples/?q=spli#webgl_geometry_extrude_splines
了解更多
原文來源: 基于three.js的三維空間曲線軌跡運動