plugins/threebox/threebox.js

import * as THREE from 'three';

var ThreeboxConstants = require('./constants.js');
var CameraSync = require('./camera/CameraSync.js');

export default class Threebox {
    constructor(map) {

        this.map = map;

        // 创建一个three场景(WebGLRenderer)
        this.renderer = new THREE.WebGLRenderer({alpha: true, antialias: true});
        this.renderer.setSize(this.map.transform.width, this.map.transform.height);

        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMapSoft = true;

        this.map['_container'].appendChild(this.renderer.domElement);
        this.renderer.domElement.style['position'] = 'relative';
        this.renderer.domElement.style['pointer-events'] = 'none';
        this.renderer.domElement.style['z-index'] = 1000;


        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(28, window.innerWidth / window.innerHeight, 0.000001, 5000000000);
        this.layers = [];

        // The CameraSync object will keep the Mapbox and THREE.js camera movements in sync.
        // It requires a world group to scale as we zoom in. Rotation is handled in the camera's
        // projection matrix itself (as is field of view and near/far clipping)
        // It automatically registers to listen for move events on the map so we don't need to do that here
        this.world = new THREE.Group();
        this.scene.add(this.world);
        this.cameraSynchronizer = new CameraSync(this.map, this.camera, this.world);

        this.mapResizeEvent = () => {

            this.renderer.setSize(this.map.transform.width, this.map.transform.height);
            this.update();

        };
        this.mapMoveEvent = () => {

            this.update();

        };
        this.map.on('resize', this.mapResizeEvent);
        this.map.on('move', this.mapMoveEvent);
        this.setupDefaultLights();

    }

    /**
     * 更新three场景
     */
    update() {

        this.renderer.render(this.scene, this.camera);

    }

    projectToWorld(coords) {

        // Spherical mercator forward projection, re-scaling to WORLD_SIZE
        var projected = [
            -ThreeboxConstants.MERCATOR_A * coords[0] * ThreeboxConstants.DEG2RAD * ThreeboxConstants.PROJECTION_WORLD_SIZE,
            -ThreeboxConstants.MERCATOR_A * Math.log(Math.tan((Math.PI * 0.25) + (0.5 * coords[1] * ThreeboxConstants.DEG2RAD))) * ThreeboxConstants.PROJECTION_WORLD_SIZE
        ];

        var pixelsPerMeter = this.projectedUnitsPerMeter(coords[1]);

        //z dimension
        var height = coords[2] || 0;
        projected.push(height * pixelsPerMeter);

        var result = new THREE.Vector3(projected[0], projected[1], projected[2]);

        return result;

    }

    projectedUnitsPerMeter(latitude) {

        return Math.abs(ThreeboxConstants.WORLD_SIZE * (1 / Math.cos(latitude * Math.PI / 180)) / ThreeboxConstants.EARTH_CIRCUMFERENCE);

    }


    unprojectFromWorld(pixel) {

        var unprojected = [
            -pixel.x / (ThreeboxConstants.MERCATOR_A * ThreeboxConstants.DEG2RAD * ThreeboxConstants.PROJECTION_WORLD_SIZE),
            2 * (Math.atan(Math.exp(pixel.y / (ThreeboxConstants.PROJECTION_WORLD_SIZE * (-ThreeboxConstants.MERCATOR_A)))) - Math.PI / 4) / ThreeboxConstants.DEG2RAD
        ];

        var pixelsPerMeter = this.projectedUnitsPerMeter(unprojected[1]);

        //z dimension
        var height = pixel.z || 0;
        unprojected.push(height / pixelsPerMeter);

        return unprojected;

    }


    addAtCoordinate(obj, lnglat, options) {

        var geoGroup = new THREE.Group();
        geoGroup.userData.isGeoGroup = true;
        geoGroup.add(obj);
        this.world.add(geoGroup);
        this.moveToCoordinate(obj, lnglat, options);

        // Bestow this mesh with animation superpowers and keeps track of its movements in the global animation queue
        //this.animationManager.enroll(obj);

        //return obj;
        this.update();
        return geoGroup;  //返回的是一个geoGroup?(测试中)

    }

    moveToCoordinate(obj, lnglat, options) {

        /** Place the given object on the map, centered around the provided longitude and latitude
         The object's internal coordinates are assumed to be in meter-offset format, meaning
         1 unit represents 1 meter distance away from the provided coordinate.
         */
        if (options === undefined) options = {};
        if (options.preScale === undefined) options.preScale = 1.0;
        if (options.scaleToLatitude === undefined || obj.userData.scaleToLatitude) options.scaleToLatitude = true;

        obj.userData.scaleToLatitude = options.scaleToLatitude;

        if (typeof options.preScale === 'number') options.preScale = new THREE.Vector3(options.preScale, options.preScale, options.preScale);
        else if (options.preScale.constructor === Array && options.preScale.length === 3) options.preScale = new THREE.Vector3(options.preScale[0], options.preScale[1], options.preScale[2]);
        else if (options.preScale.constructor !== THREE.Vector3) {

            console.warn('Invalid preScale value: number, Array with length 3, or THREE.Vector3 expected. Defaulting to [1,1,1]');
            options.preScale = new THREE.Vector3(1, 1, 1);

        }

        var scale = options.preScale;

        // Figure out if this object is a geoGroup and should be positioned and scaled directly, or if its parent
        var geoGroup;
        if (obj.userData.isGeoGroup) geoGroup = obj;
        else if (obj.parent && obj.parent.userData.isGeoGroup) geoGroup = obj.parent;
        else return console.error('Cannot set geographic coordinates of object that does not have an associated GeoGroup. Object must be added to scene with \'addAtCoordinate()\'.');

        if (options.scaleToLatitude) {

            // Scale the model so that its units are interpreted as meters at the given latitude
            var pixelsPerMeter = this.projectedUnitsPerMeter(lnglat[1]);
            scale.multiplyScalar(pixelsPerMeter);

        }

        geoGroup.scale.copy(scale);
        geoGroup.position.copy(this.projectToWorld(lnglat));
        obj.coordinates = lnglat;

        this.update();

        return geoGroup;

    }


    remove(obj) {

        this.world.remove(obj);

    }

    setupDefaultLights() {

        this.scene.add(new THREE.AmbientLight('#cccccc'));
        let sunlight = new THREE.DirectionalLight(0xffffff, 0.5);
        sunlight.position.set(0, 800, 1000);
        sunlight.matrixWorldNeedsUpdate = true;
        this.world.add(sunlight);

    }

    removeThreeBoxScene() {

        if (this.scene) {

            let groups = [];
            this.scene.traverse(obj => {

                groups.push(obj);

            });
            groups.forEach(data => {

                this.scene.remove(data);

            });

            this.map['_container'].removeChild(this.renderer.domElement);
            this.map.off('resize', this.mapMoveEvent);
            this.map.off('move', this.mapMoveEvent);

        }


    }
}