Closed20

three-geoをコードリーディングする

ぺるきぺるき

動機

  • three.jsを知っておきたい
  • DEMで遊びたい

目標

  • 参考とするライブラリとしてthree-geoを読む
ぺるきぺるき

examples/simple-viewer をざっくり眺める

    (async () => {
        THREE.Object3D.DefaultUp = new THREE.Vector3(0, 0, 1);

        const canvas = document.getElementById("canvas");
        const camera = new THREE.PerspectiveCamera(75, canvas.width/canvas.height, 0.1, 1000);
        camera.position.set(0, 0, 1.5);

        const renderer = new THREE.WebGLRenderer({ canvas });
        const controls = new THREE.OrbitControls(camera, renderer.domElement);

        const scene = new THREE.Scene();
        const walls = new THREE.LineSegments(
            new THREE.EdgesGeometry(new THREE.BoxBufferGeometry(1, 1, 1)),
            new THREE.LineBasicMaterial({color: 0xcccccc}));
        walls.position.set(0, 0, 0);
        scene.add(walls);
        scene.add(new THREE.AxesHelper(1));

        const stats = new Stats();
        stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom
        document.body.appendChild(stats.dom);
        const render = () => {
            stats.update();
            renderer.render(scene, camera);
        };

        controls.addEventListener('change', render);
        render(); // first time

        const ioToken = 'pk.eyJ1IjoiamRldmVsIiwiYSI6ImNqemFwaGJoZjAyc3MzbXA1OGNuODBxa2EifQ.7M__SgfWZGJuEiSqbBXdoQ';
        
        const tgeo = new ThreeGeo({
            tokenMapbox: 'pk.秘密', // <---- set your Mapbox API token here
        });

        if (!window.location.origin.startsWith('https://w3reality.github.io') && tgeo.tokenMapbox === ioToken) {
            const warning = 'Please set your Mapbox API token in ThreeGeo constructor.';
            alert(warning);
            throw warning;
        }

        const terrain = await tgeo.getTerrainRgb(
            [46.5763, 7.9904], // [lat, lng]
            5.0,               // radius of bounding circle (km)
            12);               // zoom resolution

        scene.add(terrain);

        render();
    })();
ぺるきぺるき

tgeo.getTerrainRgbTHREE.Groupクラスを返す

このためscene.add(terrain);で直接追加できる

ぺるきぺるき

tgeoThreeGeoオブジェクトのインスタンス

src/index.jsでは、定義とデフォルト値が以下のように設定される

const defaults = {
    unitsSide: 1.0,
    tokenMapbox: '',
    isNode: false,
    isDebug: false,
    apiVector: 'mapbox-terrain-vector',
    apiRgb: 'mapbox-terrain-rgb',
    apiSatellite: 'mapbox-satellite',
};

exampleではtokenMapboxのみを上書き

ぺるきぺるき

getTerrainRgb

async getTerrainRgb(origin, radius, zoom, _cb=undefined) {
    const { rgbDem: objs, debug } = await this.getTerrain(origin, radius, zoom, {
        // Set dummy callbacks to trigger rgb DEM fetching
        onRgbDem: () => {},
        onSatelliteMat: () => {},
    });
    return _cb ? _cb(objs) : ThreeGeo._createDemGroup('dem-rgb', objs, debug);
}

予想できる流れは

  • getTerrain でMapboxから衛星データを取得
  • _createDemGroupTHREE.Groupのインスタンスとして作成?
ぺるきぺるき

getTerrain はPromiseを返す

getTerrain(origin, radius, zoom, cbs={}) {
        return new Promise((res, rej) => {
            ...
            try{
                ...
                if (onRgbDem) {
                    (new RgbModel({
                        unitsPerMeter, projectCoord,
                        token, isNode, isDebug, apiRgb, apiSatellite,
                        onRgbDem, onSatelliteMat, watcher,
                    })).fetch(zpCovered, bbox);
                }

                if (onVectorDem) {
                    (new VectorModel({
                        unitsPerMeter, projectCoord,
                        token, isNode, isDebug, apiVector,
                        onVectorDem, watcher,
                    })).fetch(zpCovered, bbox, radius);
                }
            } catch (err) {
            ...
    }
}

引数に与えられた各種パラメータを整理し、RgbModelあるいはVectorModelに整形
それぞれのクラスのメソッドfetchで衛星データをMapboxのAPIから取得し返すものと思われる

ぺるきぺるき

getTerrainRgbを見返すと、getTerrainobjsを返している
_createDemGroupはそれを単にまとめる静的メソッドだった

    static _createDemGroup(name, objs, debug) {
        const group = new THREE.Group();
        group.name = name;
        group.userData['debug'] = () => {
            if (!debug) console.warn('Use the `isDebug` option to enable `.userData.debug()`.');
            return debug;
        };
        for (let obj of objs) { group.add(obj); }
        return group;
    }
ぺるきぺるき

RgbModel

   fetch(zpCovered, bbox) {
        // e.g. satellite's zoom: 14
        //      dem's zoom: 12 (=14-2)
        const zpEle = Fetch.getZoomposEle(zpCovered);
        console.log('RgbModel: zpEle:', zpEle);

        let count = 0;
        zpEle.forEach(async zoompos => {
            const tile = await Fetch.fetchTile(zoompos, this.apiRgb, this.token, this.isNode);
            if (tile !== null) {
                this.addTile(tile, zoompos, zpCovered, bbox);
            } else {
                console.log(`fetchTile() failed for rgb dem of zp: ${zoompos} (count: ${count}/${zpEle.length})`);
            }

            count++;
            if (count === zpEle.length) {
                this.build();
            }
        });
    }

指定されたboundingboxをカバーする最小限のzoompos(zpCovered) の構成から、それぞれに対応する各tileを取得し、データを取得

zpCoveredはこのライブラリを使って作られていた

https://github.com/mapbox/tile-cover

getZoomposEleとは何だろう?

// e.g. satellite's zoom: 14
//      dem's zoom: 12 (=14-2)

この文言も気になる
DEMを持ってくる段階でzoomの値を調整する必要がある?

ぺるきぺるき

tileを取得するときは、fetchTileで生のデータを取得、addTileで整形して追加

ぺるきぺるき

一旦終了。現在の関心は

  • addTileは何をするのか
  • THREE.Meshがどこで作成されているのか

Fetch.getZoomposEle (fetch.js) が何をする関数なのかについては、気が向いたとき有識者に伺ってみたい

ぺるきぺるき

addTiles()はタイルの生データから標高などの具体値を取得しthis.dataEleCoveredに格納しているようだが、独特かつ説明なしのコンテキストが多く解釈しづらい
build()からみてみる

ぺるきぺるき

    build() {
       ...
        const meshes = RgbModel._build(
            this.dataEleCovered, this.apiSatellite,
            this.token, this.isNode, onSatelliteMatWrapper);

        this.onRgbDem(meshes); // legacy API
        ...
    }

_buildの流れとしては、addTile()で作成したデータからMeshを作成し、外から与えられたコールバックonRgbDemに渡す

ぺるきぺるき

_build()

    static _build(dataEle, apiSatellite, token, isNode, onSatelliteMatWrapper) {
        ...
        const objs = [];
        dataEle.forEach(([zoompos, arr, zoomposEle]) => {
            ...

            let geom = new THREE.PlaneBufferGeometry(
                1, 1, cSegments[0], cSegments[1]);
            geom.attributes.position.array = new Float32Array(arr);

            // test identifying a 127x1 "belt"
            // let geom = new THREE.PlaneBufferGeometry(1, 1, 127, 1);
            // let arrBelt = arr;
            // arrBelt.length = 128*2*3;
            // geom.attributes.position.array = new Float32Array(arrBelt);

            let plane = new THREE.Mesh(geom,
                new THREE.MeshBasicMaterial({
                    wireframe: true,
                    color: 0xcccccc,
                }));
            plane.name = `dem-rgb-${zoompos.join('/')}`;
            const _toTile = zp => [zp[1], zp[2], zp[0]];
            plane.userData.threeGeo = {
                tile: _toTile(zoompos),
                srcDem: {
                    tile: _toTile(zoomposEle),
                    uri: Fetch.getUriMapbox(token, 'mapbox-terrain-rgb', zoomposEle),
                },
            };
            objs.push(plane);

            this.resolveTex(zoompos, apiSatellite, token, isNode, tex => {
                //console.log(`resolve tex done for ${zoompos}`);
                if (tex) {
                    plane.material = new THREE.MeshBasicMaterial({
                        side: THREE.FrontSide,
                        // side: THREE.DoubleSide,
                        map: tex,
                    });
                }

                if (onSatelliteMatWrapper) {
                    onSatelliteMatWrapper(plane, objs);
                }
            });
        });
        return objs;
    }
ぺるきぺるき

resolveTexはテスクチャを作成し、各planeのデフォルトのテクスチャを上書きしている
内部ではfetchTileがまた呼び出されているため、航空画像を取得すると思われる
航空画像データを表すtexTHREE.DataTexture形式で与えられ、マテリアルTHREE.MeshBasicMaterialに用いる画像として渡される

THREE.MeshBasicMaterialについては以下を参考
https://threejs.org/docs/#api/en/materials/MeshBasicMaterial
https://zenn.dev/raihara3/articles/20220505_threejs_material

ぺるきぺるき

まとめ

  • 地形をTHREE.PlaneBufferGeometryをジオメトリとしてTHREE.Meshで表現
  • THREE.MeshBasicMaterialでマテリアルを定義
  • テクスチャはTHREE.DataTextureから作成

これだけを知るにはメモしたことが多すぎた

このスクラップは2024/07/19にクローズされました