基于three.js的三維空間曲線軌跡運動

引言

我們在做項目的時候,有時候會遇到物體或者相機需要做復(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);
        });
繪制曲線的關(guān)鍵空間點

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 點

image.png

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ǎo)入模型

不過這樣的方式相當(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的三維空間曲線軌跡運動

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Threejs 為什么? webGL太難用,太復(fù)雜! 但是現(xiàn)代瀏覽器都支持 WebGL 這樣我們就不必使用 Fla...
    強某某閱讀 6,487評論 1 21
  • 照相機 此處所說照相機與現(xiàn)實有所差別, 由于threejs創(chuàng)建的場景是三維的,人眼要看出三維效果就需要有透視點。 ...
    風(fēng)銘閱讀 2,023評論 0 1
  • Threejs中文文檔 郭隆邦技術(shù)博客 2018-09-21 20:40:17 關(guān)注 Three.js中文文檔 今...
    情人波閱讀 14,405評論 0 7
  • Three.js是構(gòu)建web3d場景非常流行的框架,利用three.js我們可以更優(yōu)雅地創(chuàng)建出三維場景和三維動畫,...
    YoneChen閱讀 10,174評論 3 13
  • 1.重用Material和Geometry 2.不在render()中實例化或是賦值操作 3.粒子系統(tǒng)代替粒子 4...
    bbh123閱讀 4,795評論 1 3

友情鏈接更多精彩內(nèi)容