6 changed files with 526 additions and 58 deletions
@ -1,30 +1,177 @@ |
|||
import * as THREE from 'three' |
|||
import type IControls from '@/core/controls/IControls.ts' |
|||
import type Viewport from '@/core/engine/Viewport.ts' |
|||
import { Vector3 } from 'three/src/math/Vector3' |
|||
import { Euler } from 'three/src/math/Euler' |
|||
|
|||
export default class InstancePointManager implements IControls { |
|||
viewport: Viewport |
|||
mesh: THREE.InstancedMesh |
|||
dummy: THREE.Object3D = new THREE.Object3D() |
|||
count: number = 0 |
|||
maxInstances: number = 100000 |
|||
export default class InstancePointManager { |
|||
private readonly viewport: Viewport |
|||
private readonly instancedMesh: THREE.InstancedMesh |
|||
private readonly freeIndices: number[] = [] |
|||
private readonly maxInstanceCount: number |
|||
private readonly geometry: THREE.BufferGeometry |
|||
private readonly material: THREE.Material |
|||
private readonly dummy: THREE.Object3D = new THREE.Object3D() |
|||
// itemId -> instanceId
|
|||
private instanceData: Map<string, number> = new Map() |
|||
|
|||
init(viewport: Viewport): void { |
|||
/** |
|||
* 创建点实例 |
|||
* @param item 点数据 |
|||
* @returns 分配的实例ID (如果失败返回-1) |
|||
*/ |
|||
createPoint(item: ItemJson): number { |
|||
if (this.freeIndices.length === 0) { |
|||
system.showErrorDialog('InstancePointManager is full') |
|||
return -1 |
|||
} |
|||
|
|||
const instanceId = this.freeIndices.pop()! |
|||
this.instanceData.set(item.id, instanceId) |
|||
|
|||
this.updatePoint(item) |
|||
return instanceId |
|||
} |
|||
|
|||
/** |
|||
* 更新点实例 |
|||
* @param item 点数据 |
|||
* @param option 更新选项 |
|||
*/ |
|||
updatePoint(item: ItemJson, option: { |
|||
position?: Vector3 |
|||
rotation?: Vector3 |
|||
scale?: Vector3 |
|||
} = {}): void { |
|||
const instanceId = this.instanceData.get(item.id) |
|||
if (instanceId === undefined) return |
|||
|
|||
let [position, rotation, scale] = item.tf |
|||
if (option.position) { |
|||
position = option.position.toArray() |
|||
} |
|||
if (option.rotation) { |
|||
rotation = option.rotation.toArray() |
|||
} |
|||
if (option.scale) { |
|||
scale = option.scale.toArray() |
|||
} |
|||
|
|||
this.dummy.position.set(position[0], position[1], position[2]) |
|||
this.dummy.rotation.set( |
|||
THREE.MathUtils.degToRad(rotation[0]), |
|||
THREE.MathUtils.degToRad(rotation[1]), |
|||
THREE.MathUtils.degToRad(rotation[2]) |
|||
) |
|||
this.dummy.scale.set(scale[0], scale[1], scale[2]) |
|||
|
|||
if (item.v === false) { |
|||
this.dummy.scale.set(0, 0, 0) |
|||
} |
|||
this.dummy.updateMatrix() |
|||
|
|||
this.instancedMesh.setMatrixAt(instanceId, this.dummy.matrix) |
|||
this.instancedMesh.instanceMatrix.needsUpdate = true |
|||
} |
|||
|
|||
/** |
|||
* 删除点实例 |
|||
* @param id 点ID |
|||
*/ |
|||
deletePoint(id: string): void { |
|||
const instanceId = this.instanceData.get(id) |
|||
if (instanceId === undefined) return |
|||
|
|||
// 隐藏实例
|
|||
this.dummy.scale.set(0, 0, 0) |
|||
this.dummy.updateMatrix() |
|||
this.instancedMesh.setMatrixAt(instanceId, this.dummy.matrix) |
|||
this.instancedMesh.instanceMatrix.needsUpdate = true |
|||
|
|||
// 回收索引
|
|||
this.freeIndices.push(instanceId) |
|||
this.instanceData.delete(id) |
|||
} |
|||
|
|||
/** |
|||
* 获取点实例的世界位置 |
|||
* @param id 点ID |
|||
* @param target 目标向量 |
|||
*/ |
|||
getWorldPosition(id: string, target: THREE.Vector3): void { |
|||
const instanceId = this.instanceData.get(id) |
|||
if (instanceId === undefined) return |
|||
|
|||
const matrix = new THREE.Matrix4() |
|||
this.instancedMesh.getMatrixAt(instanceId, matrix) |
|||
target.setFromMatrixPosition(matrix) |
|||
} |
|||
|
|||
// init(viewport: Viewport): void {
|
|||
// this.viewport = viewport
|
|||
//
|
|||
// const geometry = new THREE.PlaneGeometry(0.25, 0.25)
|
|||
// const material = new THREE.MeshBasicMaterial({
|
|||
// color: 0xFFFF99,
|
|||
// transparent: true,
|
|||
// depthWrite: false,
|
|||
// side: THREE.DoubleSide
|
|||
// })
|
|||
//
|
|||
// this.mesh = new THREE.InstancedMesh(geometry, material, this.maxInstances)
|
|||
// this.mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
|
|||
// viewport.scene.add(this.mesh)
|
|||
// }
|
|||
|
|||
|
|||
public static create( |
|||
viewport: Viewport, |
|||
geometry: THREE.BufferGeometry, |
|||
material: THREE.Material, |
|||
maxInstances: number = 1000 |
|||
): InstancePointManager { |
|||
return new InstancePointManager(viewport, geometry, material, maxInstances) |
|||
} |
|||
|
|||
constructor( |
|||
viewport: Viewport, |
|||
geometry: THREE.BufferGeometry, |
|||
material: THREE.Material, |
|||
maxInstances: number = 1000 |
|||
) { |
|||
this.viewport = viewport |
|||
this.geometry = geometry |
|||
this.material = material |
|||
this.maxInstanceCount = maxInstances |
|||
|
|||
const geometry = new THREE.PlaneGeometry(0.25, 0.25) |
|||
const material = new THREE.MeshBasicMaterial({ |
|||
color: 0xFFFF99, |
|||
transparent: true, |
|||
depthWrite: false, |
|||
side: THREE.DoubleSide |
|||
}) |
|||
this.instancedMesh = new THREE.InstancedMesh(geometry, material, maxInstances) |
|||
this.instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) |
|||
viewport.scene.add(this.instancedMesh) |
|||
|
|||
this.dummy.scale.set(0, 0, 0) |
|||
for (let i = 0; i < maxInstances; i++) { |
|||
this.dummy.updateMatrix() |
|||
this.instancedMesh.setMatrixAt(i, this.dummy.matrix) |
|||
this.freeIndices.push(i) |
|||
} |
|||
|
|||
this.mesh = new THREE.InstancedMesh(geometry, material, this.maxInstances) |
|||
this.mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) |
|||
viewport.scene.add(this.mesh) |
|||
this.instancedMesh.instanceMatrix.needsUpdate = true |
|||
} |
|||
|
|||
dispose() { |
|||
this.viewport.scene.remove(this.instancedMesh) |
|||
this.instancedMesh.geometry.dispose() |
|||
|
|||
if (this.instancedMesh.material) { |
|||
if (Array.isArray(this.instancedMesh.material)) { |
|||
this.instancedMesh.material.forEach(mat => mat.dispose()) |
|||
} else { |
|||
this.instancedMesh.material.dispose() |
|||
} |
|||
} |
|||
|
|||
this.instancedMesh.dispose() |
|||
this.instanceData.clear() |
|||
this.freeIndices.length = 0 |
|||
} |
|||
} |
|||
|
|||
@ -1,16 +1,269 @@ |
|||
import * as THREE from 'three' |
|||
import type IControls from '@/core/controls/IControls.ts' |
|||
import type Viewport from '@/core/engine/Viewport.ts' |
|||
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry' |
|||
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2' |
|||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' |
|||
|
|||
export default class LineSegmentManager implements IControls { |
|||
viewport: Viewport |
|||
/** |
|||
* 线段管理器 |
|||
*/ |
|||
export default class LineSegmentManager { |
|||
private readonly viewport: Viewport |
|||
private readonly lineGeometry: LineSegmentsGeometry |
|||
private readonly lineMaterial: LineMaterial |
|||
private readonly lineSegments: LineSegments2 |
|||
private readonly segments: Map<string, LineSegmentData> = new Map() |
|||
public needsUpdate: boolean = false |
|||
private colorArray: Float32Array | null = null |
|||
private positionArray: Float32Array | null = null |
|||
|
|||
init(viewport: Viewport): void { |
|||
/** |
|||
* 创建或更新线段 |
|||
* @param key 线段唯一标识 |
|||
* @param start 起点 |
|||
* @param end 终点 |
|||
* @param color 线段颜色 (可选) |
|||
* @param userData 自定义数据 (可选) |
|||
*/ |
|||
createLine(key: string, start: THREE.Vector3Like, end: THREE.Vector3Like, color?: THREE.Color | number | string, userData?: any): void { |
|||
const startVec = start instanceof THREE.Vector3 |
|||
? start.clone() |
|||
: new THREE.Vector3(start[0], start[1], start[2]) |
|||
|
|||
const endVec = end instanceof THREE.Vector3 |
|||
? end.clone() |
|||
: new THREE.Vector3(end[0], end[1], end[2]) |
|||
|
|||
let colorObj: THREE.Color | undefined |
|||
if (color instanceof THREE.Color) { |
|||
colorObj = color |
|||
} else if (typeof color === 'number') { |
|||
colorObj = new THREE.Color(color) |
|||
} else if (typeof color === 'string') { |
|||
colorObj = new THREE.Color(color) |
|||
} |
|||
|
|||
if (this.segments.has(key)) { |
|||
// 更新现有线段
|
|||
const segment = this.segments.get(key)! |
|||
segment.start.copy(startVec) |
|||
segment.end.copy(endVec) |
|||
if (colorObj) segment.color = colorObj |
|||
if (userData) segment.userData = userData |
|||
} else { |
|||
// 创建新线段
|
|||
this.segments.set(key, { |
|||
key, |
|||
start: startVec, |
|||
end: endVec, |
|||
color: colorObj, |
|||
visible: true, |
|||
userData |
|||
}) |
|||
} |
|||
|
|||
this.needsUpdate = true |
|||
} |
|||
|
|||
/** |
|||
* 更新线段位置 |
|||
* @param key 线段唯一标识 |
|||
* @param start 新起点 |
|||
* @param end 新终点 |
|||
*/ |
|||
updateLinePosition(key: string, start: THREE.Vector3Like, end: THREE.Vector3Like): void { |
|||
const segment = this.segments.get(key) |
|||
if (!segment) return |
|||
|
|||
if (start instanceof THREE.Vector3) { |
|||
segment.start.copy(start) |
|||
} else { |
|||
segment.start.set(start[0], start[1], start[2]) |
|||
} |
|||
|
|||
if (end instanceof THREE.Vector3) { |
|||
segment.end.copy(end) |
|||
} else { |
|||
segment.end.set(end[0], end[1], end[2]) |
|||
} |
|||
|
|||
this.needsUpdate = true |
|||
} |
|||
|
|||
/** |
|||
* 更新线段颜色 |
|||
* @param key 线段唯一标识 |
|||
* @param color 新颜色 |
|||
*/ |
|||
updateLineColor(key: string, color: THREE.Color | number | string): void { |
|||
const segment = this.segments.get(key) |
|||
if (!segment) return |
|||
|
|||
if (color instanceof THREE.Color) { |
|||
segment.color = color |
|||
} else if (typeof color === 'number') { |
|||
segment.color = new THREE.Color(color) |
|||
} else if (typeof color === 'string') { |
|||
segment.color = new THREE.Color(color) |
|||
} else { |
|||
segment.color = undefined |
|||
} |
|||
|
|||
this.needsUpdate = true |
|||
} |
|||
|
|||
/** |
|||
* 设置线段可见性 |
|||
* @param key 线段唯一标识 |
|||
* @param visible 是否可见 |
|||
*/ |
|||
setLineVisible(key: string, visible: boolean): void { |
|||
const segment = this.segments.get(key) |
|||
if (segment && segment.visible !== visible) { |
|||
segment.visible = visible |
|||
this.needsUpdate = true |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除线段 |
|||
* @param key 线段唯一标识 |
|||
*/ |
|||
deleteLine(key: string): void { |
|||
if (this.segments.delete(key)) { |
|||
this.needsUpdate = true |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取线段数据 |
|||
* @param key 线段唯一标识 |
|||
*/ |
|||
getLineData(key: string): LineSegmentData | undefined { |
|||
return this.segments.get(key) |
|||
} |
|||
|
|||
/** |
|||
* 更新几何体数据 (应在渲染循环中调用) |
|||
*/ |
|||
updateGeometry(): void { |
|||
if (!this.needsUpdate) return |
|||
|
|||
const segmentCount = this.segments.size |
|||
const positionCount = segmentCount * 6 // 每条线段2个点,每个点3个分量
|
|||
const colorCount = segmentCount * 6 // 每个顶点3个颜色分量
|
|||
|
|||
// 初始化或调整数组大小
|
|||
if (!this.positionArray || this.positionArray.length !== positionCount) { |
|||
this.positionArray = new Float32Array(positionCount) |
|||
} |
|||
|
|||
if (!this.colorArray || this.colorArray.length !== colorCount) { |
|||
this.colorArray = new Float32Array(colorCount) |
|||
} |
|||
|
|||
// 填充数据
|
|||
let positionIndex = 0 |
|||
let colorIndex = 0 |
|||
|
|||
const defaultColor = new THREE.Color(this.lineMaterial.color) |
|||
|
|||
this.segments.forEach(segment => { |
|||
if (!segment.visible) { |
|||
// 对于不可见的线段,跳过位置设置但保留颜色为黑色
|
|||
positionIndex += 6 |
|||
|
|||
// 设置颜色为黑色(透明)
|
|||
this.colorArray![colorIndex++] = 0 |
|||
this.colorArray![colorIndex++] = 0 |
|||
this.colorArray![colorIndex++] = 0 |
|||
|
|||
this.colorArray![colorIndex++] = 0 |
|||
this.colorArray![colorIndex++] = 0 |
|||
this.colorArray![colorIndex++] = 0 |
|||
return |
|||
} |
|||
|
|||
// 设置起点位置
|
|||
this.positionArray![positionIndex++] = segment.start.x |
|||
this.positionArray![positionIndex++] = segment.start.y |
|||
this.positionArray![positionIndex++] = segment.start.z |
|||
|
|||
// 设置终点位置
|
|||
this.positionArray![positionIndex++] = segment.end.x |
|||
this.positionArray![positionIndex++] = segment.end.y |
|||
this.positionArray![positionIndex++] = segment.end.z |
|||
|
|||
// 设置起点颜色
|
|||
const startColor = segment.color || defaultColor |
|||
this.colorArray![colorIndex++] = startColor.r |
|||
this.colorArray![colorIndex++] = startColor.g |
|||
this.colorArray![colorIndex++] = startColor.b |
|||
|
|||
// 设置终点颜色
|
|||
const endColor = segment.color || defaultColor |
|||
this.colorArray![colorIndex++] = endColor.r |
|||
this.colorArray![colorIndex++] = endColor.g |
|||
this.colorArray![colorIndex++] = endColor.b |
|||
}) |
|||
|
|||
// 更新几何体
|
|||
this.lineGeometry.setPositions(this.positionArray) |
|||
this.lineGeometry.setColors(this.colorArray) |
|||
|
|||
// 设置实例计数为可见线段数量
|
|||
const visibleCount = Array.from(this.segments.values()).filter(s => s.visible).length |
|||
this.lineGeometry.instanceCount = visibleCount |
|||
|
|||
this.needsUpdate = false |
|||
} |
|||
|
|||
/** |
|||
* 创建线段管理器实例 |
|||
*/ |
|||
public static create(viewport: Viewport, lineMaterial: LineMaterial): LineSegmentManager { |
|||
return new LineSegmentManager(viewport, lineMaterial) |
|||
} |
|||
|
|||
private constructor(viewport: Viewport, lineMaterial: LineMaterial) { |
|||
this.viewport = viewport |
|||
this.lineMaterial = lineMaterial |
|||
|
|||
this.lineGeometry = new LineSegmentsGeometry() |
|||
|
|||
// 创建线段的渲染对象
|
|||
this.lineSegments = new LineSegments2(this.lineGeometry, this.lineMaterial) |
|||
this.lineSegments.name = 'batch_line_segments' |
|||
this.lineSegments.frustumCulled = false |
|||
this.viewport.scene.add(this.lineSegments) |
|||
} |
|||
|
|||
dispose(): void { |
|||
// 清理资源
|
|||
this.viewport = undefined |
|||
dispose() { |
|||
this.viewport.scene.remove(this.lineSegments) |
|||
this.lineGeometry.dispose() |
|||
|
|||
if (this.lineMaterial) { |
|||
if (Array.isArray(this.lineMaterial)) { |
|||
this.lineMaterial.forEach(mat => mat.dispose()) |
|||
} else { |
|||
this.lineMaterial.dispose() |
|||
} |
|||
} |
|||
|
|||
this.segments.clear() |
|||
this.positionArray = null |
|||
this.colorArray = null |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 线段数据接口 |
|||
*/ |
|||
interface LineSegmentData { |
|||
key: string; |
|||
start: THREE.Vector3; |
|||
end: THREE.Vector3; |
|||
color?: THREE.Color; |
|||
visible?: boolean; |
|||
userData?: any; |
|||
} |
|||
|
|||
Loading…
Reference in new issue