43 Three.js自定義二維圖形THREE.ShapeGeometry

專注前端30年發表於2017-12-15

簡介

通過THREE.ShapeGeometry,你可以呼叫幾個函式來建立自己的圖形。我們可以使用線條(line)、曲線(curve)和樣條曲線(spline)建立圖形的輪廓。還可以使用THREE.Shape物件的holes屬性給這個圖形打幾個孔。

建立THREE.ShapeGeometry物件

使用THREE.Shape物件繪製完成後,需要建立THREE.ShapeGeometry幾何體,我們有兩種方式建立

  • 直接例項化
var shapeGeometry = new THREE.ShapeGeometry(shapes,options);
  • 直接使用shape的內建方法makeGeometry()
var shapeGeometry = shape.makeGeometry(options);
  • 參數列
屬性 是否必需 描述
shapes 用來建立THREE.Geometry的一個或多個THREE.Shape物件。可以傳入單個THREE.Shape物件或是一個THREE.Shape物件陣列
options 還可以傳入一些選項,這些選項可以應用於使用shapes引數傳入的所有圖形。關於這些選項的解釋如下:
curveSegments—此屬性確定從圖形建立的曲線的平滑程度。預設值為12。
material—這是用於為指定圖形建立的面的materialIndex屬性。當把THREE.MeshFaceMaterial和此幾何體一起使用時,materialIndex屬性決定傳入的材質中的哪些材質用於傳入的圖形的面
UVGenerator—當對材質使用紋理時,UV對映決定紋理的哪個部分用於特定的面。使用UVGenerator屬性,可以傳入自己的物件,這將為傳入的圖形穿件的面建立UV設定。

THREE.Shape的繪圖函式

THREE.ShapeGeometry中最重要的部分是THREE.Shape,它可以用來建立圖形。所以下面介紹用來建立THREE.Shape的繪畫函式

名稱 描述
moveTo(x,y) 該函式將繪圖點移動到指定的x、y座標處
lineTo(x,y) 該函式從當前位置(例如由moveTo函式設定的位置)繪製一條線到指定的x和y座標處
quadraticCurveTo(aCPx,aCPy,x,y) 可以使用兩種不同方式來指定曲線:使用quadraticCurveTo函式,或使用bezierCurveTo函式。兩個函式的區別在於指定曲線曲率的方法。下面展示了這兩個選項之間的區別:
這裡寫圖片描述
對於二次曲線,我們要額外指定一個點(使用aCPx和aCPy引數),曲線僅基於該點繪製,當然還需要指定端點(x和y引數)。對於三次曲線(由bezierCurveTo函式繪製),你需要多指定兩個點才能定義曲線。起始點是路徑的當前位置。
bezierCurveTo(aCPx1,aCPy1,aCPx2,aCPy2,x,y) 根據提供的引數繪製一條曲線。相關說明可以參考錢一行的內容。該曲線的繪製局域兩個定義曲線的座標(aCPx1和aCPy1,aCPx2和aCPy2)以及終點座標(x和y)。起始點是路徑的當前位置。
splineThru(pts) 該函式沿著提供的座標集合pts繪製一條光滑曲線。這個引數應該是一個THREE.Vector2物件陣列。起始點是路徑的相對位置
arc(aX,aY,aRadius,aStartAngle,andAngle,aClockwise) 該函式用來畫圓(或一段圓弧)。圓弧起始於路徑的當前位置。aX和aY用來指定於當前位置的偏移量。aRadius設定圓的大小,而aStartAngleaEndAngle則用來定義圓弧要畫多長。布林屬性aClockwise決定這段圓弧是順時針還是逆時針畫。
absArc(aX,aY,aRadius,aStartAngle,andAngle,aClockwise) 參考arc函式的描述。其位置是絕對位置,而不是相對於當前的位置
ellipse(aX,aY,aRadius,aStartAngle,andAngle,aClockwise) 參考arc函式的描述。作為補充,通過ellipse函式,可以分別指定x軸半徑和y軸半徑
absEllipse(aX,aY,aRadius,aStartAngle,andAngle,aClockwise) 參考ellipse函式的描述。其位置是絕對位置,而不是相對於當前的位置。
fromPoints(vectors) 如果給該函式傳入一個THREE.Vector2(或THREE.Vector3)物件陣列,Three.js會建立一條通過提供的頂點使用直線繪製的路徑
holes holes屬性包含一個THREE.Shape物件陣列。這個陣列中的每一個物件會渲染為一個孔。關於這個屬性的一個很好的例子就是我們在本節開頭看到的例子。在那段程式碼段中,我們新增了三個THREE.Shape物件到這個陣列,一個用來渲染左邊的孔,一個用來渲染右邊的孔,還有一個渲染主要THREE.Shape物件—嘴

THREE.Shape建立幾何體的函式

名稱 描述
makeGeometry(options) 該函式從THREE.Shape物件返回一個THREE.ShapeGeometry物件。
createPointsGeometry(divisions) 該函式將圖形轉換成一個點集。divisions屬性定義返回點的數量。這個值越高,返回的點越多,最終的曲線也就越平滑。divisions會分別應用到路徑的每一部分
createSpacedPointsGeometry(divisions) 該函式也是講圖形轉換成一個點集,只不過這裡的divisions是一次性應用到整個路徑

案例程式碼

這裡寫圖片描述

案例檢視地址:http://www.wjceo.com/blog/threejs/2018-02-12/45.html

通過上面的方法一步一步的實現的下面的效果

注意:頂點必須按照逆時針方向繪製,才能夠實現裡面新增hole

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        html, body {
            margin: 0;
            height: 100%;
        }

        canvas {
            display: block;
        }

    </style>
</head>
<body onload="draw();">

</body>
<script src="https://johnson2heng.github.io/three.js-demo/lib/three.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/QuickHull.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/geometries/ConvexGeometry.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/controls/OrbitControls.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/libs/stats.min.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/js/libs/dat.gui.min.js"></script>
<script>
    var renderer;
    function initRender() {
        renderer = new THREE.WebGLRenderer({antialias:true});
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
    }

    var camera;
    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 10000);
        camera.position.set(0, 0, 100);
    }

    var scene;
    function initScene() {
        scene = new THREE.Scene();
    }

    var light;
    function initLight() {
        scene.add(new THREE.AmbientLight(0x404040));

        light = new THREE.DirectionalLight(0xffffff);
        light.position.set(1,1,1);
        scene.add(light);
    }

    function initModel() {
        var shape = new THREE.ShapeGeometry(drawShape());
        var material = new THREE.MeshPhongMaterial({color:0xff00ff});
        material.side = THREE.DoubleSide;//設定成兩面都可見
        var mesh = new THREE.Mesh(shape,material);
        scene.add(mesh);
        /*此方法是建立兩種紋理的方法
        * var shape = new THREE.ShapeGeometry(drawShape());
        var mesh = createMesh(shape);
        scene.add(mesh);
        * */
    }

    //生成2d圖形
    function drawShape() {

        // 例項化shape物件
        var shape = new THREE.Shape();

        // 設定開始點的位置
        shape.moveTo(20, 10);

        // 從起始點繪製直線到當前位置
        shape.lineTo(20, 40);

        //設定一條曲線到30 40
        shape.bezierCurveTo(15, 25, -5, 25, -30, 40);

        // 設定一條通過當前所有頂點的光滑曲線
        shape.splineThru(
            [new THREE.Vector2(-22, 30),
                new THREE.Vector2(-18, 20),
                new THREE.Vector2(-20, 10),
            ]);

        // 設定曲線回到頂點
        shape.quadraticCurveTo(0, -15, 20, 10);

        // 新增第一個眼
        var hole1 = new THREE.Path();
        hole1.absellipse(6, 20, 2, 3, 0, Math.PI * 2, true);
        shape.holes.push(hole1);

        // 新增第二個眼
        var hole2 = new THREE.Path();
        hole2.absellipse(-10, 20, 2, 3, 0, Math.PI * 2, true);
        shape.holes.push(hole2);

        // 新增嘴巴,一半的圓
        var hole3 = new THREE.Path();
        hole3.absarc(0, 5, 2, 0, Math.PI, true);
        shape.holes.push(hole3);

        // 返回shape
        return shape;
    }

    //生成模型
    function createMesh(geom) {

        // 建立兩個紋理
        var meshMaterial = new THREE.MeshNormalMaterial();
        meshMaterial.side = THREE.DoubleSide; //兩面都可見
        var wireFrameMat = new THREE.MeshBasicMaterial();
        wireFrameMat.wireframe = true; //開啟線框

        // 建立生成模型
        var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);

        return mesh;
    }

    //初始化效能外掛
    var stats;
    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }

    //使用者互動外掛 滑鼠左鍵按住旋轉,右鍵按住平移,滾輪縮放
    var controls;
    function initControls() {

        controls = new THREE.OrbitControls( camera, renderer.domElement );

        // 如果使用animate方法時,將此函式刪除
        //controls.addEventListener( 'change', render );
        // 使動畫迴圈使用時阻尼或自轉 意思是否有慣性
        controls.enableDamping = true;
        //動態阻尼係數 就是滑鼠拖拽旋轉靈敏度
        //controls.dampingFactor = 0.25;
        //是否可以縮放
        controls.enableZoom = true;
        //是否自動旋轉
        controls.autoRotate = false;
        //設定相機距離原點的最遠距離
        controls.minDistance  = 20;
        //設定相機距離原點的最遠距離
        controls.maxDistance  = 160;
        //是否開啟右鍵拖拽
        controls.enablePan = true;
    }

    function render() {
        renderer.render( scene, camera );
    }

    //視窗變動觸發的函式
    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        render();
        renderer.setSize( window.innerWidth, window.innerHeight );

    }

    function animate() {
        //更新控制器
        controls.update();
        render();

        //更新效能外掛
        stats.update();
        requestAnimationFrame(animate);
    }

    function draw() {
        initRender();
        initScene();
        initCamera();
        initLight();
        initModel();
        initControls();
        initStats();

        animate();
        window.onresize = onWindowResize;
    }
</script>
</html>

相關文章