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({ |
|||
name: 'measure', |
|||
label: '测量工具', |
|||
category: 'line' |
|||
label: '测量距离', |
|||
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