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);
}
}
}