Browse Source

Merge remote-tracking branch 'origin/master'

master
lizw-2015 7 months ago
parent
commit
96195f453b
  1. 3
      src/core/Constract.ts
  2. 18
      src/core/ModelUtils.ts
  3. 13
      src/core/base/BaseRenderer.ts
  4. 140
      src/core/controls/DragControls.js
  5. 21
      src/core/controls/EsDragControls.ts
  6. 159
      src/core/controls/SelectInspect.ts
  7. 118
      src/core/engine/Viewport.ts
  8. 26
      src/core/manager/EntityManager.ts
  9. 9
      src/editor/Model2DEditor.vue
  10. 2
      src/editor/widgets/property/PropertyView.vue
  11. 14
      src/modules/gstore/GstoreRenderer.ts
  12. 41
      src/modules/rack/RackMeta.ts
  13. 2
      src/modules/way/WayRenderer.ts
  14. 3
      src/runtime/EventBus.ts

3
src/core/Constract.ts

@ -10,6 +10,9 @@ export default Object.freeze({
CursorModeLinkAdd: 'LinkAdd',
CursorModeLinkAdd2: 'LinkAdd2',
Mode2D: '2D',
Mode3D: '3D',
// 测量相关的光标模式
CursorModeMeasure: 'measure',
CursorModeWay: 'way',

18
src/core/ModelUtils.ts

@ -100,6 +100,24 @@ export function getLineId(startId: string, endId: string, type: LinkType): strin
}
/**
* Object3D Object3D
* @param object
*/
export function getClosestObject(object: THREE.Object3D) {
if (!object) {
return undefined
}
while (object) {
if (object.userData && object.userData.t && object.userData.entityId) {
// 找到第一个有效的业务 Object3D
return object
}
// 向上查找父级
object = object.parent
}
}
/**
* 线 ID, 线, ID ID
*/
export function parseLineId(lineId): [LinkType, string, string] {

13
src/core/base/BaseRenderer.ts

@ -79,7 +79,7 @@ export default abstract class BaseRenderer {
}
fillObjectUserDataFromItem(item: ItemJson, ...objects: THREE.Object3D[]) {
_.forEach(objects, (object) => {
for (const object of objects) {
if (!object.name && item.name) {
object.name = item.name
}
@ -90,12 +90,12 @@ export default abstract class BaseRenderer {
draggable: item.dt.protected !== true,
t: item.t
}
})
}
}
fillObjectUserDataFromLine(start: ItemJson, end: ItemJson, type: LinkType, ...objects: THREE.Object3D[]) {
const id = getLineId(start.id, end.id, type)
_.forEach(objects, (object) => {
for (const object of objects) {
if (!object.name) {
object.name = id
}
@ -105,7 +105,7 @@ export default abstract class BaseRenderer {
entityId: getLineId(start.id, end.id, type),
t: start.t
}
})
}
}
/**
@ -116,9 +116,10 @@ export default abstract class BaseRenderer {
console.warn('No active viewport to append objects to.')
return
}
// const dragObjects = objects.filter(obj => !!obj.userData.draggable)
// this.tempViewport.dragControl.setDragObjects(dragObjects, 'push')
this.tempViewport.scene.add(...objects)
const dragObjects = objects.filter(obj => !!obj.userData.draggable)
this.tempViewport.dragControl.setDragObjects(dragObjects, 'push')
}
removeFromScene(...objects: THREE.Object3D[]) {

140
src/core/controls/DragControls.js

@ -1,38 +1,45 @@
import {
EventDispatcher,
Matrix4,
Plane,
Raycaster,
Vector2,
Vector3
} from 'three'
import { calcPositionUseSnap } from '@/core/ModelUtils.js'
const _plane = new Plane()
const _raycaster = new Raycaster()
const _pointer = new Vector2()
const _offset = new Vector3()
const _intersection = new Vector3()
const _worldPosition = new Vector3()
const _inverseMatrix = new Matrix4()
import { EventDispatcher, Matrix4, Plane, Raycaster, Vector2, Vector3 } from 'three'
import { calcPositionUseSnap, getClosestObject } from '@/core/ModelUtils.js'
const _plane = new Plane() // 用于拖拽操作的平面
const _raycaster = new Raycaster() // 射线检测器,用于拾取物体
const _pointer = new Vector2() // 屏幕坐标指针位置 (归一化设备坐标)
const _offset = new Vector3() // 拖动时相对于点击点的偏移量
const _intersection = new Vector3() // 与平面相交的点
const _worldPosition = new Vector3() // 世界坐标位置
const _inverseMatrix = new Matrix4() // 用于将位置转换到局部空间
/**
* DragControls 控制器类
* 提供基于鼠标或触摸的拖拽交互功能并支持 hover clickblank 等事件
*/
class DragControls extends EventDispatcher {
/**
* 构造函数
* @param _objects 可拖拽的对象数组
* @param _camera 当前使用的相机
* @param _domElement 绑定事件的目标 DOM 元素通常是 canvas
*/
constructor(_objects, _camera, _domElement) {
super()
_domElement.style.touchAction = 'none' // disable touch scroll
let _selected = null, _hovered = null
// 禁止触摸滚动行为
_domElement.style.touchAction = 'none'
const _intersections = []
let _selected = null // 当前选中(正在拖动)的对象
let _hovered = null // 当前悬停的对象
//
const _intersections = [] // 存储射线检测结果的数组
let isMove = false
let isMouseDownClicked = false
const scope = this
let isMove = false // 标记是否发生了移动
let isMouseDownClicked = false // 标记是否按下了鼠标
const scope = this // 保存当前上下文
/**
* 激活事件监听器
*/
function activate() {
_domElement.addEventListener('pointermove', onPointerMove)
_domElement.addEventListener('pointerdown', onPointerDown)
@ -40,6 +47,9 @@ class DragControls extends EventDispatcher {
_domElement.addEventListener('pointerleave', onPointerCancel)
}
/**
* 去激活事件监听器
*/
function deactivate() {
_domElement.removeEventListener('pointermove', onPointerMove)
_domElement.removeEventListener('pointerdown', onPointerDown)
@ -49,22 +59,41 @@ class DragControls extends EventDispatcher {
_domElement.style.cursor = ''
}
/**
* 销毁控制器并释放资源
*/
function dispose() {
deactivate()
}
/**
* 设置可拖拽的对象列表
* @param {Array} objects - 新的对象数组
*/
function setObjects(objects) {
_objects = objects
}
/**
* 获取当前可拖拽的对象列表
* @returns {Array}
*/
function getObjects() {
return _objects
}
/**
* 获取内部的 Raycaster 实例
* @returns {Raycaster}
*/
function getRaycaster() {
return _raycaster
}
/**
* 鼠标/指针移动事件处理函数
* 处理悬停拖拽等交互逻辑
*/
function onPointerMove(event) {
if (!scope.enabled || !scope.enabledMove) return
@ -76,6 +105,7 @@ class DragControls extends EventDispatcher {
updatePointer(event)
_raycaster.setFromCamera(_pointer, _camera)
// 如果有选中的对象,则更新其位置
if (_selected) {
if (_raycaster.ray.intersectPlane(_plane, _intersection)) {
const pos = _intersection.sub(_offset).applyMatrix4(_inverseMatrix)
@ -86,7 +116,7 @@ class DragControls extends EventDispatcher {
return
}
// hover support
// 鼠标/笔悬停检测
if (event.pointerType === 'mouse' || event.pointerType === 'pen') {
_intersections.length = 0
@ -98,24 +128,21 @@ class DragControls extends EventDispatcher {
const object = _intersections[0].object
_plane.setFromNormalAndCoplanarPoint(_camera.getWorldDirection(_plane.normal), _worldPosition.setFromMatrixPosition(object.matrixWorld))
_plane.setFromNormalAndCoplanarPoint(
_camera.getWorldDirection(_plane.normal),
_worldPosition.setFromMatrixPosition(object.matrixWorld)
)
if (_hovered !== object && _hovered !== null) {
scope.dispatchEvent({ type: 'hoveroff', object: _hovered })
_domElement.style.cursor = 'auto'
_hovered = null
}
if (_hovered !== object) {
scope.dispatchEvent({ type: 'hoveron', object: object })
_domElement.style.cursor = 'pointer'
_hovered = object
}
} else {
@ -132,6 +159,10 @@ class DragControls extends EventDispatcher {
}
/**
* 鼠标按下事件处理函数
* 检测是否点击了可拖拽对象并准备开始拖拽
*/
function onPointerDown(event) {
if (scope.enabled === false) return
@ -145,41 +176,58 @@ class DragControls extends EventDispatcher {
_raycaster.intersectObjects(objects, true, _intersections)
if (_intersections.length > 0) {
// 判断是否启用组拖动模式
_selected = (scope.transformGroup === true) ? _objects[0] : _intersections[0].object
if (scope.enabledMove) {
_plane.setFromNormalAndCoplanarPoint(_camera.getWorldDirection(_plane.normal), _worldPosition.setFromMatrixPosition(_selected.matrixWorld))
// 设置拖拽平面
_plane.setFromNormalAndCoplanarPoint(
_camera.getWorldDirection(_plane.normal),
_worldPosition.setFromMatrixPosition(_selected.matrixWorld)
)
if (_raycaster.ray.intersectPlane(_plane, _intersection)) {
// 计算偏移量
_inverseMatrix.copy(_selected.parent.matrixWorld).invert()
_offset.copy(_intersection).sub(_worldPosition.setFromMatrixPosition(_selected.matrixWorld))
_offset.copy(_intersection).sub(
_worldPosition.setFromMatrixPosition(_selected.matrixWorld)
)
}
// setTimeout(() => {
// _domElement.style.cursor = 'move'
// }, 20)
isMouseDownClicked = true
}
// 触发 dragstart 事件
scope.dispatchEvent({ type: 'dragstart', object: _selected, e: event })
}
isMove = false
}
/**
* 鼠标释放或离开事件处理函数
* 结束拖拽操作或触发点击空白区域事件
*/
function onPointerCancel(event) {
if (scope.enabled === false) return
if (_selected) {
// 结束拖拽
scope.dispatchEvent({ type: 'dragend', object: _selected, e: event })
_selected = null
} else if (!isMove) {
// 添加点击空白处的事件
// 如果没有发生移动,则认为是点击空白处
scope.dispatchEvent({ type: 'clickblank', e: event })
}
// 恢复光标状态
_domElement.style.cursor = _hovered ? 'pointer' : 'auto'
isMouseDownClicked = false
}
/**
* 更新指针位置
* 将屏幕坐标转换为 NDC 设备坐标 (-1 ~ 1)
*/
function updatePointer(event) {
const rect = _domElement.getBoundingClientRect()
@ -187,13 +235,13 @@ class DragControls extends EventDispatcher {
_pointer.y = -(event.clientY - rect.top) / rect.height * 2 + 1
}
// 初始化:激活事件监听器
activate()
// API
this.enabled = true
this.enabledMove = true
this.transformGroup = false
// 暴露 API 方法和属性
this.enabled = true // 是否启用控制器
this.enabledMove = true // 是否允许移动操作
this.transformGroup = false // 是否以组形式变换多个对象
this.activate = activate
this.deactivate = deactivate
@ -201,9 +249,7 @@ class DragControls extends EventDispatcher {
this.setObjects = setObjects
this.getObjects = getObjects
this.getRaycaster = getRaycaster
}
}
export { DragControls }

21
src/core/controls/EsDragControls.ts

@ -71,7 +71,7 @@ export default class EsDragControls {
// 拖拽开始
dragControlsStart(e) {
// 右键拖拽不响应
if (e.e.button === 2 || !e.object.visible) return
if (e.e.button === 2 || !e.object?.visible) return
const type = e.object.userData?.t
const entityId = e.object.userData?.entityId
@ -127,25 +127,6 @@ export default class EsDragControls {
if (e.object.userData.onClick) {
e.object.userData.onClick(e)
}
if (e.object.userData?.entityId) {
const entityId = e.object.userData.entityId
const item = this.viewport.entityManager.findItemById(entityId)
const itemTypeName = e.object.userData.t
if (item.dt.protected !== true) {
this.viewport.state.selectedObject = markRaw(e.object)
this.viewport.state.selectedItem = markRaw(item)
this.viewport.state.selectedEntityId = entityId
this.viewport.state.selectedObjectMeta = getMeta(itemTypeName)
EventBus.dispatch('selectedObjectChanged', {
viewport: this,
selectedObject: this.viewport.state.selectedObject,
selectedItem: this.viewport.state.selectedItem,
selectedEntityId: this.viewport.state.selectedEntityId,
selectedObjectMeta: this.viewport.state.selectedObjectMeta
})
}
}
}
const ret = this.currentInteraction?.dragPointComplete(this.viewport, e)

159
src/core/controls/SelectInspect.ts

@ -19,7 +19,12 @@ export default class SelectInspect implements IControls {
/**
* 线
*/
material: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 })
yellowMaterial: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 })
/**
* 线
*/
redMaterial: LineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 2 })
/**
*
@ -76,6 +81,11 @@ export default class SelectInspect implements IControls {
this.updateSelectionBox(this.viewport.state.selectedObject)
})
EventBus.on('multiSelectedObjectsChanged', (data) => {
// 如果多选对象发生变化,清除当前选中对象的包围盒线框
this.updateMultiSelectionBoxes(data.multiSelectedObjects)
})
EventBus.on('selectedObjectPropertyChanged', (data) => {
this.updateSelectionBox(this.viewport.state.selectedObject)
})
@ -90,6 +100,69 @@ export default class SelectInspect implements IControls {
})
}
redSelectionGroup = new THREE.Group()
private updateMultiSelectionBoxes(multiSelectedObjects: THREE.Object3D[]) {
// 为所有多选对象创建包围盒线框
this.clearRedSelectionBoxes()
if (!multiSelectedObjects || multiSelectedObjects.length === 0) {
return
}
for (const object of multiSelectedObjects) {
if (object.userData.entityId) {
this.createRedSelectionBox(object)
}
}
}
clearRedSelectionBoxes() {
// 清除之前的红色包围盒线框
if (this.redSelectionGroup.children.length > 0) {
for (const child of this.redSelectionGroup.children) {
this.redSelectionGroup.remove(child)
}
}
this.viewport.scene.remove(this.redSelectionGroup)
this.redSelectionGroup = new THREE.Group()
this.viewport.scene.add(this.redSelectionGroup)
}
createRedSelectionBox(object: THREE.Object3D) {
// 如果对象没有 entityId,则不创建包围盒线框
if (!object.userData.entityId) {
return
}
// 如果选中的对象小于 0.5,要扩展包围盒
const RED_EXPAND_AMOUNT = 0.01 // 扩展包围盒的大小
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
object.updateWorldMatrix(false, true)
const box = new THREE.Box3().setFromObject(object)
box.expandByScalar(RED_EXPAND_AMOUNT)
const size = new THREE.Vector3()
box.getSize(size)
const center = new THREE.Vector3()
box.getCenter(center)
// 创建包围盒几何体
const helperGeometry = new THREE.BoxGeometry(size.x, size.y, size.z)
const edgesGeometry = new THREE.EdgesGeometry(helperGeometry)
const lineGeom = new LineGeometry()
// @ts-ignore
lineGeom.setPositions(edgesGeometry.attributes.position.array)
const selectionBox = new Line2(lineGeom, this.redMaterial)
selectionBox.computeLineDistances()
selectionBox.position.copy(center)
this.redSelectionGroup.add(selectionBox)
}
/**
* 线
*/
@ -101,12 +174,13 @@ export default class SelectInspect implements IControls {
}
this.selectionId = selectedObject.userData?.entityId
const expandAmount = 0.2 // 扩展包围盒的大小
// 如果选中的对象小于 0.5,要扩展包围盒
const YELLOW_EXPAND_AMOUNT = 0.03 // 扩展包围盒的大小
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
selectedObject.updateWorldMatrix(false, true)
const box = new THREE.Box3().setFromObject(selectedObject)
box.expandByScalar(expandAmount)
box.expandByScalar(YELLOW_EXPAND_AMOUNT)
const size = new THREE.Vector3()
box.getSize(size)
@ -117,16 +191,13 @@ export default class SelectInspect implements IControls {
// 创建包围盒几何体
const helperGeometry = new THREE.BoxGeometry(size.x, size.y, size.z)
const edgesGeometry = new THREE.EdgesGeometry(helperGeometry)
// 使用 LineGeometry 包装 edgesGeometry
const lineGeom = new LineGeometry()
// @ts-ignore
lineGeom.setPositions(edgesGeometry.attributes.position.array)
const selectionBox = new Line2(lineGeom, this.material)
const selectionBox = new Line2(lineGeom, this.yellowMaterial)
selectionBox.computeLineDistances()
selectionBox.position.copy(center)
selectionBox.name = 'selectionBox'
this.selectionBox = selectionBox
console.log('selectedItem', this.viewport.state.selectedItem)
@ -162,7 +233,7 @@ export default class SelectInspect implements IControls {
if (this.recStartPos) {
// 创建矩形
this.rectangle = new THREE.Mesh(
new THREE.PlaneGeometry(1, 1),
new THREE.PlaneGeometry(0.001, 0.001),
this.rectMaterial
)
this.rectangle.name = 'selectRectangle'
@ -176,6 +247,7 @@ export default class SelectInspect implements IControls {
}
}
updateRectangle(position: THREE.Vector3) {
if (!this.rectangle || !this.recStartPos) return
// console.log('updateRectangle', this.recStartPos, position)
@ -201,6 +273,8 @@ export default class SelectInspect implements IControls {
// 记录鼠标按下位置
this.recStartPos = this.viewport.getClosestIntersection(event)
this.createRectangle()
this.viewport.controls.enabled = false // 禁用控制器
} else {
// 为 click 事件添加处理逻辑
this.clickTime = Date.now()
@ -220,11 +294,14 @@ export default class SelectInspect implements IControls {
disposeRect() {
if (this.rectangle !== null) {
// 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects
this.multipleSelectedObjects()
this.viewport.scene.remove(this.rectangle)
this.rectangle.geometry.dispose()
this.rectangle = null
}
this.recStartPos = null
this.viewport.controls.enabled = true // 启用控制器
}
onMouseUp(event: MouseEvent) {
@ -235,6 +312,7 @@ export default class SelectInspect implements IControls {
// 如果是点击事件,触发选中逻辑
const objects: THREE.Object3D[] = this.viewport.entityManager.getObjectByCanvasMouse(event)
if (objects.length > 0) {
console.log('mouseClick', objects)
const object = objects[0]
const entityId = object.userData.entityId
const item = this.viewport.entityManager.findItemById(entityId)
@ -244,7 +322,6 @@ export default class SelectInspect implements IControls {
this.viewport.state.selectedItem = markRaw(item)
this.viewport.state.selectedEntityId = entityId
this.viewport.state.selectedObjectMeta = getMeta(itemTypeName)
EventBus.dispatch('selectedObjectChanged', {
viewport: markRaw(this.viewport),
selectedObject: this.viewport.state.selectedObject,
@ -253,7 +330,71 @@ export default class SelectInspect implements IControls {
selectedObjectMeta: this.viewport.state.selectedObjectMeta
})
}
} else {
// 如果没有选中任何对象,清除选中状态
this.viewport.state.selectedObject = null
this.viewport.state.selectedItem = null
this.viewport.state.selectedEntityId = null
this.viewport.state.selectedObjectMeta = null
EventBus.dispatch('selectedObjectChanged', {
viewport: markRaw(this.viewport),
selectedObject: null,
selectedItem: null,
selectedEntityId: null,
selectedObjectMeta: null
})
}
}
}
private multipleSelectedObjects() {
if (!this.rectangle || !this.recStartPos) return
// 获取矩形的包围盒
const box = new THREE.Box3().setFromObject(this.rectangle)
// 获取盒子的 startX, startZ, endX, endZ
const startX = box.min.x
const startZ = box.min.z
const endX = box.max.x
const endZ = box.max.z
// 查找所有在矩形内的对象
const objects = this.viewport.entityManager.getObjectsInBox(startX, startZ, endX, endZ)
// 清空之前的多选对象
this.viewport.state.multiSelectedObjects = []
// 遍历找到的对象,添加到多选对象中
const multiSelectedObjects = []
const multiSelectedItems = []
const multiSelectedEntityIds = []
const multiSelectedObjectMetas = []
for (const object of objects) {
if (object.userData.entityId && object.userData.t) {
const item = this.viewport.entityManager.findItemById(object.userData.entityId)
if (item && item.dt.protected !== true) {
multiSelectedObjects.push(object)
multiSelectedItems.push(item)
multiSelectedEntityIds.push(object.userData.entityId)
multiSelectedObjectMetas.push(getMeta(object.userData.t))
}
}
}
// 触发多选对象更新事件
this.viewport.state.multiSelectedObjects = markRaw(objects)
this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems)
this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds
this.viewport.state.multiSelectedObjectMetas = multiSelectedObjectMetas
EventBus.dispatch('multiSelectedObjectsChanged', {
viewport: markRaw(this.viewport),
multiSelectedObjects: this.viewport.state.multiSelectedObjects,
multiSelectedItems: this.viewport.state.multiSelectedItems,
multiSelectedEntityIds: this.viewport.state.multiSelectedEntityIds,
multiSelectedObjectMetas: this.viewport.state.multiSelectedObjectMetas
})
}
}

118
src/core/engine/Viewport.ts

@ -20,6 +20,7 @@ import InteractionManager from '@/core/manager/InteractionManager'
import { calcPositionUseSnap } from '@/core/ModelUtils'
import StateManager from '@/core/manager/StateManager.ts'
import EventBus from '@/runtime/EventBus.ts'
import Constract from '@/core/Constract.ts'
import type { IMeta } from '@/core/base/IMeta.ts'
/**
@ -28,7 +29,7 @@ import type { IMeta } from '@/core/base/IMeta.ts'
*/
export default class Viewport {
viewerDom: HTMLElement
camera: THREE.OrthographicCamera
camera: THREE.Camera // THREE.OrthographicCamera
renderer: THREE.WebGLRenderer
statsControls: Stats
controls: OrbitControls
@ -65,7 +66,18 @@ export default class Viewport {
isReady: false,
isUpdating: false,
cursorMode: 'normal',
selectedObject: null,
selectedObject: undefined,
selectedItem: undefined,
selectedEntityId: undefined,
selectedObjectMeta: undefined,
multiSelectedObjects: [],
multiSelectedItems: [],
multiSelectedEntityIds: [],
multiSelectedObjectMetas: [],
view3DMode: Constract.Mode2D,
camera: {
position: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 }
@ -142,7 +154,14 @@ export default class Viewport {
this.renderer = renderer
// 创建正交摄像机
// this.initMode2DCamera()
this.watchList.push(watch(() => this.state.view3DMode, (newVal) => {
if (newVal === Constract.Mode3D) {
this.initMode3DCamera()
} else {
this.initMode2DCamera()
}
}, { immediate: true }))
// 注册拖拽组件
this.dragControl = new EsDragControls(this)
@ -165,6 +184,7 @@ export default class Viewport {
this.updateGridVisibility()
}))
// 监听窗口大小变化
if (this.resizeObserver) {
this.resizeObserver.unobserve(this.viewerDom)
@ -211,6 +231,44 @@ export default class Viewport {
}
/**
* 3D相机
*/
initMode3DCamera() {
if (this.camera) {
this.scene.remove(this.camera)
}
// ============================ 创建透视相机
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
}
/**
* 2D相机
*/
initMode2DCamera() {
@ -253,7 +311,7 @@ export default class Viewport {
controlsNew.maxDistance = 1000
this.controls = controlsNew
this.camera.updateProjectionMatrix()
cameraNew.updateProjectionMatrix()
this.syncCameraState()
}
@ -290,27 +348,33 @@ export default class Viewport {
syncCameraState() {
if (this.camera) {
const camera = this.camera
if (this.camera instanceof THREE.PerspectiveCamera) {
this.state.camera.position.x = camera.position.x
this.state.camera.position.y = (camera as THREE.PerspectiveCamera).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 = camera.zoom // this.getEffectiveViewDistance()
this.state.camera.position.y = 5
this.state.camera.position.z = camera.position.z
}
}
/**
*
*/
getEffectiveViewDistance() {
if (!this.camera) {
return 10
}
const camera = this.camera
const viewHeight = (camera.top - camera.bottom) / camera.zoom
// 假设我们希望匹配一个虚拟的透视相机(通常使用45度fov作为参考)
const referenceFOV = 45 // 参考视场角
return viewHeight / (2 * Math.tan(THREE.MathUtils.degToRad(referenceFOV) / 2))
}
// /**
// * 计算相机到目标的有效视距
// */
// getEffectiveViewDistance() {
// if (!this.camera || !(this.camera instanceof THREE.PerspectiveCamera)) {
// return 5
// }
// const camera = this.camera as THREE.PerspectiveCamera
// const viewHeight = (camera.top - camera.bottom) / camera.zoom
// // 假设我们希望匹配一个虚拟的透视相机(通常使用45度fov作为参考)
// const referenceFOV = 45 // 参考视场角
// return viewHeight / (2 * Math.tan(THREE.MathUtils.degToRad(referenceFOV) / 2))
// }
handleResize(entries: any) {
for (let entry of entries) {
// entry.contentRect包含了元素的尺寸信息
@ -358,6 +422,12 @@ export default class Viewport {
*
*/
updateGridVisibility() {
if (this.camera === undefined || !(this.camera instanceof THREE.PerspectiveCamera)) {
// 如果没有相机或相机不是透视相机,则不更新网格可见性
this.gridHelper.visible = true
this.gridHelper.material.opacity = 1
return
}
const cameraDistance = this.state.camera.position.y
const maxVisibleDistance = 4 // 网格完全不可见的最小距离
const fadeStartDistance = 9 // 开始淡出的最大距离
@ -490,18 +560,22 @@ export interface ViewportState {
cursorMode: string // CursorMode,
/**
*
*
*/
selectedObject: THREE.Object3D | undefined
selectedItem: ItemJson | undefined
selectedEntityId: string | undefined
selectedObjectMeta: IMeta | undefined
/**
*
*
*/
selectedObjectMeta: IMeta | undefined
multiSelectedObjects: THREE.Object3D[]
multiSelectedItems: ItemJson[]
multiSelectedEntityIds: string[]
multiSelectedObjectMetas: IMeta[]
view3DMode: string // Constract.Mode2D | Constract.Mode3D
/**
*

26
src/core/manager/EntityManager.ts

@ -2,7 +2,7 @@ import * as THREE from 'three'
import type Viewport from '@/core/engine/Viewport'
import type BaseRenderer from '@/core/base/BaseRenderer'
import { getRenderer } from './ModuleManager'
import { getLineId, parseLineId } from '@/core/ModelUtils'
import { getClosestObject, getLineId, parseLineId } from '@/core/ModelUtils'
import { Vector2 } from 'three'
/**
@ -541,11 +541,33 @@ export default class EntityManager {
if (!_intersections || _intersections.length === 0) {
return []
}
// 根据距离排序射线命中的对象集
return _.map(
_intersections.sort((a, b) => a.distance - b.distance),
r => r.object
r => getClosestObject(r.object)
).filter(obj => obj?.userData && obj.userData.selectable !== false)
}
/**
*
*/
getObjectsInBox(startX: number, startZ: number, endX: number, endZ: number) {
const box = new THREE.Box2(
new THREE.Vector2(startX, startZ),
new THREE.Vector2(endX, endZ)
)
const objectsInBox: THREE.Object3D[] = []
for (const [id, objects] of this.objects.entries()) {
for (const obj of objects) {
if (box.containsPoint(new Vector2(obj.position.x, obj.position.z))) {
objectsInBox.push(obj)
}
}
}
return objectsInBox
}
}

9
src/editor/Model2DEditor.vue

@ -7,6 +7,15 @@
<el-cascader placeholder="选择楼层" size="small" v-model="currentLevel"
:options="calcCatalog" filterable :show-all-levels="false" clearable
:props="{emitPath:false}" />
<span class="section-toolbar-line"></span>
<el-button-group>
<el-button :icon="renderIcon('antd CameraFilled')" link
:type="state?.view3DMode===Constract.Mode2D?'primary':''"
@click="state.view3DMode = Constract.Mode2D">2D</el-button>
<el-button :icon="renderIcon('fa CameraRetro')" link
:type="state?.view3DMode===Constract.Mode3D?'primary':''"
@click="state.view3DMode = Constract.Mode3D">3D</el-button>
</el-button-group>
</div>
<div class="section-content">
<div v-if="currentStateManagerId" :key="currentStateManagerId || getRandom()"

2
src/editor/widgets/property/PropertyView.vue

@ -93,12 +93,14 @@ export default {
selectedObjectChanged(state) {
const data = state.selectedItem;
console.log("selectedObjectChanged data", data)
if(data) {
this.viewport.stateManager.beginStateUpdate()
const item = _.find(this.viewport.stateManager.vdata.items, item => item.id === data.id)
// item.tf[0][0] = item.tf[0][0] / 2;
console.log("selectedObjectChanged item", item)
// _.extend(item, data)
this.viewport.stateManager.endStateUpdate()
}
},
},
mounted() {

14
src/modules/gstore/GstoreRenderer.ts

@ -56,7 +56,7 @@ export default class GstoreRenderer extends BaseRenderer {
group.name = GstoreRenderer.POINT_NAME
// 绘制背景矩形框
const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth);
const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth)
planeGeometry.rotateX(-Math.PI / 2)
const planeMaterial = new THREE.MeshBasicMaterial({
color: 'white',
@ -64,8 +64,8 @@ export default class GstoreRenderer extends BaseRenderer {
opacity: 0.2, // 50%透明度
depthWrite: false, // 防止深度冲突
side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
});
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
})
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
group.add(planeMesh)
if (!item.dt.storeWidth || !item.dt.storeDepth) {
@ -82,7 +82,7 @@ export default class GstoreRenderer extends BaseRenderer {
lineXLen / 2, lineYLen / 2, 0,
-(lineXLen / 2), lineYLen / 2, 0,
-(lineXLen / 2), -(lineYLen / 2), 0
]);
])
lineGeometry.rotateX(-Math.PI / 2)
const lineMaterial = new LineMaterial({
color: 0x00ff00,
@ -90,9 +90,9 @@ export default class GstoreRenderer extends BaseRenderer {
worldUnits: true,
resolution: new THREE.Vector2(window.innerWidth, window.innerHeight),
side: THREE.DoubleSide
});
})
//
const line = new Line2(lineGeometry, lineMaterial);
const line = new Line2(lineGeometry, lineMaterial)
group.add(line as THREE.Object3D)
return [group]
@ -100,6 +100,6 @@ export default class GstoreRenderer extends BaseRenderer {
dispose() {
super.dispose()
this.pointMaterial.dispose()
this.pointMaterial?.dispose()
}
}

41
src/modules/rack/RackMeta.ts

@ -7,6 +7,47 @@ export default [
{ editor: 'TransformEditor', category: 'basic' },
{ field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' },
{ editor: '-', category: 'basic' },
{ field: 'dt.rackDepth', editor: 'NumberInput', label: '货架深度', category: 'basic' },
{ field: 'dt.levelCount', editor: 'NumberInput', label: '总层数', category: 'basic' },
{ field: 'dt.bayCount', editor: 'NumberInput', label: '总列数', category: 'basic' },
{ field: 'dt.hideFloor', editor: 'NumberInput', label: '隐藏底板', category: 'basic' },
{ field: 'dt.extendColumns', editor: 'NumberInput', label: '扩展挡板', category: 'basic' },
{ field: 'dt.columnSpacing', editor: 'NumberInput', label: '支脚跨越', category: 'basic' },
{ field: 'dt.bays', editor: 'BayEditor', category: 'basic' },
/**
* dt.bays 53
* {
* dt: {
* rackDepth: 1.1, // 货架深度
* levelCount: 3, // 总层数
* bayCount: 5, // 总列数
* hideFloor: false, // 隐藏底板
* extendColumns: true, // 扩展挡板
* columnSpacing: 1, // 支脚跨越
* bays: [ // 每列的配置
* {
* bayWidth: 1.6, // 列的宽度
* levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度
* },
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]},
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]},
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]},
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]},
* ]
* }
* }
*
*
*
*
*
*
*
*/
{ field: 'tf', editor: 'InOutCenterEditor', category: 'basic' },
{ field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' },
{ field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' },

2
src/modules/way/WayRenderer.ts

@ -87,7 +87,7 @@ export default class WayRenderer extends BaseRenderer {
const width = 1
const curve = new THREE.LineCurve3(startPosition, endPosition)
const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 8, false)
const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 2, false)
const lineMesh = new THREE.Mesh(tubeGeometry, this.lineMaterial)
group.add(lineMesh)

3
src/runtime/EventBus.ts

@ -6,7 +6,8 @@ export type DispatchNames = 'selectedObjectChanged' |
'catalogChanged' |
'dataLoadComplete' |
'entityDeleted' |
'selectedObjectPropertyChanged'
'selectedObjectPropertyChanged' |
'multiSelectedObjectsChanged'
export default {
dispatch(name: DispatchNames, data?: any) {

Loading…
Cancel
Save