Maptalks3DView.js 6.3 KB
import * as echarts from 'echarts/lib/echarts';
import Maptalks3DLayer from './Maptalks3DLayer';
import SceneHelper from '../common/SceneHelper';
import graphicGL from '../../util/graphicGL';

import displayShadowGLSL from '../../util/shader/displayShadow.glsl.js';

graphicGL.Shader.import(displayShadowGLSL);

export default echarts.ComponentView.extend({

    type: 'maptalks3D',

    __ecgl__: true,

    init: function (ecModel, api) {
        this._groundMesh = new graphicGL.Mesh({
            geometry: new graphicGL.PlaneGeometry(),
            material: new graphicGL.Material({
                shader: new graphicGL.Shader({
                    vertex: graphicGL.Shader.source('ecgl.displayShadow.vertex'),
                    fragment: graphicGL.Shader.source('ecgl.displayShadow.fragment')
                }),
                depthMask: false
            }),
            // Render first
            renderOrder: -100,
            culling: false,
            castShadow: false,
            $ignorePicking: true,
            renderNormal: true
        });
    },

    _initMaptalksLayer: function (mapbox3DModel, api) {
        var zr = api.getZr();
        this._zrLayer = new Maptalks3DLayer('maptalks3D', zr, mapbox3DModel.get('center'), mapbox3DModel.get('zoom'));
        zr.painter.insertLayer(-1000, this._zrLayer);

        this._lightRoot = new graphicGL.Node();
        this._sceneHelper = new SceneHelper(this._lightRoot);
        this._sceneHelper.initLight(this._lightRoot);

        var maptalks = this._zrLayer.getMaptalks();
        var dispatchInteractAction = this._dispatchInteractAction.bind(this, api, maptalks);

        // PENDING
        ['zoomend', 'zooming', 'zoomstart', 'dragrotating', 'pitch', 'pitchend', 'movestart',
            'moving', 'moveend', 'resize', 'touchstart', 'touchmove', 'touchend','animating'].forEach(function (eName) {
            maptalks.on(eName, dispatchInteractAction);
        });

    },

    render: function (maptalks3DModel, ecModel, api) {
        if (!this._zrLayer) {
            this._initMaptalksLayer(maptalks3DModel, api);
        }

        var mtks = this._zrLayer.getMaptalks();
        var urlTemplate = maptalks3DModel.get('urlTemplate');

        var baseLayer = mtks.getBaseLayer();
        if (urlTemplate !== this._oldUrlTemplate) {
            if (!baseLayer) {
                baseLayer = new maptalks.TileLayer('maptalks-echarts-gl-baselayer', {
                    urlTemplate: urlTemplate,
                    // used sequentially to help with browser parallel requests per domain limitation
                    subdomains: ['a', 'b', 'c'],
                    attribution: maptalks3DModel.get('attribution')
                });
                mtks.setBaseLayer(baseLayer);
            }
            else {
                // PENDING setOptions may not work?
                baseLayer.setOptions({
                    urlTemplate: urlTemplate,
                    attribution: maptalks3DModel.get('attribution')
                });
            }
        }
        this._oldUrlTemplate = urlTemplate;

        mtks.setCenter(maptalks3DModel.get('center'));
        mtks.setZoom(maptalks3DModel.get('zoom'),{ animation: false });
        mtks.setPitch(maptalks3DModel.get('pitch'));
        mtks.setBearing(maptalks3DModel.get('bearing'));

        maptalks3DModel.setMaptalks(mtks);

        var coordSys = maptalks3DModel.coordinateSystem;

        // Not add to rootNode. Or light direction will be stretched by rootNode scale
        coordSys.viewGL.scene.add(this._lightRoot);
        coordSys.viewGL.add(this._groundMesh);

        this._updateGroundMesh();

        // Update lights
        this._sceneHelper.setScene(coordSys.viewGL.scene);
        this._sceneHelper.updateLight(maptalks3DModel);

        // Update post effects
        coordSys.viewGL.setPostEffect(maptalks3DModel.getModel('postEffect'), api);
        coordSys.viewGL.setTemporalSuperSampling(maptalks3DModel.getModel('temporalSuperSampling'));

        this._maptalks3DModel = maptalks3DModel;
    },

    afterRender: function (maptalks3DModel, ecModel, api, layerGL) {
        var renderer = layerGL.renderer;
        this._sceneHelper.updateAmbientCubemap(renderer, maptalks3DModel, api);
        this._sceneHelper.updateSkybox(renderer, maptalks3DModel, api);

        // FIXME If other series changes coordinate system.
        // FIXME When doing progressive rendering.
        maptalks3DModel.coordinateSystem.viewGL.scene.traverse(function (mesh) {
            if (mesh.material) {
                mesh.material.define('fragment', 'NORMAL_UP_AXIS', 2);
                mesh.material.define('fragment', 'NORMAL_FRONT_AXIS', 1);
            }
        });
    },

    updateCamera: function (maptalks3DModel, ecModel, api, payload) {
        maptalks3DModel.coordinateSystem.setCameraOption(payload);

        this._updateGroundMesh();

        api.getZr().refresh();
    },

    _dispatchInteractAction: function (api, maptalks, maptalks3DModel) {
        api.dispatchAction({
            type: 'maptalks3DChangeCamera',
            pitch: maptalks.getPitch(),
            zoom: getMapboxZoom(maptalks.getResolution()) + 1,
            center: maptalks.getCenter().toArray(),
            bearing: maptalks.getBearing(),
            maptalks3DId: this._maptalks3DModel && this._maptalks3DModel.id
        });
    },

    _updateGroundMesh: function () {
        if (this._maptalks3DModel) {
            var coordSys = this._maptalks3DModel.coordinateSystem;
            var pt = coordSys.dataToPoint(coordSys.center);
            this._groundMesh.position.set(pt[0], pt[1], -0.001);

            var plane = new graphicGL.Plane(new graphicGL.Vector3(0, 0, 1), 0);
            var ray1 = coordSys.viewGL.camera.castRay(new graphicGL.Vector2(-1, -1));
            var ray2 = coordSys.viewGL.camera.castRay(new graphicGL.Vector2(1, 1));
            var pos0 = ray1.intersectPlane(plane);
            var pos1 = ray2.intersectPlane(plane);
            var scale = pos0.dist(pos1) / coordSys.viewGL.rootNode.scale.x;
            this._groundMesh.scale.set(scale, scale, 1);
        }
    },

    dispose: function (ecModel, api) {
        if (this._zrLayer) {
            this._zrLayer.dispose();
        }
        api.getZr().painter.delLayer(-1000);
    }
});

const MAX_RES = 2 * 6378137 * Math.PI / (256 * Math.pow(2, 20));
function getMapboxZoom(res) {
    return 19 - Math.log(res / MAX_RES) / Math.LN2;
}