0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Three.jsで積分領域を可視化する (THREE.LatheBufferGeometryを使って回転体を作る)

Last updated at Posted at 2020-07-12

序章

Three.jsを利用することで, 比較的容易にWeb上で3Dモデルを作成することができる. 本稿では, Three.jsを用いて, 積分領域の可視化に取り組む.

今回扱う問題は, 回転体の体積を求める問題である. そこで, 回転体の形状を作成することを目指す.

数学的には, 断面の形状を求めてそれを円柱座標で書けば良い. Three.jsでは, THREE.LatheBufferGeometryを用いることで, 同様の発想で回転体を生成することができる.
正確には, 2次元領域の境界を点列(2次元ベクトルの配列)の形で与えることで, $y$軸回りの回転体を作ることができる.

さて, 以下が考える問題である.

問題

(京都大学2007文系2)
$3$次関数 $y = x^3 - 2 x^2 - x + 2$ のグラフ上の点 $(1, 0)$ における接線を $l$ とする. この$3$次関数のグラフと接線 $l$ で囲まれた部分を $x$ 軸の回りに回転して立体を作る. その立体の体積を求めよ.

方針

接線 $l$ は, $y = -2 x + 2 $ と書ける. また, $y = x^3 - 2 x^2 - x + 2$ と $y = -2 x + 2 $ は $ x = 0, 1$ で交わる.

それゆえ, $f(x) = x^3 - 2 x^2 - x + 2$ と $g(x) = -2 x + 2 $ について, $ x \in [0, 1]$ の範囲で頂点の組 $(x, f(x))$ と $(x, g(x))$ を生成し, そこからTHREE.LatheBufferGeometry を使って回転体を作ればよい.

なお, 点列の順番には意味があるので, 閉曲線を描くように点列を作成する必要がある.

また, THREE.LatheBufferGeometryは $y$ 軸回りの回転体を形成するため(回転軸を指定することができない), 適宜座標を入れ替える必要がある.

アニメーション

パラメータを動かすことで, 回転体が形成される様子を見ることができる.

kyoto2007b2.gif

実装

function init() {

    let stats = initStats();

    let scene = new THREE.Scene();

    let camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

    let webGLRenderer = new THREE.WebGLRenderer();
    webGLRenderer.setClearColor(new THREE.Color(0x000000));
    webGLRenderer.setSize(window.innerWidth, window.innerHeight);
    webGLRenderer.shadowMap.enabled = true;

    camera.position.x = 30;
    camera.position.y = 40;
    camera.position.z = 50;
    camera.lookAt(new THREE.Vector3(0, 0, 0));

    const controler = new THREE.OrbitControls(camera);

    document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

    const axisXLength = 20;
    const axisXHeadLength = axisXLength * 0.05;
    const axisXHeadWidth = axisXHeadLength * 0.5;
    const directionX = new THREE.Vector3(1, 0, 0);
    const startX = new THREE.Vector3(0, 0, 0);
    const colorX = 0xff0000;
    const axisX = new THREE.ArrowHelper(directionX, startX, axisXLength + axisXHeadLength * 2, colorX, axisXHeadaxisXHeadWidth);
    scene.add(axisX);

    const axisYLength = 20;
    const axisYHeadLength = axisYLength * 0.05;
    const axisYHeadWidth = axisYHeadLength * 0.5;
    const directionY = new THREE.Vector3(0, 1, 0);
    const startY = new THREE.Vector3(0, 0, 0);
    const colorY = 0x00ff00;
    const axisY = new THREE.ArrowHelper(directionY, startY, axisYLength + axisYHeadLength * 2, colorY, axisYHeadaxisYHeadWidth);
    scene.add(axisY);

    const axisZLength = 20;
    const axisZHeadLength = axisZLength * 0.05;
    const axisZHeadWidth = axisZHeadLength * 0.5;
    const directionZ = new THREE.Vector3(0, 0, 1);
    const startZ = new THREE.Vector3(0, 0, 0);
    const colorZ = 0x00f6ff;
    const axisZ = new THREE.ArrowHelper(directionZ, startZ, axisZLength + axisZHeadLength * 2, colorZ, axisZHeadaxisZHeadWidth);
    scene.add(axisZ);

    const fontLoader = new THREE.FontLoader();
    fontLoader.load('../assets/fonts/helvetiker_bold.typeface.js', function (font) {
        const textXGeometry = new THREE.TextGeometry('X', {
            font: font,
            size: axisXLength / 15,
            height: 0,
            curveSegments: 0,
            bevelEnabled: true,
            bevelThickness: 0,
            bevelSize: 0,
            bevelSegments: 0
        });
        const textXMaterial = new THREE.MeshBasicMaterial({ color: colorX });
        const textX = new THREE.Mesh(textXGeometry, textXMaterial);
        textX.position.set(axisXLength + axisXHeadLength * 2, 1, 0);
        scene.add(textX);

        const textYGeometry = new THREE.TextGeometry('Y', {
            font: font,
            size: axisYLength / 15,
            height: 0,
            curveSegments: 0,
            bevelEnabled: true,
            bevelThickness: 0,
            bevelSize: 0,
            bevelSegments: 0
        });
        const textYMaterial = new THREE.MeshBasicMaterial({ color: colorY });
        const textY = new THREE.Mesh(textYGeometry, textYMaterial);
        textY.position.set(1, axisYLength + axisYHeadLength * 2, 0);
        scene.add(textY);

        const textZGeometry = new THREE.TextGeometry('Z', {
            font: font,
            size: axisZLength / 15,
            height: 0,
            curveSegments: 0,
            bevelEnabled: true,
            bevelThickness: 0,
            bevelSize: 0,
            bevelSegments: 0
        });
        const textZMaterial = new THREE.MeshBasicMaterial({ color: colorZ });
        const textZ = new THREE.Mesh(textZGeometry, textZMaterial);
        textZ.position.set(-2, 1, axisZLength + axisZHeadLength * 2);
        scene.add(textZ);
    });

    let latheMesh;

    const phiStart = Math.PI / 2;
    const actual_segments = 40;
    let segments = actual_segments;
    const start = 0;
    const end = 1;
    const pointNum = 15;

    let controls = new function () {
        this.phiLength = 2 * Math.PI;
        this.scale = 10;

        this.redraw = function () {
            scene.remove(latheMesh);

            segments = actual_segments * controls.phiLength / (2 * Math.PI);

            generatePoints(segments, phiStart, controls.phiLength, controls.scale, start, end, pointNum);
        };
    };

    generatePoints(segments, phiStart, controls.phiLength, controls.scale, start, end, pointNum);

    let gui = new dat.GUI();
    gui.add(controls, 'phiLength', 0, 2 * Math.PI).onChange(controls.redraw);
    gui.add(controls, 'scale', 0.0, 20.0).onChange(controls.redraw);

    render();

    function generatePoints(segments, phiStart, phiLength, scale, start, end, pointNum) {
        let points = [];
        let x;
        let y;

        for (let i = 0; i <= pointNum; i++) {
            x = (1 - i / pointNum) * start + (i / pointNum) * end;
            y = g(x)
            points.push(new THREE.Vector2(scale * y, - scale * x));
        }
        for (let i = pointNum; i >= 0; i--) {
            x = (1 - i / pointNum) * start + (i / pointNum) * end;
            y = f(x)
            points.push(new THREE.Vector2(scale * y, - scale * x));
        }

        let latheGeometry = new THREE.LatheBufferGeometry(points, segments, phiStart, phiLength);
        latheMesh = createMesh(latheGeometry);
        latheMesh.rotation.z = Math.PI / 2;
        scene.add(latheMesh);
    }

    function f(x) {
        return x ** 3 - 2 * x ** 2 - x + 2;
    }

    function g(x) {
        return -2 * x + 2;
    }

    function createMesh(geom) {

        let facemat = [
            new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 1, transparent: true }),
            new THREE.MeshBasicMaterial({ color: 0xffffcf, side: THREE.BackSide, opacity: 1, transparent: true }
            new THREE.MeshBasicMaterial({ color: 0x1f5e44, wireframe: true, wireframeLinewidth: 100.0 })
        ];

        let mesh = THREE.SceneUtils.createMultiMaterialObject(geom, facemat);

        return mesh;
    }

    function render() {
        stats.update();

        requestAnimationFrame(render);
        webGLRenderer.render(scene, camera);
    }

    function initStats() {

        let stats = new Stats();
        stats.setMode(0);

        stats.domElement.style.position = 'absolute';
        stats.domElement.style.left = '0px';
        stats.domElement.style.top = '0px';

        document.getElementById("Stats-output").appendChild(stats.domElement);

        return stats;
    }
}
window.onload = init;

参考

・Jos Dirksen (あんどうやすし 訳), 初めてのThree.js, オライリージャパン.

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?