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.
282 lines
7.3 KiB
282 lines
7.3 KiB
import * as THREE from 'three'
|
|
import gsap from 'gsap'
|
|
import type { Object3DLike } from '@/types/ModelTypes.ts'
|
|
import Viewport from '@/core/engine/Viewport.ts'
|
|
import TaskQueue, { type Task } from '@/core/base/TaskQueue.ts'
|
|
import type { MeshWrap } from '@/core/manager/InstanceMeshManager.ts'
|
|
import { getRenderer } from '@/core/manager/ModuleManager.ts'
|
|
|
|
/**
|
|
* 物流实体基类
|
|
*/
|
|
export default abstract class BaseEntity implements EntityIf, Loadable, Carry, Walk, ForkArm, LiftingArm {
|
|
protected readonly viewport: Viewport
|
|
protected readonly taskQueue: TaskQueue = new TaskQueue()
|
|
readonly id: string
|
|
readonly item: ItemJson
|
|
readonly object: THREE.Object3D
|
|
readonly t: string
|
|
|
|
isArmExtended: boolean = false
|
|
isArmRaised: boolean = false
|
|
isCarrying: boolean = false
|
|
isLoading: boolean = false
|
|
isUnloading: boolean = false
|
|
isWalking: boolean = false
|
|
isPaused: boolean = false
|
|
|
|
constructor(viewport: Viewport, id: string) {
|
|
this.viewport = viewport
|
|
this.id = id
|
|
this.item = viewport.find(id)
|
|
this.object = viewport.entityManager.findObjectById(id) as THREE.Object3D
|
|
this.t = this.item.t
|
|
}
|
|
|
|
// 帮助方法:获取机械臂对象
|
|
getArmObject(): THREE.Object3D | undefined {
|
|
console.error('getArmObject 方法未实现')
|
|
return undefined
|
|
}
|
|
|
|
// 帮助方法:获取机械臂立柱
|
|
getArmPillar(): THREE.Object3D | undefined {
|
|
console.error('getArmPillar 方法未实现')
|
|
return undefined
|
|
}
|
|
|
|
// 待执行任务总数
|
|
get taskCount(): number {
|
|
return this.taskQueue.length
|
|
}
|
|
|
|
// 是否正在执行任务
|
|
get taskIsRunning(): boolean {
|
|
return this.taskQueue.isExecuting
|
|
}
|
|
|
|
// 取消所有任务
|
|
cancelTask(): void {
|
|
this.taskQueue.cancelAll()
|
|
}
|
|
|
|
// 暂停任务
|
|
taskPause(): void {
|
|
this.isPaused = true
|
|
this.taskQueue.pause()
|
|
}
|
|
|
|
// 恢复任务
|
|
taskResume(): void {
|
|
this.isPaused = false
|
|
this.taskQueue.resume()
|
|
}
|
|
|
|
// 开始执行任务
|
|
taskStartRun(): void {
|
|
this.taskQueue.start()
|
|
}
|
|
|
|
// 创建基础任务
|
|
protected createTask<T extends Task>(type: string, execute: () => Promise<void>, cancel?: () => void): T {
|
|
return { type, execute, cancel, isCancelable: true } as T
|
|
}
|
|
|
|
// 上
|
|
addArmRaise(height: number): void {
|
|
this.taskQueue.add(this.createTask('ARM_RAISE', async () => {
|
|
return new Promise(resolve => {
|
|
const arm = this.getArmObject()
|
|
if (!arm) return resolve()
|
|
|
|
gsap.to(arm.position, {
|
|
y: height,
|
|
duration: 3,
|
|
ease: 'sine.inOut',
|
|
onComplete: resolve
|
|
})
|
|
})
|
|
}, () => {
|
|
// 取消动画
|
|
gsap.killTweensOf(this.getArmObject()?.position)
|
|
}))
|
|
}
|
|
|
|
// 下
|
|
addArmLower(): void {
|
|
this.taskQueue.add(this.createTask('ARM_LOWER', async () => {
|
|
return new Promise(resolve => {
|
|
const arm = this.getArmObject()
|
|
if (!arm) return resolve()
|
|
|
|
gsap.to(arm.position, {
|
|
y: 0,
|
|
duration: 3,
|
|
ease: 'sine.inOut',
|
|
onComplete: resolve
|
|
})
|
|
})
|
|
}, () => {
|
|
gsap.killTweensOf(this.getArmObject()?.position)
|
|
}))
|
|
}
|
|
|
|
// 伸
|
|
addArmExtender(z: number = 1.3): void {
|
|
this.taskQueue.add(this.createTask('ARM_EXTEND', async () => {
|
|
return new Promise(resolve => {
|
|
const pillar = this.getArmPillar()
|
|
if (!pillar) return resolve()
|
|
|
|
gsap.to(pillar.position, {
|
|
z: -1.2,
|
|
duration: 3,
|
|
ease: 'sine.inOut',
|
|
onComplete: () => {
|
|
this.isArmExtended = true
|
|
resolve()
|
|
}
|
|
})
|
|
})
|
|
}, () => {
|
|
gsap.killTweensOf(this.getArmPillar()?.position)
|
|
}))
|
|
}
|
|
|
|
// 缩
|
|
addArmRetractor(): void {
|
|
this.taskQueue.add(this.createTask('ARM_RETRACT', async () => {
|
|
return new Promise(resolve => {
|
|
const pillar = this.getArmPillar()
|
|
if (!pillar) return resolve()
|
|
|
|
gsap.to(pillar.position, {
|
|
z: 0,
|
|
duration: 3,
|
|
ease: 'sine.inOut',
|
|
onComplete: () => {
|
|
this.isArmExtended = false
|
|
resolve()
|
|
}
|
|
})
|
|
})
|
|
}, () => {
|
|
gsap.killTweensOf(this.getArmPillar()?.position)
|
|
}))
|
|
}
|
|
|
|
// 装
|
|
addLoad(item: string): void {
|
|
this.taskQueue.add(this.createTask('LOAD', async () => {
|
|
// 实际业务中应包含装载逻辑
|
|
this.isCarrying = true
|
|
throw new Error(`装载物品 ${item} 的逻辑未实现`)
|
|
}))
|
|
}
|
|
|
|
// 卸
|
|
addUnload(item: string, rack: string, bay?: number, level?: number): void {
|
|
this.taskQueue.add(this.createTask('UNLOAD', async () => {
|
|
// 实际业务中应包含卸载逻辑
|
|
this.isCarrying = false
|
|
throw new Error(`装载物品 ${item} 的逻辑未实现`)
|
|
}))
|
|
}
|
|
|
|
// 转
|
|
addRotation(angle: number): void {
|
|
this.taskQueue.add(this.createTask('ROTATION', async () => {
|
|
return new Promise(resolve => {
|
|
const time = Math.abs(angle - THREE.MathUtils.radToDeg(this.object.rotation.y)) / 45
|
|
const duration = Math.max(0.5, time)
|
|
|
|
gsap.to(this.object.rotation, {
|
|
y: THREE.MathUtils.degToRad(angle),
|
|
duration,
|
|
ease: 'none',
|
|
onComplete: resolve
|
|
})
|
|
})
|
|
}, () => {
|
|
gsap.killTweensOf(this.object.rotation)
|
|
}))
|
|
}
|
|
|
|
// 走
|
|
addTravel(pos: Vector3IF | string): void {
|
|
this.taskQueue.add(this.createTask('TRAVEL', async () => {
|
|
return new Promise(resolve => {
|
|
const fromPos = this.object.position
|
|
|
|
let position: Vector3IF
|
|
if (pos instanceof String || typeof pos === 'string') {
|
|
pos = Model.getPositionByEntityId(pos as string)
|
|
}
|
|
|
|
const toPos = new THREE.Vector3(pos.x, pos.y, pos.z)
|
|
const distance = fromPos.distanceTo(toPos)
|
|
const speed = 2 // 2m/s
|
|
const duration = Math.max(1.5, distance / speed)
|
|
|
|
this.isWalking = true
|
|
|
|
gsap.to(this.object.position, {
|
|
x: toPos.x,
|
|
y: toPos.y,
|
|
z: toPos.z,
|
|
duration,
|
|
ease: 'sine.inOut',
|
|
onComplete: () => {
|
|
this.isWalking = false
|
|
resolve()
|
|
}
|
|
})
|
|
})
|
|
}, () => {
|
|
this.isWalking = false
|
|
gsap.killTweensOf(this.object.position)
|
|
}))
|
|
}
|
|
|
|
/**
|
|
* 拾取物品
|
|
*/
|
|
pickupItem(id: string): THREE.Object3D {
|
|
// 找到物品所在的地方, 删除他
|
|
const item = this.viewport.entityManager.findItemById(id)
|
|
const wrap = this.viewport.entityManager.findObjectById(id) as MeshWrap
|
|
|
|
if (wrap.type !== 'MeshWrap') {
|
|
throw new Error(`无法拾取物品 ${id},它不是一个有效的 MeshWrap`)
|
|
}
|
|
item.dt.storeAt = {
|
|
item: this.id
|
|
}
|
|
const mesh = wrap.manager.wrapToObject3D(wrap)
|
|
this.viewport.entityManager.replaceObject(id, mesh)
|
|
return mesh
|
|
}
|
|
|
|
/**
|
|
* 卸下物品
|
|
*/
|
|
dropItem(item: ItemJson, storeItemId: string, bay?: number, level?: number, cell?: number): void {
|
|
item.dt.storeAt = {
|
|
item: storeItemId,
|
|
bay: bay,
|
|
level: level,
|
|
cell: cell
|
|
}
|
|
|
|
const itemRenderer = getRenderer(item.t)
|
|
if (!itemRenderer) {
|
|
throw new Error(`Renderer for type ${item.t} not found`)
|
|
}
|
|
|
|
this.getArmObject().clear()
|
|
|
|
itemRenderer.tempViewport = this.viewport
|
|
itemRenderer.createOrUpdatePointForRuntime(item)
|
|
itemRenderer.tempViewport = null
|
|
}
|
|
}
|
|
|