10 changed files with 498 additions and 51 deletions
@ -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 |
||||
|
} |
||||
@ -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 { |
||||
|
} |
||||
|
} |
||||
@ -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[] |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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({ |
export default defineItemType({ |
||||
name: 'measure', |
name: 'measure', |
||||
label: '测量工具', |
label: '测量距离', |
||||
category: 'line' |
actionType: 'ln', |
||||
|
clazz: new Measure() |
||||
}) |
}) |
||||
@ -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) |
|
||||
}) |
|
||||
} |
|
||||
@ -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…
Reference in new issue