three.js流動線

此功能借助meshline插件來實現最爲簡便,初學就暫不探究其原理了。

github地址:   https://github.com/spite/THREE.MeshLine

本文主要學習了躍焱邵隼作者的源代碼,但由於作者已將自己的插件一步一步壯大,導致初學時,無法準確切入到核心,因此特別記錄一下。

https://www.wellyyss.cn/ysThree/main/app.html

效果

先看最基本的

  function initThree(el, options) {
        options = options || {}
        const t = this
        appInstance  = this
        const width = el.offsetWidth
        const height = el.offsetHeight
        const asp = width / height

        // scene
        const scene = new THREE.Scene()

        // camera
        let camera
        if (options.camera) {
            camera = options.camera
        } else {
            camera = new THREE.PerspectiveCamera(45, asp, 1, 100000)
            window.addEventListener('resize', function() {
                camera.aspect = el.offsetWidth / el.offsetHeight
                renderer.setSize(el.offsetWidth, el.offsetHeight) // 重新獲取
                camera.updateProjectionMatrix()
                renderer.render(scene, camera)
            }, false)
        }
        camera.position.set(30, 30, 30)

        // renderer
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
        renderer.setPixelRatio(window.devicePixelRatio)
        renderer.setSize(width, height)
        el.append(renderer.domElement)
        renderer.setClearColor(options.clearColor || '#000')

        // 輔助
        if (options.axes) scene.add(new THREE.AxesHelper(10))// 座標軸輔助紅x 綠y 藍z
        if (options.gridHelper) scene.add(new THREE.GridHelper(100, 100))// 網格參考線

        //按序渲染
        renderer.sortObjects = options.sortObjects

        // to the instance
        t.renderer = renderer
        t.scene = scene
        t.camera = camera
        t.el = el
    }

    const el = document.getElementById('box')

    const app = new initThree(el,{
        // gridHelper:true,//網格參考線
        // axes:true,//座標輔助
        clearColor:'#000'//畫布顏色
    })
    const camera = app.camera
    const renderer = app.renderer
    const scene = app.scene
    function initControls(scene,camera,renderer) {
        const controls = new THREE.OrbitControls( camera, renderer.domElement );
        // 如果使用animate方法時,將此函數刪除
        controls.addEventListener( 'change', ()=>{
            renderer.render( scene, camera );
        });
        // // 使動畫循環使用時阻尼或自轉 意思是否有慣性
        // controls.enableDamping = true;
        // //動態阻尼係數 就是鼠標拖拽旋轉靈敏度
        // //controls.dampingFactor = 0.25;
        // //是否可以縮放
        // controls.enableZoom = true;
        // //是否自動旋轉
        // controls.autoRotate = true;
        // controls.autoRotateSpeed = 0.5;
        // //設置相機距離原點的最遠距離
        // controls.minDistance  = 1;
        // //設置相機距離原點的最遠距離
        // controls.maxDistance  = 200;
        // //是否開啓右鍵拖拽
        //controls.enablePan = true;
        return controls
    }
    var controls = initControls(scene,camera,renderer)
    const clock = new THREE.Clock()

    //add light
    const directionalLight = new THREE.DirectionalLight( '#fff' )
    directionalLight.position.set( 30, 30, 30 ).normalize()
    scene.add( directionalLight )
    const ambientLight = new THREE.AmbientLight('#fff',0.3) // obj 唯一 id
    scene.add(ambientLight)
   const pointList1 = [
        [20,5,10],
        [10,5,-9],
        [10,5,20],
        [-40,5,-40]
    ]
    let line1
    textureLoader.load( '../../images/ysThree/green_line.png', function (texture1) {
        const material1 = new MeshLineMaterial({
            color: "green",
            map: texture1,
            useMap: true,
            lineWidth: 4,
            resolution: resolution,
            dashArray: 0.8,  // 破折號之間的長度和間距。(0 -無破折號)
            dashRatio: 0.5, // 定義可見和不可見之間的比率(0 -更可見,1 -更不可見)。
            dashOffset: 0,
            transparent: true,
            sizeAttenuation: 1, //使線寬不變,不管距離(1個單位是屏幕上的1px)(0 -衰減,1 -不衰減)
            side: THREE.FrontSide,
            depthTest: true,
            blending: THREE.AdditiveBlending,
            near: camera.near,
            far: camera.far,
        })
        const l = []
        pointList1.forEach(e => l.push(new THREE.Vector3(e[0], e[1], e[2])))
        let curve = new THREE.CatmullRomCurve3(l) // 曲線路徑
        const geo = new THREE.Geometry()
        geo.vertices = curve.getPoints( 50)
        console.log(geo)
        const meshLine = new MeshLine()
        meshLine.setGeometry(geo)
        console.log(meshLine.geometry)
        line1=new THREE.Mesh(meshLine.geometry, material1)
        scene.add(line1)
    })

   function render() {
        controls.update(clock.getDelta())
        renderer.render( scene,camera)
        requestAnimationFrame(render)
        //
        if(line1){
            line1.material.uniforms.dashOffset.value -= 0.01
        }

    }
    render()

由上文可以看到,其實核心就是基於meshline作者提供的插件來完成的。

完成代碼如下:

<script src="../../plugins/threeLibrary/three.min.js"></script>
<script src="../../plugins/threeLibrary/js/controls/OrbitControls.js"></script>
<script src="../../plugins/threeLibrary/js/lines/MeshLine.js"></script>

<script>

    function initThree(el, options) {
        options = options || {}
        const t = this
        appInstance  = this
        const width = el.offsetWidth
        const height = el.offsetHeight
        const asp = width / height

        // scene
        const scene = new THREE.Scene()

        // camera
        let camera
        if (options.camera) {
            camera = options.camera
        } else {
            camera = new THREE.PerspectiveCamera(45, asp, 1, 100000)
            window.addEventListener('resize', function() {
                camera.aspect = el.offsetWidth / el.offsetHeight
                renderer.setSize(el.offsetWidth, el.offsetHeight) // 重新獲取
                camera.updateProjectionMatrix()
                renderer.render(scene, camera)
            }, false)
        }
        camera.position.set(30, 30, 30)

        // renderer
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
        renderer.setPixelRatio(window.devicePixelRatio)
        renderer.setSize(width, height)
        el.append(renderer.domElement)
        renderer.setClearColor(options.clearColor || '#000')

        // 輔助
        if (options.axes) scene.add(new THREE.AxesHelper(10))// 座標軸輔助紅x 綠y 藍z
        if (options.gridHelper) scene.add(new THREE.GridHelper(100, 100))// 網格參考線

        //按序渲染
        renderer.sortObjects = options.sortObjects

        // to the instance
        t.renderer = renderer
        t.scene = scene
        t.camera = camera
        t.el = el
    }

    const el = document.getElementById('box')

    const app = new initThree(el,{
        // gridHelper:true,//網格參考線
        // axes:true,//座標輔助
        clearColor:'#000'//畫布顏色
    })
    const camera = app.camera
    const renderer = app.renderer
    const scene = app.scene
    function initControls(scene,camera,renderer) {
        const controls = new THREE.OrbitControls( camera, renderer.domElement );
        // 如果使用animate方法時,將此函數刪除
        controls.addEventListener( 'change', ()=>{
            renderer.render( scene, camera );
        });
        // // 使動畫循環使用時阻尼或自轉 意思是否有慣性
        // controls.enableDamping = true;
        // //動態阻尼係數 就是鼠標拖拽旋轉靈敏度
        // //controls.dampingFactor = 0.25;
        // //是否可以縮放
        // controls.enableZoom = true;
        // //是否自動旋轉
        // controls.autoRotate = true;
        // controls.autoRotateSpeed = 0.5;
        // //設置相機距離原點的最遠距離
        // controls.minDistance  = 1;
        // //設置相機距離原點的最遠距離
        // controls.maxDistance  = 200;
        // //是否開啓右鍵拖拽
        //controls.enablePan = true;
        return controls
    }
    var controls = initControls(scene,camera,renderer)
    const clock = new THREE.Clock()

    //add light
    const directionalLight = new THREE.DirectionalLight( '#fff' )
    directionalLight.position.set( 30, 30, 30 ).normalize()
    scene.add( directionalLight )
    const ambientLight = new THREE.AmbientLight('#fff',0.3) // obj 唯一 id
    scene.add(ambientLight)


    camera.position.set(100,100,100)
    const resolution = new THREE.Vector2( el.offsetWidth,  el.offsetHeight );
    const textureLoader = new THREE.TextureLoader()


    function getSphereHeightPoints (v0, v3, n1, n2, p0) {
        // 夾角
        const angle = (v0.angleTo(v3) * 180) / Math.PI / 10 // 0 ~ Math.PI
        const aLen = angle * (n1 || 10)
        const hLen = angle * angle * (n2 || 120)
        p0 = p0 || new THREE.Vector3(0, 0, 0) // 默認以 座標原點爲參考對象
        // 法線向量
        const rayLine = new THREE.Ray(p0, v0.clone().add(v3.clone()).divideScalar(2))
        // 頂點座標
        const vtop = rayLine.at(hLen / rayLine.at(1).distanceTo(p0))
        // 計算制高點
        const getLenVector = (v1, v2, len) => v1.lerp(v2, len / v1.distanceTo(v2))
        // 控制點座標
        return [getLenVector(v0.clone(), vtop, aLen), getLenVector(v3.clone(), vtop, aLen)]
    }

    /*  **** **** ****   ****/
   function createAnimateLine (option) {
        let curve
        if (option.kind === 'sphere') { // 由兩點之間連線成貝塞爾曲線
            const sphereHeightPointsArgs = option.sphereHeightPointsArgs
            const pointList = this.getSphereHeightPoints(...sphereHeightPointsArgs) // v0,v3,n1,n2,p0
            curve = new THREE.CubicBezierCurve3(sphereHeightPointsArgs[0], pointList[0], pointList[1], sphereHeightPointsArgs[1])
        } else { // 由多個點數組構成的曲線 通常用於道路
            const l = []
            option.pointList.forEach(e => l.push(new THREE.Vector3(e[0], e[1], e[2])))
            curve = new THREE.CatmullRomCurve3(l) // 曲線路徑
        }
        if (option.type === 'pipe') { // 使用管道線
            // 管道體
            const tubeGeometry = new THREE.TubeGeometry(curve, option.number || 50, option.radius || 1, option.radialSegments)
            return new THREE.Mesh(tubeGeometry, option.material)
        } else { // 使用 meshLine
            if (!MeshLine || !MeshLineMaterial) console.error('you need import MeshLine & MeshLineMaterial!')
            else {
                const geo = new THREE.Geometry()
                geo.vertices = curve.getPoints(option.number || 50)
                const meshLine = new MeshLine()
                meshLine.setGeometry(geo)
                return new THREE.Mesh(meshLine.geometry, option.material)
            }
        }
    }

    const pointList1 = [
        [20,5,10],
        [10,5,-9],
        [10,5,20],
        [-40,5,-40]
    ]
    let line1
    textureLoader.load( '../../images/ysThree/green_line.png', function (texture1) {
        const material1 = new MeshLineMaterial({
            color: "green",
            map: texture1,
            useMap: true,
            lineWidth: 4,
            resolution: resolution,
            dashArray: 0.8,  // 破折號之間的長度和間距。(0 -無破折號)
            dashRatio: 0.5, // 定義可見和不可見之間的比率(0 -更可見,1 -更不可見)。
            dashOffset: 0,
            transparent: true,
            sizeAttenuation: 1, //使線寬不變,不管距離(1個單位是屏幕上的1px)(0 -衰減,1 -不衰減)
            side: THREE.FrontSide,
            depthTest: true,
            blending: THREE.AdditiveBlending,
            near: camera.near,
            far: camera.far,
        })
        const l = []
        pointList1.forEach(e => l.push(new THREE.Vector3(e[0], e[1], e[2])))
        let curve = new THREE.CatmullRomCurve3(l) // 曲線路徑
        const geo = new THREE.Geometry()
        geo.vertices = curve.getPoints( 50)
        console.log(geo)
        const meshLine = new MeshLine()
        meshLine.setGeometry(geo)
        console.log(meshLine.geometry)
        line1=new THREE.Mesh(meshLine.geometry, material1)
        scene.add(line1)
    })

    /** 2:繪製普通pipeLine**/

    const pointList2 = [
        [-20,5,-10],
        [30,5,-15],
        [10,5,20],
        [40,5,40]
    ]
    const texture2 = textureLoader.load("../../images/ysThree/red_line.png")
    texture2.wrapS = texture2.wrapT = THREE.RepeatWrapping; //每個都重複
    texture2.repeat.set(1, 1)
    const  material2 = new THREE.MeshBasicMaterial({map:texture2,side:THREE.BackSide,transparent:true})
    texture2.needsUpdate = true
    const line2 = createAnimateLine({
        // kind: 'sphere',//默認不填 爲普通 ; 如爲sphere,則表示球面建點
        type: 'pipe',//默認不填 爲MeshLine ; 如爲pipe,則表示管道線
        pointList: pointList2,
        material: material2,
        number: 100
    })
    scene.add(line2)

    /** 1:在球面上繪製meshLine**/
    const v0 =  new THREE.Vector3( -80, 10,  0 )
    const v3 =  new THREE.Vector3( 80, 10,  0 )

    let line3
    textureLoader.load( '../../images/ysThree/green_line.png', function (texture3) {
        const material3 = new MeshLineMaterial({
            color: "green",
            map: texture3,
            useMap: true,
            lineWidth: 4,
            resolution: resolution,
            dashArray: 0.8,  // 破折號之間的長度和間距。(0 -無破折號)
            dashRatio: 0.5, // 定義可見和不可見之間的比率(0 -更可見,1 -更不可見)。
            dashOffset: 0,
            transparent: true,
            sizeAttenuation: 1, //使線寬不變,不管距離(1個單位是屏幕上的1px)(0 -衰減,1 -不衰減)
            side: THREE.FrontSide,
            depthTest: true,
            blending: THREE.AdditiveBlending,
            near: camera.near,
            far: camera.far,
        })
        line3 = createAnimateLine({
            kind: 'sphere',//默認不填 爲普通 ; 如爲sphere,則表示球面建點
            // type: 'pipe',//默認不填 爲MeshLine ; 如爲pipe,則表示管道線
            sphereHeightPointsArgs: [v0,v3],
            material: material3
        })
        scene.add(line3)
    })

    /** 1:在球面上繪製pipeLine**/

    const v0_1 =  new THREE.Vector3( -60, 10,  0 )
    const v3_1 =  new THREE.Vector3( 60, 10,  0 )

    const texture4 = textureLoader.load("../../images/ysThree/red_line.png")
    texture4.wrapS = texture4.wrapT = THREE.RepeatWrapping; //每個都重複
    texture4.repeat.set(1, 1)
    const  materia4 = new THREE.MeshBasicMaterial({map:texture4,side:THREE.BackSide,transparent:true})
    texture4.needsUpdate = true
    const line4 = createAnimateLine({
        kind: 'sphere',//默認不填 爲普通 ; 如爲sphere,則表示球面建點
        type: 'pipe',//默認不填 爲MeshLine ; 如爲pipe,則表示管道線
        sphereHeightPointsArgs: [v0_1,v3_1],
        material: materia4,
        number: 100,
        radius: 1 // 默認
    })
    scene.add(line4)

    /*  **** **** ****   ****/
    function render() {
        controls.update(clock.getDelta())
        renderer.render( scene,camera)
        requestAnimationFrame(render)
        //
        if(line1){
            line1.material.uniforms.dashOffset.value -= 0.01
        }

        //
        texture2.offset.x -= 0.01

        //
        if(line3){
            line3.material.uniforms.dashOffset.value -= 0.01
        }

        texture4.offset.x -= 0.01
    }
    render()
</script>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章