LoginSignup
2
1

More than 3 years have passed since last update.

【JavaScript】動くデジタル時計【three.js】

Last updated at Posted at 2020-11-06

はじめに

test.gif
こんな感じのやつを電子工作で作ってみたくなって、イメージを沸かせるためにjavascriptで書いてみました。
自分用のメモ代わりの記事です。
ちなみに電子工作全くやったことないです。
three.jsを使っています。three.js初めて使いました。

ソース

書きなぐっています。すいません。
あーコロン動かすの忘れた。まあいいや。おやすみなさい。

<html>
<head>
<meta charset="utf-8" />
<style>
body {
    margin: 0;
}
</style>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script>
// ページの読み込みを待つ
$(e => {
    // サイズを指定
    const width = 960 / 2;
    const height = 540 / 2;

    // レンダラーを作成
    const renderer = new THREE.WebGLRenderer({
      canvas: document.querySelector('#myCanvas')
    });
    renderer.setSize(width, height);

    // シーンを作成
    const scene = new THREE.Scene();        

    // カメラを作成
    const camera = new THREE.PerspectiveCamera(
      45,
      width / height,
      1,
      10000
    );
    camera.position.set(0, 200, 1000);
    camera.lookAt(new THREE.Vector3(0, 0, 0));

    // カメラを作成
    //const camera = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2);

    // コンテナーを作成
    const container = new THREE.Object3D();
    scene.add(container);

    // マテリアルを作成
    const material = new THREE.MeshStandardMaterial({
      color: 0xcccccc,
      side: THREE.DoubleSide,
      flatShading: true,
    });

    // 平行光源を作成
    const directionalLight = new THREE.DirectionalLight(0xffffff);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);
    // 環境光を作成
    const ambientLight = new THREE.AmbientLight(0x999999);
    scene.add(ambientLight);

    // アニメーション用の回転角度
    let rotXShift = 0.0025;
    let rotYShift = 0.01;

    const numDefs = [
        /* 0 */ [ 0, 1, 2, 4, 5, 6 ],
        /* 1 */ [ 2, 5 ],
        /* 2 */ [ 0, 2, 3, 4, 6 ],
        /* 3 */ [ 0, 2, 3, 5, 6 ],
        /* 4 */ [ 1, 2, 3, 5 ],
        /* 5 */ [ 0, 1, 3, 5, 6 ],
        /* 6 */ [ 0, 1, 3, 4, 5, 6 ],
        /* 7 */ [ 0, 2, 5 ],
        /* 8 */ [ 0, 1, 2, 3, 4, 5, 6 ],
        /* 9 */ [ 0, 1, 2, 3, 5, 6 ],
    ];

    const meshes = [];    

    createClock();

    const shifts = [];
    for(let i = 0; i < meshes.length; i += 1) {
        shifts.push([ 0, 0, 0, 0, 0, 0, 0 ]);
    }    

    tick();

    function updateShifts() {
        const time = getCurrentTime();
        initShifts();
        for(let i = 0; i < shifts.length; i += 1) {
            const shift = shifts[i];
            const t = parseInt(time[i]);
            const def = numDefs[t];
            def.forEach(n => {
                shift[n] = 1;
            });
        }
    }

    function initShifts() {
        for(let i = 0; i < shifts.length; i += 1) {
            for(let j = 0; j < 7; j += 1) {
                shifts[i][j] = -1;
            }
        }
    }

    function createClock() {
        const a = 20,
            b = 60, 
            c = 100,
            s = 5,      // 数字の要素の幅
            ss = 10,    // 数字間の幅
            cr = 20,    // :(コロン)の半径
            cbs = 60;   // コロンの上下のスペース
            cs = 20;    // コロンと数値のスペース

        // 時
        meshes[0] = createNumber(-a - ss * 0.5
             - (a + ss + a + s + a + b + a + s + a + cs + cr * 2 + cs + a + s + a + b + a + s), a, b, c, s, ss);
        meshes[1] = createNumber(a + ss * 0.5 + s + 2 * a + b + s
            - (a + ss + a + s + a + b + a + s + a + cs + cr * 2 + cs + a + s + a + b + a + s), a, b, c, s, ss);

        // 分
        meshes[2] = createNumber(-a - ss * 0.5, a, b, c, s, ss);
        meshes[3] = createNumber(a + ss * 0.5 + s + 2 * a + b + s, a, b, c, s, ss);  

        // 秒
        meshes[4] = createNumber(-a - ss * 0.5
             + a + ss + a + s + a + b + a + s + a + cs + cr * 2 + cs + a + s + a + b + a + s, a, b, c, s, ss);
        meshes[5] = createNumber(a + ss * 0.5 + s + 2 * a + b + s
            + a + ss + a + s + a + b + a + s + a + cs + cr * 2 + cs + a + s + a + b + a + s, a, b, c, s, ss);

        // 時と分のコロン
        createColon(-a - ss * 0.5, a, b, c, s, ss, cr, cbs, cs);  

        // 分と秒のコロン    
        createColon(-a - ss * 0.5
         + cr + cs + a + s + a + b + a + s + a + ss + a + s + a + b + a + s + a + cs + cr , a, b, c, s, ss, cr, cbs, cs); 
    }    

    //先頭ゼロ付加
    function padZero(num) {
        var result;
        if (num < 10) {
            result = "0" + num;
        } else {
            result = "" + num;
        }
        return result;
    }

    function getCurrentTime() {
        var now = new Date();
        return padZero(now.getHours()) + padZero(now.getMinutes()) + padZero(now.getSeconds());
    }

    // コロンを作成
    function createColon(shiftX, a, b, c, s, ss, cr, cbs, cs) {

        const ret = [];
        let geometry, mesh;
        geometry = new THREE.CylinderGeometry(cr, cr, c, 8);  
        mesh = new THREE.Mesh(geometry, material); 
        mesh.position.x = shiftX - 2 * a - b - s * 2 - a - cs - cr;
        mesh.position.y = cbs / 2;
        mesh.position.z = 0;
        mesh.rotation.x = Math.PI * 0.5;
        container.add(mesh);
        ret.push(mesh);

        geometry = new THREE.CylinderGeometry(cr, cr, c, 8);  
        mesh = new THREE.Mesh(geometry, material); 
        mesh.position.x = shiftX - 2 * a - b - s * 2 - a - cs - cr;
        mesh.position.y = -cbs / 2;
        mesh.position.z = 0;
        mesh.rotation.x = Math.PI * 0.5;
        container.add(mesh);
        ret.push(mesh);

        return ret;
    }

    // 数字を作成する
    function createNumber(shiftX, a, b, c, s, ss) {
        return [
            createHexGeometry({x: shiftX - b * 0.5 - a - s, y: 2 * a + b + s, z: 0 }, a, b, c),
            createHexGeometry({x: shiftX - 2 * a - b - s * 2 , y: (2 * a + b + s) * 0.5, z: 0 }, a, b, c, 1),
            createHexGeometry({x: shiftX, y: (2 * a + b + s) * 0.5, z: 0 }, a, b, c, 1),
            createHexGeometry({x: shiftX - b * 0.5 - a - s, y: 0, z: 0 }, a, b, c),
            createHexGeometry({x: shiftX - 2 * a - b - s * 2 , y: -(2 * a + b + s) * 0.5, z: 0 }, a, b, c, 1),
            createHexGeometry({x: shiftX, y: -(2 * a + b + s) * 0.5, z: 0 }, a, b, c, 1),
            createHexGeometry({x: shiftX - b * 0.5 - a - s, y: -(2 * a + b + s), z: 0 }, a, b, c),
        ];
    }

    // 六角柱を作成する
    function createHexGeometry(pos, a, b, c, rot) {
        // 六角柱の蓋を定義する
        const vertices = [
            // 上面の蓋
            new THREE.Vector3(a + b / 2,    0,      c / 2),     // #0
            new THREE.Vector3(b / 2,        a,      c / 2),     // #1
            new THREE.Vector3(-b / 2,        a,      c / 2),    // #2
            new THREE.Vector3(-a - b / 2,    0,      c / 2),    // #3
            new THREE.Vector3(-b / 2,        -a,      c / 2),   // #4
            new THREE.Vector3(b / 2,        -a,      c / 2),    // #5
            new THREE.Vector3(a + b / 2,    0,      -c / 2),    // #6
            new THREE.Vector3(b / 2,        a,      -c / 2),    // #7
            new THREE.Vector3(-b / 2,        a,     -c / 2),    // #8
            new THREE.Vector3(-a - b / 2,    0,      -c / 2),   // #9
            new THREE.Vector3(-b / 2,        -a,      -c / 2),  // #10
            new THREE.Vector3(b / 2,        -a,      -c / 2),   // #11
        ];

        const faces = [
            new THREE.Face3(0, 1, 5), // 上面
            new THREE.Face3(1, 2, 5), // 上面
            new THREE.Face3(2, 4, 5), // 上面
            new THREE.Face3(2, 3, 4), // 上面
            new THREE.Face3(6, 11, 7), // 下面
            new THREE.Face3(7, 11, 8), // 下面
            new THREE.Face3(8, 11, 10), // 下面
            new THREE.Face3(8, 10, 9), // 下面
            new THREE.Face3(0, 6, 7), // 側面 
            new THREE.Face3(7, 1, 0), // 側面
            new THREE.Face3(1, 7, 8), // 側面 
            new THREE.Face3(8, 2, 1), // 側面
            new THREE.Face3(2, 8, 9), // 側面 
            new THREE.Face3(9, 3, 2), // 側面
            new THREE.Face3(3, 9, 10), // 側面 
            new THREE.Face3(10, 4, 3), // 側面
            new THREE.Face3(4, 10, 11), // 側面 
            new THREE.Face3(11, 5, 4), // 側面
            new THREE.Face3(5, 11, 6), // 側面 
            new THREE.Face3(6, 0, 5), // 側面 
        ];

        const geometry = new THREE.Geometry();
        vertices.forEach(v => geometry.vertices.push(v));
        faces.forEach(f => geometry.faces.push(f));
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.x = pos.x;
        mesh.position.y = pos.y;
        mesh.position.z = pos.z;
        if(rot) {
            mesh.rotation.z = Math.PI * 0.5;
        }
        container.add(mesh);
        return mesh;
    }    

    // 毎フレーム時に実行されるループイベントです
    function tick() {

        // 移動情報取得
        updateShifts();

        // 数字を動かす
        for(let i = 0; i < meshes.length; i += 1) {
            const shift = shifts[i];
            for(j = 0; j < 7; j += 1) {
                const delta = shift[j];
                meshes[i][j].position.z += 5 * delta;
                if(meshes[i][j].position.z > 50) {
                    meshes[i][j].position.z = 50;
                } else if(meshes[i][j].position.z < -50) {
                    meshes[i][j].position.z = -50;
                }
            }
        }

        // コンテナを回転させる
        container.rotation.x += rotXShift;
        container.rotation.y += rotYShift;

        if(container.rotation.x >= Math.PI * 0.2
            || container.rotation.x <= -Math.PI * 0.2) {
            rotXShift *= -1;
        }
        if(container.rotation.y >= Math.PI * 0.2 || 
            container.rotation.y <= -Math.PI * 0.2) {
            rotYShift *= -1;
        }

        // レンダリング
        renderer.render(scene, camera);

        requestAnimationFrame(tick);
    }
});
</script>
</head>
<body>
    <canvas id="myCanvas"></canvas>
</body>
</html>

2
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
2
1