diff --git a/src/designer/Viewport.ts b/src/designer/Viewport.ts index 81431c5..2ee9b59 100644 --- a/src/designer/Viewport.ts +++ b/src/designer/Viewport.ts @@ -1,11 +1,13 @@ import _ from 'lodash' import * as THREE from 'three' -import { AxesHelper, GridHelper, OrthographicCamera, Scene, WebGLRenderer } from 'three' +import { AxesHelper, GridHelper, OrthographicCamera, Raycaster, Scene, WebGLRenderer } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import Stats from 'three/examples/jsm/libs/stats.module' import type WorldModel from '@/designer/WorldModel.ts' import $ from 'jquery' -import { watch } from 'vue' +import { reactive, watch } from 'vue' +import MouseMoveInspect from '@/designer/model2DEditor/tools/MouseMoveInspect.ts' +import type { ITools } from '@/designer/model2DEditor/tools/ITools.ts' /** * 编辑器对象 @@ -21,8 +23,13 @@ export default class Viewport { statsControls: Stats controls: OrbitControls worldModel: WorldModel + raycaster: Raycaster animationFrameId: any = null + tools: ITools[] = [ + new MouseMoveInspect() + ] + /** * 监听窗口大小变化 */ @@ -30,15 +37,24 @@ export default class Viewport { unwatchList: (() => void)[] = [] - state: ViewportState = { - currentFloor: null, + //@ts-ignore + state: ViewportState = reactive({ + currentFloor: '', isReady: false, cursorMode: 'normal', camera: { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0 } + }, + mouse: { + leftPx: 0, + topPx: 0, + posX: 0, + posZ: 0, + x: 0, + y: 0 } - } + }) constructor(worldModel: WorldModel) { this.worldModel = worldModel @@ -143,6 +159,12 @@ export default class Viewport { this.resizeObserver = new ResizeObserver(this.handleResize.bind(this)) this.resizeObserver.observe(this.viewerDom) + this.raycaster = new Raycaster() + + for (const tool of this.tools) { + tool.init(this) + } + this.state.isReady = true } @@ -182,12 +204,12 @@ export default class Viewport { controlsNew.screenSpacePanning = false // 定义平移时如何平移相机的位置 控制不上下移动 controlsNew.listenToKeyEvents(viewerDom) // 监听键盘事件 controlsNew.keys = { LEFT: 'KeyA', UP: 'KeyW', RIGHT: 'KeyD', BOTTOM: 'KeyS' } + controlsNew.addEventListener('change', this.syncCameraState.bind(this)) controlsNew.panSpeed = 1 controlsNew.keyPanSpeed = 20 // normal 7 controlsNew.minDistance = 0.1 controlsNew.maxDistance = 1000 this.controls = controlsNew - controlsNew.addEventListener('change', this.syncCameraState.bind(this)) this.camera.updateProjectionMatrix() @@ -279,7 +301,6 @@ export default class Viewport { // 修改网格材质透明度 this.gridHelper.material.opacity = opacity this.gridHelper.visible = opacity > 0 - console.log('opacity', opacity) } destroy() { @@ -297,6 +318,15 @@ export default class Viewport { this.unwatchList = [] } + if (this.tools) { + for (const tool of this.tools) { + if (tool.destory) { + tool.destory() + } + } + this.tools = [] + } + if (this.resizeObserver) { this.resizeObserver.unobserve(this.viewerDom) this.resizeObserver.disconnect() @@ -316,6 +346,15 @@ export default class Viewport { this.renderer.domElement = null } } + + /** + * 获取鼠标在设计图上的坐标 + */ + getGridHelpAtPosition(param: { x: number; z: number }) { + const pickPosition = new THREE.Vector2(param.x, param.z) + this.raycaster.setFromCamera(pickPosition, this.camera) + return this.raycaster.intersectObject(this.gridHelper) + } } export interface ViewportState { @@ -341,4 +380,27 @@ export interface ViewportState { position: { x: number, y: number, z: number }, rotation: { x: number, y: number, z: number } } + + /** + * 鼠标位置(归一化坐标) + */ + mouse: { + /* + * 鼠标在 DOM 上的位置 + */ + leftPx: number, + topPx: number, + + /** + * 鼠标在 THREE 中的归一化坐标 + */ + posX: number, + posZ: number, + + /** + * 鼠标在设计图上的坐标 + */ + x: number, + z: number + } } \ No newline at end of file diff --git a/src/designer/model2DEditor/Model2DEditor.vue b/src/designer/model2DEditor/Model2DEditor.vue index 0e9a4a4..f4dcffb 100644 --- a/src/designer/model2DEditor/Model2DEditor.vue +++ b/src/designer/model2DEditor/Model2DEditor.vue @@ -9,7 +9,7 @@ :props="{emitPath:false}" />
-
@@ -56,7 +56,7 @@
- X=14.091,Y=12.397 + {{ state.mouse.x.toFixed(2) }},{{ state.mouse.z.toFixed(2) }}
diff --git a/src/designer/model2DEditor/tools/ITools.ts b/src/designer/model2DEditor/tools/ITools.ts new file mode 100644 index 0000000..21a0a8f --- /dev/null +++ b/src/designer/model2DEditor/tools/ITools.ts @@ -0,0 +1,5 @@ +export interface ITools { + init(viewport: any): void + + destory(): void +} \ No newline at end of file diff --git a/src/designer/model2DEditor/tools/MouseMoveInspect.ts b/src/designer/model2DEditor/tools/MouseMoveInspect.ts index 54349b5..bd97d7c 100644 --- a/src/designer/model2DEditor/tools/MouseMoveInspect.ts +++ b/src/designer/model2DEditor/tools/MouseMoveInspect.ts @@ -1,16 +1,56 @@ -import Editor from '@/designer/Editor.js' import type Viewport from '@/designer/Viewport.ts' +import type { ITools } from '@/designer/model2DEditor/tools/ITools.ts' /** * 鼠标移动时,将鼠标位置的坐标转换为设计图上的坐标,并设置到 designer.mousePos 属性中 */ -export default class MouseMoveInspect { +export default class MouseMoveInspect implements ITools { viewport: Viewport - constructor(viewport: Viewport) { + constructor() { + } + + init(viewport: Viewport) { this.viewport = viewport + const viewerDom = this.viewport.viewerDom + + viewerDom.addEventListener('mousemove', this.mouseMove.bind(this)) } - mouseMove(event) { + destory() { + const viewerDom = this.viewport.viewerDom + viewerDom.removeEventListener('mousemove', this.mouseMove.bind(this)) } + + mouseMove = _.throttle(function(this: MouseMoveInspect, event: MouseEvent) { + const viewer = this.viewport.viewerDom + // 获取鼠标在three.js 中的归一化坐标 取值范围是 (-1 to +1) + const rect = viewer.getBoundingClientRect() + + this.viewport.state.mouse.leftPx = event.clientX - rect.left + this.viewport.state.mouse.topPx = event.clientY - rect.top + + this.viewport.state.mouse.posX = ((event.clientX - rect.left) / rect.width) * 2 - 1 + this.viewport.state.mouse.posZ = ((event.clientY - rect.top) / rect.height) * -2 + 1 + + const intersects = this.viewport.getGridHelpAtPosition({ + x: this.viewport.state.mouse.posX, + z: this.viewport.state.mouse.posZ + }) + if (!intersects || intersects.length < 2) { + this.viewport.state.mouse.x = NaN + this.viewport.state.mouse.z = NaN + return + } + + if (!_.every(intersects, (intersect) => intersect.object.type === 'GridHelper')) { + this.viewport.state.mouse.x = NaN + this.viewport.state.mouse.z = NaN + return + } + + this.viewport.state.mouse.x = Math.floor(intersects[0].point.x * 10) / 10 + this.viewport.state.mouse.z = Math.floor(intersects[0].point.z * 10) / 10 + + }, 1) } \ No newline at end of file diff --git a/src/designer/model3DView/Model3DView.vue b/src/designer/model3DView/Model3DView.vue index 728d3f9..5ca10e7 100644 --- a/src/designer/model3DView/Model3DView.vue +++ b/src/designer/model3DView/Model3DView.vue @@ -91,7 +91,7 @@ const transformEditCtl = ref(null) // Three.js 场景相关 let scene, camera, renderer, controls -let statsControls, axesHelper, gridHelper +let statsControls, axesHelper, gridHelper, animationFrameId let gui, tcontrols, modelGroup, resizeObserver const restate = reactive({ @@ -130,6 +130,11 @@ onMounted(() => { }) onBeforeUnmount(() => { + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId) + animationFrameId = null + } + cleanupThree() const viewerDom = canvasContainer.value @@ -245,7 +250,7 @@ function initThree() { // 动画循环 function animate() { - requestAnimationFrame(animate) + animationFrameId = requestAnimationFrame(animate) renderView() }