diff --git a/src/designer/Viewport.ts b/src/designer/Viewport.ts index c50d8bc..eb2f360 100644 --- a/src/designer/Viewport.ts +++ b/src/designer/Viewport.ts @@ -10,6 +10,8 @@ import { reactive, watch } from 'vue' import MouseMoveInspect from '@/designer/model2DEditor/tools/MouseMoveInspect.ts' import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts' import RulerTool from '@/designer/model2DEditor/tools/RulerTool.ts' +import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer' +import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer' /** * 编辑器对象 @@ -39,8 +41,14 @@ export default class Viewport { */ resizeObserver?: ResizeObserver + /** + * vue 的 watcher + */ watchList: (() => void)[] = [] + css2DRenderer: CSS2DRenderer = new CSS2DRenderer() + css3DRenderer: CSS3DRenderer = new CSS3DRenderer() + //@ts-ignore state: ViewportState = reactive({ currentFloor: '', @@ -75,6 +83,7 @@ export default class Viewport { console.log('init floor', floor) this.state.currentFloor = floor this.viewerDom = viewerDom + const rect = viewerDom.getBoundingClientRect() this.worldModel.registerViewport(this) // 场景 @@ -96,9 +105,36 @@ export default class Viewport { renderer.clearDepth() renderer.shadowMap.enabled = true renderer.toneMapping = THREE.ACESFilmicToneMapping - renderer.setPixelRatio(window.devicePixelRatio) - renderer.setSize(viewerDom.getBoundingClientRect().width, viewerDom.getBoundingClientRect().height) + renderer.setPixelRatio(Math.max(Math.ceil(window.devicePixelRatio), 1)); + renderer.setViewport(0, 0, this.viewerDom.offsetWidth, this.viewerDom.offsetHeight) + renderer.setSize(this.viewerDom.offsetWidth, this.viewerDom.offsetHeight) + viewerDom.appendChild(renderer.domElement) + + renderer.domElement.style.touchAction = 'none' + + // 防止重复添加 + if (this.css2DRenderer.domElement.parentNode !== this.viewerDom) { + this.css2DRenderer.setSize(this.viewerDom.offsetWidth, this.viewerDom.offsetHeight) + this.css2DRenderer.domElement.setAttribute('id', 'astral-3d-preview-css2DRenderer') + this.css2DRenderer.domElement.style.position = 'absolute' + this.css2DRenderer.domElement.style.top = '0px' + this.css2DRenderer.domElement.style.pointerEvents = 'none' + + this.viewerDom.appendChild(this.css2DRenderer.domElement) + } + + // 防止重复添加 + if (this.css3DRenderer.domElement.parentNode !== this.viewerDom) { + this.css3DRenderer.setSize(this.viewerDom.offsetWidth, this.viewerDom.offsetHeight) + this.css3DRenderer.domElement.setAttribute('id', 'astral-3d-preview-css3DRenderer') + this.css3DRenderer.domElement.style.position = 'absolute' + this.css3DRenderer.domElement.style.top = '0px' + this.css3DRenderer.domElement.style.pointerEvents = 'none' + + this.viewerDom.appendChild(this.css3DRenderer.domElement) + } + this.renderer = renderer // 创建正交摄像机 @@ -268,6 +304,9 @@ export default class Viewport { renderView() { this.statsControls?.update() this.renderer?.render(this.scene, this.camera) + + this.css2DRenderer.render(this.scene, this.camera) + this.css3DRenderer.render(this.scene, this.camera) } /** diff --git a/src/designer/model2DEditor/Model2DEditorJs.js b/src/designer/model2DEditor/Model2DEditorJs.js index a80a46e..41fa38e 100644 --- a/src/designer/model2DEditor/Model2DEditorJs.js +++ b/src/designer/model2DEditor/Model2DEditorJs.js @@ -1,3 +1,4 @@ +import * as THREE from 'three' import { renderIcon } from '@/utils/webutils.ts' import { defineComponent, markRaw } from 'vue' import Viewport from '@/designer/Viewport.ts' @@ -58,6 +59,7 @@ export default defineComponent({ viewport.initThree(viewerDom, floor) window['viewport'] = viewport + window['THREE'] = THREE window['scene'] = viewport.scene window['renderer'] = viewport.renderer window['camera'] = viewport.camera diff --git a/src/designer/model2DEditor/tools/RulerTool.ts b/src/designer/model2DEditor/tools/RulerTool.ts index 9ee3e95..23ffd6f 100644 --- a/src/designer/model2DEditor/tools/RulerTool.ts +++ b/src/designer/model2DEditor/tools/RulerTool.ts @@ -9,6 +9,9 @@ import { Vector3 } from 'three' let pdFn, pmFn, puFn, kdFn +/** + * 用于在 threejs 中创建一系列的 object_for_measure 点,并标记他的距离 + */ export default class RulerTool implements ITool { static OBJ_NAME = 'object_for_measure' static LABEL_NAME = 'label_for_measure' @@ -79,12 +82,6 @@ export default class RulerTool implements ITool { // 当次绘制点 this.pointArray = [] - // 测量距离、面积和角度需要折线 - this.polyline = this.createLine() - this.group.add(this.polyline) - - this.viewport.viewerDom.style.cursor = 'crosshair' - system.msg('进入鼠标测距模式') } @@ -116,7 +113,6 @@ export default class RulerTool implements ITool { this.mouseMoved = false } - // 鼠标移动,创建对应的临时点与线 mousemove = (e: MouseEvent) => { if (this.isCompleted) return @@ -189,12 +185,11 @@ export default class RulerTool implements ITool { // 双击触发两次点击事件,我们需要避免这里的第二次点击 const now = Date.now() - if (this.lastClickTime && (now - this.lastClickTime < 100)) return + if (this.lastClickTime && (now - this.lastClickTime < 10)) return this.lastClickTime = now this.pointArray.push(point) - console.log('pointArray', this.pointArray) const count = this.pointArray.length const marker = this.createPointMarker(point) @@ -216,6 +211,7 @@ export default class RulerTool implements ITool { } } + // this.redrawComplete() // this.viewport.dispatchSignal('sceneGraphChanged') } @@ -285,6 +281,7 @@ export default class RulerTool implements ITool { addOrUpdateTempLabel(label: string, position: THREE.Vector3) { if (!this.tempLabel) { this.tempLabel = this.createLabel(label) + console.log('addOrUpdateTempLabel', label, position) this.viewport.scene.add(this.tempLabel) } this.tempLabel.position.set(position.x, position.y, position.z) @@ -315,4 +312,48 @@ export default class RulerTool implements ITool { } return obj } + + // 重绘完成 + redrawComplete() { + if (!this.tempPointMarker) return + + const point = this.tempPointMarker.userData.point + this.pointArray[this.tempPointMarker.userData.pointIndex] = point + const count = this.pointArray.length + + if (this.polyline) { + this.polyline.geometry.setFromPoints(this.pointArray) + // 如果是距离测量,则清除group中已有的label,再重新创建 + if (this.viewport.state.cursorMode === 'Ruler' && count > 1) { + this.clearCurrentLabel() + // 绘制label + for (let i = 0; i < count - 1; i++) { + const p0 = this.pointArray[i] + const p1 = this.pointArray[i + 1] + if (!p0 || !p1) continue + const dist = p0.distanceTo(p1) + const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}` + const position = new THREE.Vector3((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, (p0.z + p1.z) / 2) + const labelObj = this.createLabel(label) + labelObj.position.set(position.x, position.y, position.z) + labelObj.element.innerHTML = label + this.group.add(labelObj) + } + } + } + + // this.destory() + } + + /** + * 清除当前group label + */ + clearCurrentLabel() { + for (let i = this.group.children.length - 1; i >= 0; i--) { + const c = this.group.children[i] + if (c.userData.type === 'label') { + this.group.remove(c) + } + } + } } \ No newline at end of file