You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
694 lines
20 KiB
694 lines
20 KiB
import _ from 'lodash'
|
|
import * as THREE from 'three'
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|
import Stats from 'three/examples/jsm/libs/stats.module'
|
|
import type WorldModel from '../manager/WorldModel'
|
|
import $ from 'jquery'
|
|
import { markRaw, reactive, watch } from 'vue'
|
|
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
|
|
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
|
|
|
import SceneHelp from './SceneHelp'
|
|
import SelectManager from '../manager/SelectManager'
|
|
import MouseMoveManager from '../manager/MouseMoveManager'
|
|
import EntityManager from '../manager/EntityManager'
|
|
import InteractionManager from '@/core/manager/InteractionManager'
|
|
import { calcPositionUseSnap } from '@/core/ModelUtils'
|
|
import StateManager from '@/core/manager/StateManager.ts'
|
|
import Constract from '@/core/Constract.ts'
|
|
import DragManager from '@/core/manager/DragManager.ts'
|
|
import type { PropertySetter } from '@/core/base/PropertyTypes.ts'
|
|
import LabelManager from '@/core/manager/LabelManager.ts'
|
|
import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts'
|
|
import type { Object3DLike } from '@/types/ModelTypes.ts'
|
|
import type InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts'
|
|
import ItemFindManager from '@/core/manager/ItemFindManager.ts'
|
|
import { MapControls } from 'three/examples/jsm/controls/MapControls'
|
|
|
|
/**
|
|
* 视窗对象
|
|
* 所有状态管理器,场景,控制器,摄像机,实体管理器, 都在这里可以取到
|
|
*/
|
|
export default class Viewport {
|
|
viewerDom: HTMLElement
|
|
camera: THREE.Camera // THREE.OrthographicCamera
|
|
renderer: THREE.WebGLRenderer
|
|
statsControls: Stats
|
|
controls: OrbitControls
|
|
raycaster: THREE.Raycaster
|
|
// dragControl: any // EsDragControls
|
|
animationFrameId: any = null
|
|
scene: SceneHelp
|
|
|
|
selectManager = new SelectManager()
|
|
mouseMoveManager = new MouseMoveManager()
|
|
dragManager = new DragManager()
|
|
labelManager = new LabelManager()
|
|
entityManager = new EntityManager()
|
|
itemFindManager = new ItemFindManager()
|
|
interactionManager = new InteractionManager()
|
|
|
|
// 状态管理器
|
|
stateManager: StateManager
|
|
|
|
tools: IControls[] = [
|
|
markRaw(this.selectManager),
|
|
markRaw(this.mouseMoveManager),
|
|
markRaw(this.dragManager),
|
|
markRaw(this.labelManager),
|
|
markRaw(this.entityManager),
|
|
markRaw(this.itemFindManager),
|
|
markRaw(this.interactionManager)
|
|
]
|
|
|
|
// 对象实例管理器 moduleName -> InstanceMeshManager
|
|
meshManager: Map<string, InstanceMeshManager> = new Map()
|
|
|
|
// 线段实例管理器 moduleName -> InstancePointManager
|
|
lineSegmentManagerMap: Map<string, LineSegmentManager> = new Map()
|
|
|
|
// 监听窗口大小变化
|
|
resizeObserver?: ResizeObserver
|
|
|
|
// vue 的 watcher 管理器, 卸载时需要调用
|
|
watchList: (() => void)[] = []
|
|
|
|
css2DRenderer: CSS2DRenderer = new CSS2DRenderer()
|
|
css3DRenderer: CSS3DRenderer = new CSS3DRenderer()
|
|
|
|
//@ts-ignore
|
|
state: ViewportState = reactive({
|
|
isReady: false,
|
|
isUpdating: false,
|
|
cursorMode: 'normal',
|
|
|
|
selectedObject: undefined,
|
|
selectedItem: undefined,
|
|
selectedEntityId: undefined,
|
|
selectedObjectSetter: undefined,
|
|
|
|
multiSelectedObjects: [],
|
|
multiSelectedItems: [],
|
|
multiSelectedEntityIds: [],
|
|
|
|
view3DMode: Constract.Mode2D,
|
|
camera: {
|
|
position: { x: 0, y: 0, z: 0 },
|
|
rotation: { x: 0, y: 0, z: 0 }
|
|
},
|
|
mouse: {
|
|
x: 0,
|
|
y: 0
|
|
}
|
|
})
|
|
|
|
constructor(sceneHelp: SceneHelp, viewerDom: HTMLElement, stateManager) {
|
|
this.scene = sceneHelp
|
|
this.viewerDom = viewerDom
|
|
this.stateManager = stateManager
|
|
}
|
|
|
|
/**
|
|
* 获取或创建形状管理器
|
|
* @param typeName 点类型名称
|
|
* @param createFn 创建点管理器的函数
|
|
*/
|
|
getOrCreateMeshManager(typeName: string, createFn: () => InstanceMeshManager): InstanceMeshManager {
|
|
let meshManager = this.meshManager.get(typeName)
|
|
if (!meshManager) {
|
|
meshManager = createFn()
|
|
if (meshManager) {
|
|
this.meshManager.set(typeName, meshManager)
|
|
}
|
|
}
|
|
return meshManager
|
|
}
|
|
|
|
/**
|
|
* 获取或创建线段管理器
|
|
* @param typeName 线段类型名称
|
|
* @param createFn 创建线段管理器的函数
|
|
*/
|
|
getOrCreateLineManager(typeName: string, createFn: () => LineSegmentManager): LineSegmentManager {
|
|
let lineSegmentManager = this.lineSegmentManagerMap.get(typeName)
|
|
if (!lineSegmentManager) {
|
|
lineSegmentManager = createFn()
|
|
if (lineSegmentManager) {
|
|
this.lineSegmentManagerMap.set(typeName, lineSegmentManager)
|
|
}
|
|
}
|
|
return lineSegmentManager
|
|
}
|
|
|
|
/**
|
|
* 初始化 THREE 渲染器
|
|
*/
|
|
async initThree(option: InitThreeOption) {
|
|
console.log('viewport on catelogCode: ' + this.scene.catalogCode)
|
|
const viewerDom = this.viewerDom
|
|
|
|
// 状态管理器初始化
|
|
this.stateManager.init(this)
|
|
|
|
// 渲染器
|
|
const renderer = new THREE.WebGLRenderer({
|
|
logarithmicDepthBuffer: true,
|
|
antialias: true,
|
|
alpha: true,
|
|
precision: 'mediump',
|
|
premultipliedAlpha: true,
|
|
preserveDrawingBuffer: false,
|
|
powerPreference: 'high-performance'
|
|
})
|
|
renderer.debug.checkShaderErrors = true
|
|
//@ts-ignore
|
|
renderer.outputEncoding = THREE.SRGBColorSpace
|
|
renderer.clearDepth()
|
|
renderer.shadowMap.enabled = true
|
|
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
|
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
|
|
|
|
// 性能监控
|
|
const statsControls = new Stats()
|
|
this.statsControls = statsControls
|
|
statsControls.showPanel(0)
|
|
statsControls.dom.style.position = 'absolute'
|
|
statsControls.dom.style.top = '0'
|
|
statsControls.dom.style.left = '0'
|
|
statsControls.dom.style.zIndex = '1'
|
|
viewerDom.parentElement.parentElement.appendChild(statsControls.dom)
|
|
$(statsControls.dom).children().css('height', '28px')
|
|
|
|
// 监听事件
|
|
this.watchList.push(watch(() => this.state.camera.position.y, (newVal) => {
|
|
if (!this.state.isReady) {
|
|
return
|
|
}
|
|
this.updateGridVisibility()
|
|
}))
|
|
|
|
|
|
// 监听窗口大小变化
|
|
if (this.resizeObserver) {
|
|
this.resizeObserver.unobserve(this.viewerDom)
|
|
}
|
|
this.resizeObserver = new ResizeObserver(this.handleResize.bind(this))
|
|
this.resizeObserver.observe(this.viewerDom)
|
|
|
|
// 初始化射线投射器
|
|
this.raycaster = new THREE.Raycaster()
|
|
|
|
// 初始化所有常驻工具
|
|
for (const tool of this.tools) {
|
|
tool.init(this)
|
|
}
|
|
|
|
// 创建正交摄像机
|
|
// this.initMode2DCamera()
|
|
this.watchList.push(watch(() => this.state.view3DMode, (newVal) => {
|
|
if (newVal === Constract.Mode3D) {
|
|
this.initMode3DCamera()
|
|
} else {
|
|
this.initMode2DCamera()
|
|
}
|
|
}, { immediate: true }))
|
|
|
|
this.animate()
|
|
|
|
window['viewport'] = this
|
|
window['stateManager'] = this.stateManager
|
|
window['entityManager'] = this.entityManager
|
|
window['renderer'] = this.renderer
|
|
window['camera'] = this.camera
|
|
window['renderer'] = this.renderer
|
|
window['controls'] = this.controls
|
|
this.state.isReady = true
|
|
}
|
|
|
|
/**
|
|
* 初始化3D相机
|
|
*/
|
|
initMode3DCamera() {
|
|
if (this.camera) {
|
|
this.scene.remove(this.camera)
|
|
}
|
|
if (this.controls) {
|
|
this.controls.dispose()
|
|
this.controls = null
|
|
}
|
|
|
|
// ============================ 创建透视相机
|
|
const viewerDom = this.viewerDom
|
|
const cameraNew = new THREE.PerspectiveCamera(
|
|
25,
|
|
viewerDom.clientWidth / viewerDom.clientHeight,
|
|
1,
|
|
2000
|
|
)
|
|
cameraNew.position.set(4, 2, -3)
|
|
cameraNew.lookAt(0, 0, 0)
|
|
this.camera = cameraNew
|
|
this.scene.add(this.camera)
|
|
|
|
// ============================ 创建控制器
|
|
const controls = new OrbitControls(
|
|
this.camera,
|
|
this.renderer?.domElement
|
|
)
|
|
controls.enableDamping = false
|
|
controls.screenSpacePanning = false // 定义平移时如何平移相机的位置 控制不上下移动
|
|
controls.minDistance = 2
|
|
controls.maxDistance = 1000
|
|
controls.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.ROTATE }
|
|
// 下面这句话非常影响性能
|
|
// this.controls.addEventListener('change', ()=>{
|
|
// this.renderer.render(this.scene, this.camera);
|
|
// });
|
|
this.controls = controls
|
|
|
|
window['camera'] = this.camera
|
|
window['controls'] = this.controls
|
|
}
|
|
|
|
/**
|
|
* 初始化2D相机
|
|
*/
|
|
initMode2DCamera() {
|
|
if (this.camera) {
|
|
this.scene.remove(this.camera)
|
|
}
|
|
if (this.controls) {
|
|
this.controls.dispose()
|
|
this.controls = null
|
|
}
|
|
|
|
// ============================ 创建2D正交相机
|
|
// 模拟俯视2D的模式, 操作也用2D模式
|
|
const viewerDom = this.viewerDom
|
|
const cameraNew = new THREE.OrthographicCamera(
|
|
viewerDom.clientWidth / -2,
|
|
viewerDom.clientWidth / 2,
|
|
viewerDom.clientHeight / 2,
|
|
viewerDom.clientHeight / -2,
|
|
1,
|
|
500
|
|
)
|
|
this.camera = cameraNew
|
|
this.scene.add(this.camera)
|
|
|
|
// ============================ 创建控制器
|
|
const controlsNew = new MapControls(this.camera, this.renderer.domElement)
|
|
controlsNew.enableRotate = false // 禁止旋转
|
|
controlsNew.enableZoom = true // 启用缩放
|
|
// controlsNew.zoomSpeed = 0.5 // 调整缩放速度
|
|
// controlsNew.panSpeed = 0.5 // 调整平移速度
|
|
controlsNew.mouseButtons = {
|
|
LEFT: THREE.MOUSE.PAN,
|
|
RIGHT: THREE.MOUSE.PAN
|
|
}
|
|
controlsNew.addEventListener('change', () => {
|
|
this.syncCameraState()
|
|
})
|
|
this.controls = controlsNew
|
|
|
|
window['camera'] = this.camera
|
|
window['controls'] = this.controls
|
|
|
|
if (this.camera instanceof THREE.OrthographicCamera) {
|
|
this.camera.position.set(0, 100, 0)
|
|
this.camera.lookAt(0, 0, 0)
|
|
this.camera.zoom = 34
|
|
this.camera.updateProjectionMatrix()
|
|
}
|
|
|
|
this.syncCameraState()
|
|
}
|
|
|
|
offset = 0
|
|
|
|
/**
|
|
* 动画循环
|
|
*/
|
|
animate() {
|
|
this.animationFrameId = requestAnimationFrame(this.animate.bind(this))
|
|
|
|
for (const lineSegmentManager of this.lineSegmentManagerMap.values()) {
|
|
if (lineSegmentManager.needsUpdate) {
|
|
lineSegmentManager.updateGeometry()
|
|
}
|
|
}
|
|
|
|
this.statsControls?.update()
|
|
this.renderer?.render(this.scene.scene, this.camera)
|
|
|
|
this.css2DRenderer.render(this.scene.scene, this.camera)
|
|
this.css3DRenderer.render(this.scene.scene, this.camera)
|
|
|
|
// if (window['lineMaterial']) {
|
|
// this.offset -= 0.002
|
|
// window['lineMaterial'].dashOffset = this.offset
|
|
// }
|
|
}
|
|
|
|
/**
|
|
* 同步相机状态到全局状态
|
|
*/
|
|
syncCameraState() {
|
|
if (this.camera) {
|
|
const camera = this.camera
|
|
if (this.camera instanceof THREE.OrthographicCamera) {
|
|
this.state.camera.position.x = camera.position.x
|
|
this.state.camera.position.y = (camera as THREE.OrthographicCamera).zoom // this.getEffectiveViewDistance()
|
|
this.state.camera.position.z = camera.position.z
|
|
|
|
} else {
|
|
this.state.camera.position.x = camera.position.x
|
|
this.state.camera.position.y = 5
|
|
this.state.camera.position.z = camera.position.z
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 摄像机追踪到指定位置
|
|
*/
|
|
cameraToEntity(id: string) {
|
|
const { tf } = this.entityManager.findItemById(id)
|
|
// 移动正交相机去往目标点
|
|
if (this.camera instanceof THREE.OrthographicCamera) {
|
|
const targetX = tf[0][0]
|
|
const targetZ = tf[0][2]
|
|
|
|
// this.controls.target.set(targetX, 0, targetZ)
|
|
// this.camera.position.set(targetX, 60, targetZ) // y 可以固定一个值,比如 60
|
|
// this.camera.zoom = 34
|
|
// this.camera.updateProjectionMatrix()
|
|
// this.controls.update()
|
|
this.smoothMoveCameraTo(targetX, targetZ)
|
|
|
|
} else if (this.camera instanceof THREE.PerspectiveCamera) {
|
|
this.camera.position.set(tf[0][0], tf[1][1] + 10, tf[2][2])
|
|
this.camera.lookAt(tf[0][0], tf[1][1], tf[2][2])
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 动画移动相机
|
|
*/
|
|
smoothMoveCameraTo(targetX: number, targetZ: number, duration = 300) {
|
|
const orthCamera = this.camera as THREE.OrthographicCamera
|
|
|
|
const start = performance.now()
|
|
const startTarget = this.controls.target.clone()
|
|
const startPosition = this.camera.position.clone()
|
|
const startZoom = orthCamera.zoom
|
|
|
|
const endZoom = 34
|
|
const endPosition = new THREE.Vector3(targetX, 60, targetZ)
|
|
const endTarget = new THREE.Vector3(targetX, 0, targetZ)
|
|
|
|
const animate = (now: number) => {
|
|
const elapsed = now - start
|
|
const t = Math.min(elapsed / duration, 1)
|
|
|
|
orthCamera.position.copy(startPosition.clone().lerp(endPosition, t))
|
|
this.controls.target.copy(startTarget.clone().lerp(endTarget, t))
|
|
orthCamera.zoom = startZoom + (endZoom - startZoom) * t
|
|
|
|
orthCamera.updateProjectionMatrix()
|
|
this.controls.update()
|
|
|
|
if (t < 1) {
|
|
requestAnimationFrame(animate)
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(animate)
|
|
}
|
|
|
|
handleResize(entries: any) {
|
|
for (let entry of entries) {
|
|
// entry.contentRect包含了元素的尺寸信息
|
|
// console.log('Element size changed:', entry.contentRect)
|
|
|
|
const width = entry.contentRect.width
|
|
const height = entry.contentRect.height
|
|
|
|
if (this.camera instanceof THREE.PerspectiveCamera) {
|
|
this.camera.aspect = width / height
|
|
this.camera.updateProjectionMatrix()
|
|
|
|
} else if (this.camera instanceof THREE.OrthographicCamera) {
|
|
this.camera.left = width / -2
|
|
this.camera.right = width / 2
|
|
this.camera.top = height / 2
|
|
this.camera.bottom = height / -2
|
|
this.camera.updateProjectionMatrix()
|
|
}
|
|
|
|
this.renderer.setSize(width, height)
|
|
this.css2DRenderer.setSize(width, height)
|
|
this.css3DRenderer.setSize(width, height)
|
|
break
|
|
}
|
|
}
|
|
|
|
beginViewUpdate() {
|
|
this.state.isUpdating = true
|
|
}
|
|
|
|
endViewUpdate() {
|
|
this.state.isUpdating = false
|
|
}
|
|
|
|
get worldModel(): WorldModel {
|
|
return this.scene.worldModel
|
|
}
|
|
|
|
get gridHelper(): THREE.GridHelper {
|
|
return this.scene.gridHelper
|
|
}
|
|
|
|
/**
|
|
* 根据可视化范围更新网格的透明度
|
|
*/
|
|
updateGridVisibility() {
|
|
if (this.camera === undefined || !(this.camera instanceof THREE.OrthographicCamera)) {
|
|
// 如果没有相机或相机不是透视相机,则不更新网格可见性
|
|
this.gridHelper.visible = true
|
|
this.gridHelper.material.opacity = 1
|
|
return
|
|
}
|
|
const cameraDistance = this.state.camera.position.y
|
|
const maxVisibleDistance = 8 // 网格完全不可见的最小距离
|
|
const fadeStartDistance = 35 // 开始淡出的最大距离
|
|
|
|
let opacity
|
|
if (cameraDistance >= fadeStartDistance) {
|
|
// 如果摄像机位置在淡出开始距离或更远,则网格完全可见
|
|
opacity = 1
|
|
} else if (cameraDistance <= maxVisibleDistance) {
|
|
// 如果摄像机位置小于等于最大可见距离,则网格完全不可见
|
|
opacity = 0
|
|
} else {
|
|
// 计算透明度,使用线性插值
|
|
opacity = 1 - (fadeStartDistance - cameraDistance) / (fadeStartDistance - maxVisibleDistance)
|
|
}
|
|
|
|
// 设置材质的透明度,并确保材质的transparent属性为true以支持透明
|
|
this.gridHelper.material.opacity = opacity
|
|
this.gridHelper.material.transparent = true
|
|
|
|
// 更新网格是否可见
|
|
this.gridHelper.visible = opacity > 0
|
|
}
|
|
|
|
dispose() {
|
|
this.state.isReady = false
|
|
|
|
if (this.animationFrameId !== null) {
|
|
cancelAnimationFrame(this.animationFrameId)
|
|
this.animationFrameId = null
|
|
}
|
|
|
|
if (this.meshManager.size > 0) {
|
|
this.meshManager.forEach((manager) => {
|
|
if (manager.dispose) {
|
|
manager.dispose()
|
|
}
|
|
})
|
|
this.meshManager.clear()
|
|
}
|
|
if (this.lineSegmentManagerMap.size > 0) {
|
|
this.lineSegmentManagerMap.forEach((manager) => {
|
|
if (manager.dispose) {
|
|
manager.dispose()
|
|
}
|
|
})
|
|
this.lineSegmentManagerMap.clear()
|
|
}
|
|
|
|
if (this.tools) {
|
|
for (const tool of this.tools) {
|
|
if (tool.dispose) {
|
|
tool.dispose()
|
|
}
|
|
}
|
|
this.tools = []
|
|
}
|
|
|
|
if (this.watchList) {
|
|
_.forEach(this.watchList, (unWatchFn => {
|
|
if (typeof unWatchFn === 'function') {
|
|
unWatchFn()
|
|
}
|
|
}))
|
|
this.watchList = []
|
|
}
|
|
|
|
|
|
if (this.resizeObserver) {
|
|
this.resizeObserver.unobserve(this.viewerDom)
|
|
this.resizeObserver.disconnect()
|
|
this.resizeObserver = undefined
|
|
}
|
|
|
|
if (this.statsControls) {
|
|
this.statsControls.dom.remove()
|
|
}
|
|
|
|
if (this.renderer) {
|
|
this.renderer.dispose()
|
|
this.renderer.forceContextLoss()
|
|
console.log('WebGL disposed, memory:', this.renderer.info.memory)
|
|
this.renderer.domElement = null
|
|
}
|
|
|
|
if (this.stateManager) {
|
|
this.stateManager.dispose()
|
|
this.stateManager = null
|
|
}
|
|
|
|
if (this.controls) {
|
|
this.controls.dispose()
|
|
this.controls = null
|
|
}
|
|
|
|
delete window['viewport']
|
|
delete window['stateManager']
|
|
delete window['entityManager']
|
|
delete window['renderer']
|
|
delete window['camera']
|
|
delete window['renderer']
|
|
delete window['controls']
|
|
}
|
|
|
|
getIntersects(point: THREE.Vector2) {
|
|
const mouse = new THREE.Vector2()
|
|
mouse.set((point.x * 2) - 1, -(point.y * 2) + 1)
|
|
this.raycaster.setFromCamera(mouse, this.camera)
|
|
|
|
return this.raycaster.intersectObjects([this.gridHelper], false)
|
|
}
|
|
|
|
/**
|
|
* 获取鼠标所在的 x,y,z 位置。
|
|
* 鼠标坐标是相对于 canvas 元素 (renderer.domElement) 元素的
|
|
*/
|
|
getClosestIntersection(e: MouseEvent) {
|
|
const _point = new THREE.Vector2()
|
|
_point.x = e.offsetX / this.renderer.domElement.offsetWidth
|
|
_point.y = e.offsetY / this.renderer.domElement.offsetHeight
|
|
|
|
const intersects = this.getIntersects(_point)
|
|
if (intersects && intersects.length > 2) {
|
|
const point = new THREE.Vector3(intersects[0].point.x, 0.1, intersects[1].point.z)
|
|
|
|
return calcPositionUseSnap(e, point)
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
export interface ViewportState {
|
|
/**
|
|
* 是否准备完成
|
|
*/
|
|
isReady: boolean
|
|
|
|
/**
|
|
* 鼠标模式
|
|
*/
|
|
cursorMode: string // CursorMode,
|
|
|
|
/**
|
|
* 黄选的对象
|
|
*/
|
|
selectedObject: Object3DLike | undefined
|
|
selectedItem: ItemJson | undefined
|
|
selectedEntityId: string | undefined
|
|
selectedObjectSetter: PropertySetter | undefined
|
|
|
|
/**
|
|
* 红选的对象集
|
|
*/
|
|
multiSelectedObjects: Object3DLike[]
|
|
multiSelectedItems: ItemJson[]
|
|
multiSelectedEntityIds: string[]
|
|
|
|
view3DMode: string // Constract.Mode2D | Constract.Mode3D
|
|
|
|
/**
|
|
* 是否正在更新中
|
|
*/
|
|
isUpdating: boolean
|
|
|
|
/**
|
|
* 相机状态
|
|
*/
|
|
camera: {
|
|
position: { x: number, y: number, z: number },
|
|
rotation: { x: number, y: number, z: number }
|
|
}
|
|
|
|
/**
|
|
* 鼠标位置(归一化坐标)
|
|
*/
|
|
mouse: {
|
|
/**
|
|
* 鼠标在设计图上的坐标
|
|
*/
|
|
x: number,
|
|
z: number
|
|
}
|
|
}
|
|
|