Files
speckle-server/packages/viewer/src/modules/extensions/ExplodeExtension.ts
T
2026-03-02 16:11:12 +00:00

100 lines
3.1 KiB
TypeScript

import { Box3, Vector3 } from 'three'
import { Extension } from './Extension.js'
import { UpdateFlags } from '../../IViewer.js'
export enum ExplodeEvent {
Finshed = 'explode-finished'
}
export interface ViewModeEventPayload {
[ExplodeEvent.Finshed]: void
}
export class ExplodeExtension extends Extension {
protected _enabled: boolean = true
public get enabled(): boolean {
return this._enabled
}
public set enabled(value: boolean) {
this._enabled = value
}
/** Similar to SpeckleRenderer's visibleSceneBox, but with static boxes from render views */
public get visibleWorld(): Box3 {
const bounds: Box3 = new Box3()
const scratchBox = new Box3()
const batches = this.viewer.getRenderer().batcher.getBatches()
for (let k = 0; k < batches.length; k++) {
const batch = batches[k]
const rvs = batch.renderViews.slice()
rvs.sort((a, b) => {
return a.batchStart - b.batchStart
})
const visibleRange = batch.getVisibleRange()
let lo = 0,
hi = rvs.length
while (lo < hi) {
const mid = (lo + hi) >>> 1
if (rvs[mid].batchStart < visibleRange.offset) lo = mid + 1
else hi = mid
}
const qStart = visibleRange.offset
const qEnd = visibleRange.offset + visibleRange.count
for (; lo < rvs.length; lo++) {
const s = rvs[lo]
if (s.batchStart >= qEnd) break
const sEnd = s.batchStart + s.batchCount
if (s.batchStart >= qStart && sEnd <= qEnd) {
/** Alex 18.02.2026: There is an issue with using the render view's aabb.
* Instanced render views will always provide incorrect bounds unless transformed
* Which is what we are doing here */
scratchBox.copy(s.aabb)
s.renderData.geometry.transform &&
s.renderData.geometry.instanced &&
scratchBox.applyMatrix4(s.renderData.geometry.transform)
bounds.union(scratchBox)
}
}
}
return bounds
}
private explodeTime = -1
private explodeRange = 0
private explodeOrigin: Vector3 = new Vector3()
public onEarlyUpdate() {
if (!this._enabled) return
if (this.explodeTime > -1) {
this.explode(this.explodeTime, this.explodeRange)
this.explodeTime = -1
}
}
public setExplode(time: number) {
const visibleWorld = this.visibleWorld
const size = visibleWorld.getSize(new Vector3())
this.explodeTime = time
this.explodeRange = Math.sqrt(size.x * size.x + size.y * size.y + size.z * size.z)
visibleWorld.getCenter(this.explodeOrigin)
}
private explode(time: number, range: number) {
const objects = this.viewer.getRenderer().getObjects()
const vecBuff = new Vector3()
for (let i = 0; i < objects.length; i++) {
const center = objects[i].aabb.getCenter(vecBuff)
const dir = center.sub(this.explodeOrigin)
dir.normalize().multiplyScalar(time * range)
objects[i].transformTRS(dir, undefined, undefined, undefined)
}
this.viewer.requestRender(UpdateFlags.RENDER_RESET | UpdateFlags.SHADOWS)
this.emit(ExplodeEvent.Finshed)
}
}