@orca-fe/x-map
Version:
302 lines (301 loc) • 12.8 kB
JavaScript
/* eslint-disable prefer-destructuring */
import { AmbientLight, AxesHelper, DirectionalLight, GridHelper, Matrix4, PerspectiveCamera, Raycaster, Scene, Vector2, WebGLRenderer, } from 'three';
import ResizeObserver from 'resize-observer-polyfill';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { EventEmitter } from 'events';
export default class SimpleThree extends EventEmitter {
constructor(dom, options = {}) {
super();
this.scene = new Scene();
this.rayCasterObjects = new Set();
this.camera = new PerspectiveCamera(50, 1, 1, 10000);
this.mousedown = false;
this.resizeTime = Date.now();
this.autoResize = false;
this.handleWindowMouseup = () => {
this.mousedown = false;
};
this.handleWindowResize = () => {
const now = Date.now();
if (now - this.resizeTime < 20)
return;
this.computeSize();
const [width, height] = this.domSize;
this.emit('resize', { width, height });
if (this.autoResize) {
this.resize();
}
this.resizeTime = now;
};
this.disposed = false;
this.options = options;
const { axesAndGround, rendererOptions, cameraLookAt = [0, 0, 0], cameraPosition, autoResize = true, background, orbitControl, ambientLightColor, ambientLightIntensity = 1, preventDefaultRender = false, rayCaster = true, camera, map, } = options;
this.dom = dom;
this.map = map;
this.preventDefaultRender = preventDefaultRender;
this.domSize = [this.dom.clientWidth, this.dom.clientHeight];
this.autoResize = autoResize;
// 射线检测内容是否被选中
if (rayCaster) {
const raycaster = new Raycaster();
raycaster.setFromCamera({ x: 0.5, y: 0.5 }, this.camera);
const mouse = new Vector2();
let hoverObject = undefined;
/* 鼠标移动,检查物体是否被选中 */
dom.addEventListener('mousemove', (event) => {
var _a;
if (this.mousedown)
return;
if (this.rayCasterObjects.size <= 0)
return;
// const [clientWidth, clientHeight] = this.domSize;
const { left, top, width, height } = this.dom.getBoundingClientRect();
const xPercent = (event.clientX - left) / width;
const yPercent = (event.clientY - top) / height;
// 计算得出鼠标在屏幕的坐标百分比(-1 ~ 1)
mouse.x = xPercent * 2 - 1;
mouse.y = -yPercent * 2 + 1;
// if (!this.cameraProjectionMatrix) return;
// if (!this.cameraViewMatrix) return;
// if (!this.map) return;
// const { threeCenter } = this.map;
// const camInverseProjection = this.cameraViewMatrix.multiply(this.cameraProjectionMatrix).invert();
// 计算出相机位置的起始点坐标
// const cameraPosition = new Vector3(mouse.x, mouse.y, 0).applyMatrix4(camInverseProjection);
// 计算出大地位置的结束点坐标(利用经纬度转像素的API计算)
// const [lng, lat] = this.map.pixelToLnglat([xPercent * clientWidth, yPercent * clientHeight]);
// const [mercatorX, mercatorY] = lonLat2Mercator([lng, lat]).map((value, index) => value - threeCenter[index]);
// const targetPosition = new Vector3(mercatorX, mercatorY, 0);
// 利用起始点和结束点构建射线
// raycaster.set(cameraPosition, targetPosition.clone().sub(cameraPosition)
// .normalize());
raycaster.setFromCamera(mouse, this.camera);
// 得到当前鼠标射线命中的内容
const intersects = raycaster.intersectObjects([...this.rayCasterObjects]);
let currentHoverObject = (_a = intersects[0]) === null || _a === void 0 ? void 0 : _a.object;
if (currentHoverObject) {
while (currentHoverObject.parent && currentHoverObject.parent.type !== 'Scene') {
currentHoverObject = currentHoverObject.parent;
}
}
if ((hoverObject === null || hoverObject === void 0 ? void 0 : hoverObject.object) !== currentHoverObject) {
if (hoverObject) {
this.emit('mouseleave', hoverObject, event);
}
if (currentHoverObject) {
this.emit('mouseenter', intersects[0], event);
}
hoverObject = intersects[0];
}
});
const currentPosition = { x: 0, y: 0 };
dom.addEventListener('mousedown', (event) => {
this.mousedown = true;
if (hoverObject) {
currentPosition.x = event.clientX;
currentPosition.y = event.clientY;
this.emit('mousedown', hoverObject, event);
}
});
dom.addEventListener('mouseup', (event) => {
this.mousedown = false;
if (hoverObject) {
this.emit('mouseup', hoverObject, event);
if (currentPosition.x === event.clientX && currentPosition.y === event.clientY) {
this.emit('click', hoverObject, event);
}
}
});
window.addEventListener('mouseup', this.handleWindowMouseup);
}
const canvas = document.createElement('canvas');
canvas.width = 1440;
canvas.height = 900;
canvas.style.width = '100%';
canvas.style.height = '100%';
this.dom.appendChild(canvas);
this.renderer = new WebGLRenderer(Object.assign(Object.assign({}, rendererOptions), { canvas }));
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.autoClear = false;
if (background) {
this.scene.background = background;
}
if (ambientLightColor) {
this.scene.add(new AmbientLight(ambientLightColor, ambientLightIntensity));
}
if (axesAndGround) {
this.addGrid();
this.addAxes();
}
if (camera) {
this.camera = camera;
}
if (cameraPosition) {
this.camera.position.set(...cameraPosition);
}
this.camera.lookAt(...cameraLookAt);
this.observer = new ResizeObserver((entries) => {
this.handleWindowResize();
});
this.observer.observe(this.dom);
if (autoResize) {
this.resize();
}
if (orbitControl) {
console.warn('orbitControl running');
this.control = new OrbitControls(this.camera, this.renderer.domElement);
this.control.minDistance = orbitControl[0];
this.control.maxDistance = orbitControl[1];
this.control.screenSpacePanning = false;
this.control.update();
this.control.addEventListener('change', () => {
requestAnimationFrame(() => {
this.render();
});
});
}
}
computeSize() {
if (this.dom) {
this.domSize = [this.dom.clientWidth, this.dom.clientHeight];
}
}
dispose() {
var _a;
if (this.disposed)
return;
this.disposed = true;
console.warn('dispose');
if (this.observer) {
console.warn('disconnect');
this.observer.disconnect();
}
this.dom.innerHTML = '';
(_a = this.control) === null || _a === void 0 ? void 0 : _a.dispose();
this.stop();
this.renderer.dispose();
// @ts-expect-error
delete this.renderer;
// @ts-expect-error
delete this.dom;
delete this.control;
delete this.observer;
}
/**
* 添加平行光
* @param options 平行光选项
* @returns
*/
addDirectionalLight(options = {}) {
const { direction = [1, 1, 1], color = 0xffffff, intensity = 1 } = options;
const light = new DirectionalLight(color, intensity);
if (direction)
light.position.set(...direction);
this.scene.add(light);
return light;
}
/**
* 添加坐标轴方向
* @param size 长度
*/
addAxes(size = 2000) {
this.removeAxes();
this.axes = new AxesHelper(size);
this.scene.add(this.axes);
return this.axes;
}
removeAxes() {
if (this.axes) {
this.scene.remove(this.axes);
this.axes = undefined;
}
}
/**
* 添加大地网格
* @param size 网格大小
* @param divisions 网格拆分数量
* @param color1 横向网格线颜色
* @param color2 纵向网格线颜色
*/
addGrid(size = 50, divisions = 10, color1, color2) {
this.removeGrid();
this.grid = new GridHelper(size, divisions, color1, color2);
this.scene.add(this.grid);
return this.grid;
}
removeGrid() {
if (this.grid) {
this.scene.remove(this.grid);
this.grid = undefined;
}
}
updateMatrix(matrix, render = true) {
if (this.disposed)
return;
this.cameraProjectionMatrix = Array.isArray(matrix[0]) ? new Matrix4().fromArray(matrix[0]) : matrix[0];
this.cameraViewMatrix = Array.isArray(matrix[1]) ? new Matrix4().fromArray(matrix[1]) : matrix[1];
if (render)
this.render(false);
}
animate(callback = () => { }) {
this.stop();
if (this.disposed)
return;
const fn = () => {
this.animationKey = requestAnimationFrame(fn);
const res = callback();
if (res !== false)
this.render(false);
};
this.animationKey = requestAnimationFrame(fn);
}
stop() {
if (this.animationKey) {
cancelAnimationFrame(this.animationKey);
this.animationKey = undefined;
}
}
render(clear = true) {
if (this.disposed)
return;
if (this.cameraProjectionMatrix && this.cameraViewMatrix) {
const [width, height] = this.domSize;
// const projectionMatrix = mat4.create();
// const projectionMatrixI = mat4.create();
// const viewMatrix = mat4.create();
// const viewMatrixI = mat4.create();
// const projectionViewMatrix = this.cameraProjectionMatrix.multiply(this.cameraProjectionMatrix);
// mat4.perspective(projectionMatrix, fov, width / height, 1, far);
// mat4.invert(projectionMatrixI, projectionMatrix);
// mat4.multiply(viewMatrix, projectionMatrixI, projectionViewMatrix.elements as Mat4);
// mat4.invert(viewMatrixI, viewMatrix);
// console.log(this.camera.projectionMatrix.elements);
// this.camera.projectionMatrix = new Matrix4().fromArray(projectionMatrix);
this.camera.matrix = this.cameraViewMatrix.invert();
this.camera.aspect = width / height;
this.camera.matrix.decompose(this.camera.position, this.camera.quaternion, this.camera.scale);
this.camera.projectionMatrix = this.cameraProjectionMatrix;
this.camera.projectionMatrixInverse = this.cameraProjectionMatrix.clone().invert();
// this.camera.updateProjectionMatrix();
}
if (this.preventDefaultRender) {
this.emit('render', clear);
return;
}
if (clear) {
this.renderer.clear();
}
this.renderer.resetState();
this.renderer.render(this.scene, this.camera);
}
resize() {
if (this.disposed)
return;
if (document.body.contains(this.dom)) {
const [width, height] = this.domSize;
this.camera.aspect = width / height;
this.renderer.setSize(width, height);
this.render(true);
}
}
}