ae7ff59d09
* Implemented shuffling for point batches * Points are now drawn indexed. This is needed in order to implement shuffling for point batches, to allow for best runtime performance in scenarios where the baches are split into multiple non-flat-able draw groups * Implemented draw range flattening before sending over to be set in batches. This reduces execution time significantly especially in scenarios where a lot of objects need to change material at once. Implemented batching of draw ranges in SelectionExtension when reseting a selection. This makes un-selecting significatly faster when a lot of objects are involved * Added a note
330 lines
10 KiB
TypeScript
330 lines
10 KiB
TypeScript
import {
|
|
Color,
|
|
DynamicDrawUsage,
|
|
InstancedInterleavedBuffer,
|
|
InterleavedBufferAttribute,
|
|
Line,
|
|
Material,
|
|
Object3D,
|
|
Vector4,
|
|
WebGLRenderer
|
|
} from 'three'
|
|
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js'
|
|
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js'
|
|
import { Geometry } from '../converter/Geometry'
|
|
import SpeckleLineMaterial from '../materials/SpeckleLineMaterial'
|
|
import { NodeRenderView } from '../tree/NodeRenderView'
|
|
import {
|
|
AllBatchUpdateRange,
|
|
Batch,
|
|
BatchUpdateRange,
|
|
GeometryType,
|
|
NoneBatchUpdateRange
|
|
} from './Batch'
|
|
import { ObjectLayers } from '../../IViewer'
|
|
import { DrawGroup } from './InstancedMeshBatch'
|
|
import Materials from '../materials/Materials'
|
|
|
|
export default class LineBatch implements Batch {
|
|
public id: string
|
|
public subtreeId: string
|
|
public renderViews: NodeRenderView[]
|
|
private geometry: LineSegmentsGeometry
|
|
public batchMaterial: SpeckleLineMaterial
|
|
private mesh: LineSegments2 | Line
|
|
public colorBuffer: InstancedInterleavedBuffer
|
|
private static readonly vector4Buffer: Vector4 = new Vector4()
|
|
|
|
public get bounds() {
|
|
if (!this.geometry.boundingBox) this.geometry.computeBoundingBox()
|
|
return this.geometry.boundingBox
|
|
}
|
|
|
|
public get drawCalls(): number {
|
|
return 1
|
|
}
|
|
|
|
public get minDrawCalls(): number {
|
|
return 1
|
|
}
|
|
|
|
public get triCount(): number {
|
|
return (this.geometry.index.count / 3) * this.geometry['_maxInstanceCount']
|
|
}
|
|
|
|
public get vertCount(): number {
|
|
return this.geometry.attributes.position.count * this.geometry.instanceCount
|
|
}
|
|
|
|
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
|
|
this.id = id
|
|
this.subtreeId = subtreeId
|
|
this.renderViews = renderViews
|
|
}
|
|
|
|
public get renderObject(): Object3D {
|
|
return this.mesh
|
|
}
|
|
|
|
public get geometryType(): GeometryType {
|
|
return this.renderViews[0].geometryType
|
|
}
|
|
|
|
public get materials(): Material[] {
|
|
return this.mesh.material as Material[]
|
|
}
|
|
|
|
public get groups(): DrawGroup[] {
|
|
return []
|
|
}
|
|
|
|
public getCount(): number {
|
|
return this.geometry.attributes.position.array.length / 6
|
|
}
|
|
|
|
public setBatchMaterial(material: SpeckleLineMaterial) {
|
|
this.batchMaterial = material
|
|
}
|
|
|
|
public onUpdate(deltaTime: number) {
|
|
deltaTime
|
|
}
|
|
|
|
public onRender(renderer: WebGLRenderer) {
|
|
renderer.getDrawingBufferSize(this.batchMaterial.resolution)
|
|
}
|
|
|
|
public setVisibleRange(...ranges: BatchUpdateRange[]) {
|
|
if (
|
|
ranges.length === 1 &&
|
|
ranges[0].offset === NoneBatchUpdateRange.offset &&
|
|
ranges[0].count === NoneBatchUpdateRange.count
|
|
) {
|
|
this.mesh.visible = false
|
|
return
|
|
}
|
|
|
|
if (
|
|
ranges.length === 1 &&
|
|
ranges[0].offset === AllBatchUpdateRange.offset &&
|
|
ranges[0].count === AllBatchUpdateRange.count
|
|
) {
|
|
this.mesh.visible = true
|
|
return
|
|
}
|
|
this.mesh.visible = true
|
|
const data = this.colorBuffer.array as number[]
|
|
for (let k = 0; k < data.length; k += 4) {
|
|
data[k + 3] = 0
|
|
}
|
|
for (let i = 0; i < ranges.length; i++) {
|
|
const start = ranges[i].offset * this.colorBuffer.stride
|
|
const len =
|
|
ranges[i].offset * this.colorBuffer.stride +
|
|
ranges[i].count * this.colorBuffer.stride
|
|
for (let k = start; k < len; k += 4) {
|
|
data[k + 3] = 1
|
|
}
|
|
}
|
|
|
|
this.colorBuffer.updateRange = { offset: 0, count: data.length }
|
|
this.colorBuffer.needsUpdate = true
|
|
this.geometry.attributes['instanceColorStart'].needsUpdate = true
|
|
this.geometry.attributes['instanceColorEnd'].needsUpdate = true
|
|
}
|
|
|
|
public getVisibleRange() {
|
|
return AllBatchUpdateRange
|
|
// TO DO if required
|
|
}
|
|
|
|
public getOpaque(): BatchUpdateRange {
|
|
if (Materials.isOpaque(this.batchMaterial)) return AllBatchUpdateRange
|
|
return NoneBatchUpdateRange
|
|
}
|
|
public getTransparent(): BatchUpdateRange {
|
|
if (Materials.isTransparent(this.batchMaterial)) return AllBatchUpdateRange
|
|
return NoneBatchUpdateRange
|
|
}
|
|
public getStencil(): BatchUpdateRange {
|
|
if (this.batchMaterial.stencilWrite === true) return AllBatchUpdateRange
|
|
return NoneBatchUpdateRange
|
|
}
|
|
|
|
public setBatchBuffers(...ranges: BatchUpdateRange[]): void {
|
|
const data = this.colorBuffer.array as number[]
|
|
|
|
for (let i = 0; i < ranges.length; i++) {
|
|
const material = ranges[i].material as SpeckleLineMaterial
|
|
const materialOptions = ranges[i].materialOptions
|
|
const color: Color =
|
|
materialOptions && materialOptions.rampIndexColor
|
|
? materialOptions.rampIndexColor
|
|
: material.color
|
|
const alpha: number = material.visible ? material.opacity : 0
|
|
this.batchMaterial.transparent ||= material.opacity < 1
|
|
const start = ranges[i].offset * this.colorBuffer.stride
|
|
const len =
|
|
ranges[i].offset * this.colorBuffer.stride +
|
|
ranges[i].count * this.colorBuffer.stride
|
|
|
|
LineBatch.vector4Buffer.set(color.r, color.g, color.b, alpha)
|
|
this.updateColorBuffer(
|
|
start,
|
|
ranges[i].count === Infinity ? this.colorBuffer.array.length : len,
|
|
LineBatch.vector4Buffer
|
|
)
|
|
}
|
|
this.colorBuffer.updateRange = { offset: 0, count: data.length }
|
|
this.colorBuffer.needsUpdate = true
|
|
this.geometry.attributes['instanceColorStart'].needsUpdate = true
|
|
this.geometry.attributes['instanceColorEnd'].needsUpdate = true
|
|
}
|
|
|
|
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
|
this.setBatchBuffers(...ranges)
|
|
}
|
|
|
|
public resetDrawRanges() {
|
|
this.setDrawRanges({
|
|
offset: 0,
|
|
count: Infinity,
|
|
material: this.batchMaterial
|
|
})
|
|
this.mesh.material = this.batchMaterial
|
|
this.mesh.visible = true
|
|
this.batchMaterial.transparent = false
|
|
}
|
|
|
|
public buildBatch() {
|
|
let attributeCount = 0
|
|
this.renderViews.forEach(
|
|
(val: NodeRenderView) =>
|
|
(attributeCount += val.needsSegmentConversion
|
|
? (val.renderData.geometry.attributes.POSITION.length - 3) * 2
|
|
: val.renderData.geometry.attributes.POSITION.length)
|
|
)
|
|
const position = new Float64Array(attributeCount)
|
|
let offset = 0
|
|
for (let k = 0; k < this.renderViews.length; k++) {
|
|
const geometry = this.renderViews[k].renderData.geometry
|
|
let points = null
|
|
/** We need to make sure the line geometry has a layout of :
|
|
* start(x,y,z), end(x,y,z), start(x,y,z), end(x,y,z)... etc
|
|
* Some geometries have that inherent form, some don't
|
|
*/
|
|
if (this.renderViews[k].needsSegmentConversion) {
|
|
const length = geometry.attributes.POSITION.length - 3
|
|
points = new Array(2 * length)
|
|
|
|
for (let i = 0; i < length; i += 3) {
|
|
points[2 * i] = geometry.attributes.POSITION[i]
|
|
points[2 * i + 1] = geometry.attributes.POSITION[i + 1]
|
|
points[2 * i + 2] = geometry.attributes.POSITION[i + 2]
|
|
|
|
points[2 * i + 3] = geometry.attributes.POSITION[i + 3]
|
|
points[2 * i + 4] = geometry.attributes.POSITION[i + 4]
|
|
points[2 * i + 5] = geometry.attributes.POSITION[i + 5]
|
|
}
|
|
} else {
|
|
points = geometry.attributes.POSITION
|
|
}
|
|
|
|
position.set(points, offset)
|
|
this.renderViews[k].setBatchData(this.id, offset / 6, points.length / 6)
|
|
|
|
offset += points.length
|
|
}
|
|
this.makeLineGeometry(position)
|
|
this.mesh = new LineSegments2(
|
|
this.geometry as LineSegmentsGeometry,
|
|
this.batchMaterial as SpeckleLineMaterial
|
|
)
|
|
;(this.mesh as LineSegments2).computeLineDistances()
|
|
this.mesh.scale.set(1, 1, 1)
|
|
|
|
this.mesh.uuid = this.id
|
|
this.mesh.layers.set(ObjectLayers.STREAM_CONTENT_LINE)
|
|
}
|
|
|
|
public getRenderView(index: number): NodeRenderView {
|
|
for (let k = 0; k < this.renderViews.length; k++) {
|
|
if (
|
|
index >= this.renderViews[k].batchStart &&
|
|
index < this.renderViews[k].batchEnd &&
|
|
/** A bit cheaty, but ok for now */
|
|
this.colorBuffer.array[index * this.colorBuffer.stride + 3] !== 0
|
|
) {
|
|
return this.renderViews[k]
|
|
}
|
|
}
|
|
}
|
|
|
|
public getMaterialAtIndex(index: number): Material {
|
|
index
|
|
return this.batchMaterial
|
|
}
|
|
|
|
/** TODO: I wish we wouldn't clone the material here... */
|
|
public getMaterial(rv: NodeRenderView): Material {
|
|
const start = rv.batchStart * this.colorBuffer.stride
|
|
const data = this.colorBuffer.array as number[]
|
|
const material = this.batchMaterial.clone()
|
|
material.color.setRGB(data[start], data[start + 1], data[start + 2])
|
|
return material
|
|
}
|
|
|
|
private makeLineGeometry(position: Float64Array) {
|
|
this.geometry = this.makeLineGeometryTriangle(new Float32Array(position))
|
|
Geometry.updateRTEGeometry(this.geometry, position)
|
|
}
|
|
|
|
private makeLineGeometryTriangle(position: Float32Array): LineSegmentsGeometry {
|
|
const geometry = new LineSegmentsGeometry()
|
|
/** This will set the instanceStart and instanceEnd attributes. These will be our high parts */
|
|
geometry.setPositions(position)
|
|
|
|
const buffer = new Float32Array(position.length + position.length / 3)
|
|
this.colorBuffer = new InstancedInterleavedBuffer(buffer, 8, 1) // rgba, rgba
|
|
this.colorBuffer.setUsage(DynamicDrawUsage)
|
|
this.updateColorBuffer(
|
|
0,
|
|
buffer.length,
|
|
new Vector4(
|
|
this.batchMaterial.color.r,
|
|
this.batchMaterial.color.g,
|
|
this.batchMaterial.color.b,
|
|
1
|
|
)
|
|
)
|
|
geometry.setAttribute(
|
|
'instanceColorStart',
|
|
new InterleavedBufferAttribute(this.colorBuffer, 4, 0)
|
|
) // rgb
|
|
geometry.setAttribute(
|
|
'instanceColorEnd',
|
|
new InterleavedBufferAttribute(this.colorBuffer, 4, 4)
|
|
) // rgb
|
|
geometry.computeBoundingBox()
|
|
return geometry
|
|
}
|
|
|
|
private updateColorBuffer(start: number, end: number, color: Vector4) {
|
|
const data = this.colorBuffer.array as number[]
|
|
for (let k = start; k < end; k += 4) {
|
|
data[k] = color.x
|
|
data[k + 1] = color.y
|
|
data[k + 2] = color.z
|
|
data[k + 3] = color.w
|
|
}
|
|
}
|
|
|
|
public purge() {
|
|
this.renderViews.length = 0
|
|
this.geometry.dispose()
|
|
this.batchMaterial.dispose()
|
|
this.mesh = null
|
|
this.colorBuffer.length = 0
|
|
}
|
|
}
|