UNPKG

@orca-fe/x-map

Version:
302 lines (301 loc) 12.8 kB
/* 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); } } }