Browse Source

WorldModel.Measure

master
修宁 7 months ago
parent
commit
afd6d11f9a
  1. 49
      src/model/ModelUtils.ts
  2. 24
      src/model/WorldModel.ts
  3. 51
      src/model/example1.js
  4. 29
      src/model/itemTypeDefine/ItemTypeBase.ts
  5. 131
      src/model/itemTypeDefine/ItemTypeDefine.ts
  6. 128
      src/model/itemTypeDefine/ItemTypeLineBase.ts
  7. 71
      src/model/itemTypeDefine/measure/Measure.ts
  8. 8
      src/model/itemTypeDefine/measure/MeasureMeta.ts
  9. 28
      src/runtime/DefineItem.ts
  10. 28
      src/runtime/DefineItemType.ts

49
src/model/ModelUtils.ts

@ -0,0 +1,49 @@
import * as THREE from 'three'
import type { ItemJson } from '@/model/itemTypeDefine/ItemTypeDefine.ts'
import { getAllItemTypes, getItemTypeByName } from '@/runtime/DefineItemType.ts'
export function loadSceneFromJson(scene: THREE.Scene, items: ItemJson[]) {
const object3ds = loadObject3DFromJson(items)
// 通知所有加载的对象, 模型加载完成
getAllItemTypes().forEach(itemType => {
if (typeof itemType.clazz.afterLoadComplete === 'function') {
itemType.clazz.afterLoadComplete(object3ds)
}
})
object3ds.forEach(object3D => {
scene.add(object3D)
})
// 通知所有加载的对象, 模型加载完成
getAllItemTypes().forEach(itemType => {
itemType.clazz.afterAddScene(scene, object3ds)
})
}
function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] {
const result: THREE.Object3D[] = []
for (const item of items) {
if (!item || !item.t) {
console.error('unkown item:', item)
continue
}
const object3D: THREE.Object3D | undefined = getItemTypeByName(item.t)?.clazz.loadFromJson(item)
if (object3D === undefined) {
continue
}
if (_.isArray(item.items)) {
// 如果有子元素,递归处理
const children = loadObject3DFromJson(item.items)
children.forEach(child => object3D.add(child))
}
result.push(object3D)
}
return result
}

24
src/model/WorldModel.ts

@ -1,9 +1,12 @@
import _ from 'lodash'
import Example1 from './example1'
import { markRaw, reactive } from 'vue'
import * as THREE from 'three'
import { Scene } from 'three'
import type Viewport from '@/designer/Viewport.ts'
import * as THREE from 'three'
import { loadSceneFromJson } from '@/model/ModelUtils.ts'
import MeasureMeta from './itemTypeDefine/measure/MeasureMeta'
/**
*
@ -21,6 +24,23 @@ export default class WorldModel {
init() {
window['worldModel'] = this
Promise.all([
MeasureMeta.clazz.init(this)
]).then(() => {
console.log('世界模型初始化完成')
})
}
loadFloorToScene(scene: THREE.Scene, levelCode: string) {
const floor = _.find(this.data.items, r => r.name === levelCode && r.t === 'floor')
if (!floor) {
console.warn(`未找到楼层数据: ${levelCode}`)
return []
}
loadSceneFromJson(scene, floor.items)
}
open() {
@ -63,6 +83,8 @@ export default class WorldModel {
createScene(floor: string) {
const scene = new Scene()
scene.background = new THREE.Color(0xeeeeee)
this.loadFloorToScene(scene, floor)
return scene
}

51
src/model/example1.js

@ -13,34 +13,49 @@ export default {
{ name: 'OnStop', fn: '' }
]
},
item: [
items: [
{
name: 'f1', type: 'floor', uuid: 'f1',
name: 'f1', t: 'floor', // 楼层
items: [
{
uuid: 'measure-group', type: 'group', items: [
name: 'measure-group', t: 'measure', a: 'gp', // 类型, itemType.name == 'measure' 的组件处理. a:'gp' 代表分组, 渲染时他会是 Three.Group
items: [
{
uuid: 'p1',
type: 'measure',
category: 'line',
pos: [],
rotation: [0, 0, 0],
scale: [1, 1, 1],
link: ['p2']
id: 'p1', // 物体ID, 唯一标识, 需保证唯一, three.js 中的 uuid
t: 'measure', // 物体类型, measure表示测量, 需交给 itemType.name == 'measure' 的组件处理
a: 'ln', // 交互类型, ln表示线点操作, pt 表示点操作
l: '测量1', // 标签名称, 显示用
c: '#ff0000', // 颜色, 显示用. 十六进制颜色值, three.js 中的材质颜色
tf: [ // 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前的右手坐标系
[-9.0, 0, -1.0], // 平移向量 position
[0, 0, 0], // 旋转向量 rotation, 表示绕Y轴旋转的角度, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算
[0.25, 0.1, 0.25] // 缩放向量 scale
],
dt: { // 用户数据, 可自定义, 一般用在 three.js 的 userData 中
link: ['p2'], // 用于 a='ln' 的测量线段, 关联的点对象(uuid)
center: [], // 物流关联对象(uuid)
in: [], // 物流入方向关联的对象(uuid)
out: [] // 物流出方向关联的对象(uuid)
}
},
{
uuid: 'p2',
type: 'measure',
category: 'line',
pos: [],
rotation: [0, 0, 0],
scale: [1, 1, 1],
id: 'p2',
t: 'measure', a: 'ln', l: '测量2', c: '#ff0000',
tf: [[-9.0, 0, 3], [0, 0, 0], [0.25, 0.1, 0.25]],
dt: {
link: ['p3']
}
},
{ uuid: 'p3', type: 'measure', category: 'line', pos: [], rotation: [0, 0, 0], scale: [1, 1, 1], link: [] }
{
id: 'p3',
t: 'measure', a: 'ln', l: '测量3', c: '#ff0000',
tf: [[-5.0, 0, 3], [0, 0, 0], [0.25, 0.1, 0.25]],
dt: {
link: []
}
}
]
}
]
}
],

29
src/model/itemTypeDefine/ItemTypeBase.ts

@ -0,0 +1,29 @@
import { type Object3D } from 'three'
import type WorldModel from '@/model/WorldModel.ts'
import type { ItemJson, ItemTypeDefineOption } from '@/model/itemTypeDefine/ItemTypeDefine.ts'
import * as THREE from 'three'
export default abstract class ItemTypeBase {
name: string
option: ItemTypeDefineOption
worldModel: WorldModel
public init(worldModel: WorldModel) {
this.worldModel = worldModel
// 初始化方法,子类可以重写
return Promise.resolve()
}
abstract loadFromJson(item: ItemJson): undefined | THREE.Object3D
afterLoadComplete(objects: THREE.Object3D[]): THREE.Object3D[] {
return []
}
/**
* scene
*/
afterAddScene(scene: THREE.Scene, objects: THREE.Object3D[]): void {
}
}

131
src/model/itemTypeDefine/ItemTypeDefine.ts

@ -0,0 +1,131 @@
import type ItemTypeBase from '@/model/itemTypeDefine/ItemTypeBase.ts'
export type ActionType =
/**
* 线
*/
'ln' |
/**
*
*/
'pt' |
/**
*
*/
'fl' |
/**
*
*/
'gp'
export interface ItemTypeDefineOption {
name: string
label: string
actionType: ActionType
clazz: ItemTypeBase
}
/**
*
* :
* {
* id: 'p1', // 物体ID, 唯一标识, 需保证唯一, three.js 中的 uuid
* t: 'measure', // 物体类型, measure表示测量, 需交给 itemType.name == 'measure' 的组件处理
* a: 'ln', // 交互类型, ln表示线点操作, pt 表示点操作
* l: '测量1', // 标签名称, 显示用
* c: '#ff0000', // 颜色, 显示用. 十六进制颜色值, three.js 中的材质颜色
* tf: [ // 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前的右手坐标系
* [-9.0, 0, -1.0], // 平移向量 position
* [0, 0, 0], // 旋转向量 rotation, 表示绕Y轴旋转的角度, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算
* [0.25, 0.1, 0.25] // 缩放向量 scale
* ],
* dt: { // 用户数据, 可自定义, 一般用在 three.js 的 userData 中
* link: ['p2'], // 用于 a='ln' 的测量线段, 关联的点对象(uuid)
* center: [], // 物流关联对象(uuid)
* in: [], // 物流入方向关联的对象(uuid)
* out: [] // 物流出方向关联的对象(uuid)
* }
* }
*/
export interface ItemJson {
/**
* , , three.js name , ,
*/
name?: string
/**
* three.js uuid, ID, ,
*/
id: string
/**
* , defineItemType.name
*/
t: string
/**
*
*/
a: ActionType
/**
* , , three.js userData.label
*/
l?: string
/**
* , three.js userData.color
*/
c: string
/**
* , 3x3矩阵, Y轴向上为正, X轴向右, Z轴向前的右手坐标系
*/
tf: [
/**
* position,
*/
[number, number, number],
/**
* rotation, Y轴旋转的角度, three.js "角度""弧度"
*/
[number, number, number],
/**
* scale,
*/
[number, number, number],
]
/**
* , , three.js userData
*/
dt: {
/**
* 线(uuid), a='ln'
*/
link?: string[]
/**
* (uuid)
*/
center?: string[]
/**
* (uuid)
*/
in?: string[]
/**
* (uuid)
*/
out?: string[]
/**
* ,
*/
[key: string]: any
},
/**
* , ,
*/
items: ItemJson[]
}

128
src/model/itemTypeDefine/ItemTypeLineBase.ts

@ -0,0 +1,128 @@
import * as THREE from 'three'
import ItemTypeBase from '@/model/itemTypeDefine/ItemTypeBase.ts'
import type { ItemJson } from '@/model/itemTypeDefine/ItemTypeDefine.ts'
import type WorldModel from '@/model/WorldModel.ts'
/**
* ILineType 线
* 线
*/
export default abstract class ItemTypeLineBase extends ItemTypeBase {
/**
*
*/
pointArray: THREE.Object3D[] = []
/**
* 线
*/
linkArray: THREE.Object3D[] = []
public init(worldModel: WorldModel) {
return super.init(worldModel).then(() => {
// 初始化方法,子类可以重写
this.pointArray.length = 0
this.linkArray.length = 0
})
}
afterLoadGroup(group: THREE.Group): void {
}
afterLoadLine(line: THREE.Line): void {
}
afterLoadPoint(point: THREE.Object3D): void {
}
afterAddScene(scene: THREE.Scene, objects: THREE.Object3D[]) {
super.afterAddScene(scene, objects)
// 为所有的 pointArray 连接线
for (let i = 0; i < this.pointArray.length; i++) {
const startPoint = this.pointArray[i]
// 找到这个元素的 userData.link 数组
const linkArray: string[] = startPoint.userData.link || []
for (let j = 0; j < linkArray.length; j++) {
const linkId = linkArray[j]
// 在 pointArray 中查找对应的点
const endPoint = this.pointArray.find(p => p.uuid === linkId)
if (!endPoint) {
console.warn('not found link point uuid=${}', linkId)
continue
}
const line = this.createLine()
const geom = line.geometry
geom.setFromPoints([startPoint.position, endPoint.position])
this.afterLoadLine(line)
if (startPoint.parent) {
startPoint.parent.add(line)
} else {
scene.add(line)
}
this.linkArray.push(line)
}
}
}
override loadFromJson(item: ItemJson): undefined | THREE.Object3D {
if (item.a === 'gp') {
// gp 是为了分组而存在的
const group = new THREE.Group()
group.name = item.name
group.uuid = item.id || THREE.MathUtils.generateUUID()
group.userData = _.cloneDeep(item.dt) || {}
group.userData.type = item.t
group.userData.actionType = item.a
group.userData.label = item.l
group.userData.color = item.c
this.afterLoadGroup(group)
return group
}
// 其他情况都是 ln
else if (item.a === 'ln') {
const position = new THREE.Vector3(
item.tf[0][0],
item.tf[0][1],
item.tf[0][2]
)
const point = this.createPoint(position)
point.name = item.name
point.uuid = item.id || THREE.MathUtils.generateUUID()
point.userData = _.cloneDeep(item.dt) || {}
point.userData.type = item.t
point.userData.actionType = item.a
point.userData.label = item.l
point.userData.color = item.c
point.rotation.set(
THREE.MathUtils.degToRad(item.tf[1][0]),
THREE.MathUtils.degToRad(item.tf[1][1]),
THREE.MathUtils.degToRad(item.tf[1][2])
)
point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2])
this.pointArray.push(point)
this.afterLoadPoint(point)
return point
}
console.error('ItemTypeLineBase.loadFromJson: Unsupported', item)
}
abstract createPoint(position: THREE.Vector3): THREE.Object3D
abstract createLine(): THREE.Line
}

71
src/model/itemTypeDefine/measure/Measure.ts

@ -0,0 +1,71 @@
import * as THREE from 'three'
import ItemTypeLineBase from '@/model/itemTypeDefine/ItemTypeLineBase.ts'
import type WorldModel from '@/model/WorldModel.ts'
import { Material } from 'three/src/materials/Material'
export const TYPE_NAME = 'measure'
export const POINT_NAME = 'measure_point'
export const LABEL_NAME = 'measure_label'
export const LINE_NAME = 'measure_line'
export default class Measure extends ItemTypeLineBase {
/**
* , 线. 线
*/
group: THREE.Group
pointMaterial!: Material
lineMaterial!: Material
override init(worldModel: WorldModel): Promise<void> {
super.init(worldModel)
this.lineMaterial = new THREE.LineBasicMaterial({
color: 0xE63C17,
linewidth: 2,
opacity: 0.9,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false
})
this.pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
return Promise.resolve()
}
// 创建完线之后,创建 label
afterLoadLine(line: THREE.Line) {
super.afterLoadLine(line)
}
/**
*
*/
createPoint(position?: THREE.Vector3): THREE.Object3D {
const p = position
const scale = 0.25
const tt = new THREE.BoxGeometry(1, 1, 1)
const obj = new THREE.Mesh(tt, this.pointMaterial)
obj.scale.set(scale, 0.1, scale)
if (p) {
obj.position.set(p.x, p.y, p.z)
}
obj.name = POINT_NAME
return obj
}
/**
* 线
*/
createLine(): THREE.Line {
const geom = new THREE.BufferGeometry()
const obj = new THREE.Line(geom, this.lineMaterial)
obj.frustumCulled = false
obj.name = LINE_NAME
obj.uuid = THREE.MathUtils.generateUUID()
return obj
}
}

8
src/model/itemTypeDefine/measure/MeasureMeta.ts

@ -1,7 +1,9 @@
import { defineItemType } from '@/runtime/DefineItem.ts'
import { defineItemType } from '@/runtime/DefineItemType.ts'
import Measure from '@/model/itemTypeDefine/measure/Measure.ts'
export default defineItemType({
name: 'measure',
label: '测量工具',
category: 'line'
label: '测量距离',
actionType: 'ln',
clazz: new Measure()
})

28
src/runtime/DefineItem.ts

@ -1,28 +0,0 @@
/**
*
*/
export class ItemTypeDefine {
option!: ItemOption
constructor(option: ItemOption) {
this.option = option
}
}
export type ItemCategory = 'point' | 'line' | 'store' | 'executer' | 'flow_item' | 'other'
export interface ItemOption {
name: string
label: string
category: ItemCategory
}
/**
*
*/
export function defineItemType(option: ItemOption): Promise<ItemTypeDefine> {
return new Promise((resolve, reject) => {
const item = new ItemTypeDefine(option)
resolve(item)
})
}

28
src/runtime/DefineItemType.ts

@ -0,0 +1,28 @@
import _ from 'lodash'
import type { ItemTypeDefineOption } from '@/model/itemTypeDefine/ItemTypeDefine.ts'
const itemTypes: Record<string, ItemTypeDefineOption> = {}
window['itemTypes'] = itemTypes
/**
*
*/
export function defineItemType(option: ItemTypeDefineOption) {
itemTypes[option.name] = option
option.clazz.name = option.name
option.clazz.option = option
return option
}
export function getItemTypeByName(type: string): ItemTypeDefineOption {
const itemType = _.get(itemTypes, type)
if (!itemType) {
console.warn(`未找到物流单元类型定义: ${type}`)
return
}
return itemType
}
export function getAllItemTypes(): ItemTypeDefineOption[] {
return Object.values(itemTypes)
}
Loading…
Cancel
Save