Support for visible bounds (#5466)
* feat(viewer-lib): Added visible bounds for mesh batches * feat(viewer-lib): Single visible scene box implementation and renderer level. Updated some internals related to use the visible box instead of the whole box. ExplodeExtension and default zooming to extents now uses the visible box * Updates regarding batches and visible range management and reporting - Added stardard functions for All and None batch update ranges for visibility - Fixed an issue with mesh batch where draw ranges where not reshuffled when there was onyl a single hidden draw group - Fixed an issue with instanced mesh batch where hidden groups were not properly handled all the time - Line batch now holds a range visibility map and can now properly report if the entire batch is visible or not. Still no per object visibility reporting as it's less feasible with the current line batch rendering approach but posssible if really required * feat(viewer-lib): Text batches now also report visible ranges unitarily * chore(viewer-lib): Fixed compiler error
This commit is contained in:
committed by
GitHub
parent
87f3096892
commit
ac6d0d892e
@@ -429,7 +429,7 @@ export default class Sandbox {
|
||||
this.selectionList.map((val) => val.hits[0].node.model.raw.id) as string[]
|
||||
)
|
||||
if (!box) {
|
||||
box = this.viewer.getRenderer().sceneBox
|
||||
box = this.viewer.getRenderer().visibleSceneBox
|
||||
}
|
||||
this.viewer.getExtension(SectionTool).setBox(box)
|
||||
this.viewer.getExtension(SectionTool).toggle()
|
||||
|
||||
@@ -56,7 +56,6 @@ const createViewer = async (containerName: string, _stream: string) => {
|
||||
const boxSelect = viewer.createExtension(BoxSelection)
|
||||
boxSelect.realtimeSelection = false
|
||||
viewer.createExtension(PassReader)
|
||||
// viewer.createExtension(SectionCaps)
|
||||
|
||||
const sandbox = new Sandbox(controlsContainer, viewer, multiSelectList)
|
||||
|
||||
|
||||
@@ -188,6 +188,48 @@ export default class SpeckleRenderer {
|
||||
return bounds
|
||||
}
|
||||
|
||||
public get visibleSceneBox(): Box3 {
|
||||
const bounds: Box3 = new Box3()
|
||||
const batches = this.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
|
||||
})
|
||||
let batchObjects = null
|
||||
if (isAcceleratedBatchType(batch)) {
|
||||
batchObjects = batch.mesh.batchObjects.slice()
|
||||
batchObjects.sort((a, b) => {
|
||||
return a.renderView.batchStart - b.renderView.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]
|
||||
const b = batchObjects ? batchObjects[lo] : null
|
||||
if (s.batchStart >= qEnd) break
|
||||
const sEnd = s.batchStart + s.batchCount
|
||||
if (s.batchStart >= qStart && sEnd <= qEnd) {
|
||||
bounds.union(b ? b.aabb : s.aabb)
|
||||
}
|
||||
}
|
||||
}
|
||||
return bounds
|
||||
}
|
||||
|
||||
public get sceneSphere(): Sphere {
|
||||
return this.sceneBox.getBoundingSphere(new Sphere())
|
||||
}
|
||||
@@ -199,7 +241,7 @@ export default class SpeckleRenderer {
|
||||
public get clippingVolume(): OBB {
|
||||
return !this._clippingVolume.isEmpty() && this._renderer.localClippingEnabled
|
||||
? this._clippingVolume
|
||||
: new OBB().fromBox3(this.sceneBox)
|
||||
: new OBB().fromBox3(this.visibleSceneBox)
|
||||
}
|
||||
|
||||
public set clippingVolume(box: Box3 | OBB) {
|
||||
@@ -1215,7 +1257,7 @@ export default class SpeckleRenderer {
|
||||
rvs.push(...this.tree.getRenderTree().getRenderViewsForNode(node))
|
||||
})
|
||||
}
|
||||
} else box = this.sceneBox
|
||||
} else box = this.visibleSceneBox
|
||||
for (let k = 0; k < rvs.length; k++) {
|
||||
const object = this.getObject(rvs[k])
|
||||
const aabb = object ? object.aabb : rvs[k].aabb
|
||||
|
||||
@@ -91,3 +91,18 @@ export function isAcceleratedBatchType(batch: Batch): batch is AcceleratedBatchT
|
||||
batch.geometryType === GeometryType.TEXT)
|
||||
)
|
||||
}
|
||||
|
||||
export function isNoneBatchUpdateRange(range: BatchUpdateRange) {
|
||||
return (
|
||||
range.offset === NoneBatchUpdateRange.offset &&
|
||||
range.count === NoneBatchUpdateRange.count
|
||||
)
|
||||
}
|
||||
|
||||
export function isAllBatchUpdateRange(range: BatchUpdateRange, totalCount?: number) {
|
||||
return (
|
||||
range.offset === AllBatchUpdateRange.offset &&
|
||||
(range.count === AllBatchUpdateRange.count ||
|
||||
(totalCount ? range.count === totalCount : true))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type DrawGroup,
|
||||
GeometryType,
|
||||
INSTANCE_TRANSFORM_BUFFER_STRIDE,
|
||||
isNoneBatchUpdateRange,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch.js'
|
||||
import SpeckleInstancedMesh from '../objects/SpeckleInstancedMesh.js'
|
||||
@@ -139,7 +140,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
/** Note: You can only set visibility on ranges that exist as draw groups! */
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
/** Entire batch needs to NOT be drawn */
|
||||
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
|
||||
if (ranges.length === 1 && isNoneBatchUpdateRange(ranges[0])) {
|
||||
this.mesh.children.forEach((instance) => (instance.visible = false))
|
||||
return
|
||||
}
|
||||
@@ -324,9 +325,32 @@ export class InstancedMeshBatch implements Batch {
|
||||
}
|
||||
this.setBatchBuffers(ranges)
|
||||
this.cleanMaterials()
|
||||
const transparentDepthHiddenGroup = this.groups.find(
|
||||
(value) =>
|
||||
this.materials[value.materialIndex].transparent === true ||
|
||||
this.materials[value.materialIndex].visible === false ||
|
||||
this.materials[value.materialIndex].colorWrite === false
|
||||
)
|
||||
/** We shuffle only when above a certain fragmentation threshold. We don't want to be shuffling every single time */
|
||||
if (this.drawCalls > this.maxDrawCalls) {
|
||||
this.needsShuffle = true
|
||||
} else if (transparentDepthHiddenGroup) {
|
||||
if (this.groups.length === 1) this.needsShuffle = true
|
||||
else {
|
||||
for (
|
||||
let k = this.groups.indexOf(transparentDepthHiddenGroup);
|
||||
k < this.groups.length;
|
||||
k++
|
||||
) {
|
||||
const material = this.materials[this.groups[k].materialIndex]
|
||||
if (material.visible) {
|
||||
if (!material.transparent || material.colorWrite) {
|
||||
this.needsShuffle = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
this.mesh.updateDrawGroups(
|
||||
this.getCurrentTransformBuffer(),
|
||||
|
||||
@@ -18,14 +18,18 @@ import {
|
||||
AllBatchUpdateRange,
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
type DrawGroup,
|
||||
DrawGroup,
|
||||
GeometryType,
|
||||
isAllBatchUpdateRange,
|
||||
isNoneBatchUpdateRange,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch.js'
|
||||
import { ObjectLayers } from '../../IViewer.js'
|
||||
import Materials from '../materials/Materials.js'
|
||||
import { ChunkArray } from '../converter/VirtualArray.js'
|
||||
|
||||
const vec4Buffer = new Vector4()
|
||||
|
||||
export default class LineBatch implements Batch {
|
||||
public id: string
|
||||
public subtreeId: string
|
||||
@@ -37,7 +41,7 @@ export default class LineBatch implements Batch {
|
||||
protected mesh: LineSegments2
|
||||
public colorBuffer: InstancedInterleavedBuffer
|
||||
|
||||
private static readonly vector4Buffer: Vector4 = new Vector4()
|
||||
protected visibilityRanges: { [offset: number]: boolean } = {}
|
||||
|
||||
public get bounds(): Box3 {
|
||||
if (!this.geometry.boundingBox) this.geometry.computeBoundingBox()
|
||||
@@ -65,6 +69,11 @@ export default class LineBatch implements Batch {
|
||||
this.subtreeId = subtreeId
|
||||
this.renderViews = renderViews
|
||||
}
|
||||
|
||||
get groups(): DrawGroup[] {
|
||||
return []
|
||||
}
|
||||
|
||||
public get pointCount(): number {
|
||||
return 0
|
||||
}
|
||||
@@ -88,10 +97,6 @@ export default class LineBatch implements Batch {
|
||||
return this.mesh.material as unknown as Material[]
|
||||
}
|
||||
|
||||
public get groups(): DrawGroup[] {
|
||||
return []
|
||||
}
|
||||
|
||||
public getCount(): number {
|
||||
return this.geometry.attributes.position.array.length / 6
|
||||
}
|
||||
@@ -112,23 +117,16 @@ export default class LineBatch implements Batch {
|
||||
}
|
||||
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
if (
|
||||
ranges.length === 1 &&
|
||||
ranges[0].offset === NoneBatchUpdateRange.offset &&
|
||||
ranges[0].count === NoneBatchUpdateRange.count
|
||||
) {
|
||||
if (ranges.length === 1 && isNoneBatchUpdateRange(ranges[0])) {
|
||||
this.mesh.visible = false
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
ranges.length === 1 &&
|
||||
ranges[0].offset === AllBatchUpdateRange.offset &&
|
||||
ranges[0].count === AllBatchUpdateRange.count
|
||||
) {
|
||||
if (ranges.length === 1 && isAllBatchUpdateRange(ranges[0], this.getCount())) {
|
||||
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) {
|
||||
@@ -150,9 +148,11 @@ export default class LineBatch implements Batch {
|
||||
this.geometry.attributes['instanceColorEnd'].needsUpdate = true
|
||||
}
|
||||
|
||||
/** Line batches do not sort their ranges. This means we can have hidden/transparent objects anywhere inside the batch.
|
||||
*/
|
||||
public getVisibleRange() {
|
||||
if (!this.mesh.visible) return NoneBatchUpdateRange
|
||||
return AllBatchUpdateRange
|
||||
// TO DO if required
|
||||
}
|
||||
|
||||
public getOpaque(): BatchUpdateRange {
|
||||
@@ -199,17 +199,26 @@ export default class LineBatch implements Batch {
|
||||
ranges[i].offset * this.colorBuffer.stride +
|
||||
ranges[i].count * this.colorBuffer.stride
|
||||
|
||||
LineBatch.vector4Buffer.set(color.r, color.g, color.b, alpha)
|
||||
vec4Buffer.set(color.r, color.g, color.b, alpha)
|
||||
this.updateColorBuffer(
|
||||
start,
|
||||
ranges[i].count === Infinity ? this.colorBuffer.array.length : len,
|
||||
LineBatch.vector4Buffer
|
||||
vec4Buffer
|
||||
)
|
||||
this.visibilityRanges[ranges[i].offset] = material.visible
|
||||
}
|
||||
this.colorBuffer.updateRange = { offset: 0, count: data.length }
|
||||
this.colorBuffer.needsUpdate = true
|
||||
this.geometry.attributes['instanceColorStart'].needsUpdate = true
|
||||
this.geometry.attributes['instanceColorEnd'].needsUpdate = true
|
||||
|
||||
const visibility = Object.values(this.visibilityRanges)
|
||||
let anyVisible = false
|
||||
for (let k = 0; k < visibility.length; k++) {
|
||||
anyVisible ||= visibility[k]
|
||||
}
|
||||
if (anyVisible) this.setVisibleRange([AllBatchUpdateRange])
|
||||
else this.setVisibleRange([NoneBatchUpdateRange])
|
||||
}
|
||||
|
||||
public setDrawRanges(ranges: BatchUpdateRange[]) {
|
||||
@@ -220,14 +229,16 @@ export default class LineBatch implements Batch {
|
||||
this.setDrawRanges([
|
||||
{
|
||||
offset: 0,
|
||||
count: Infinity,
|
||||
count: this.getCount(),
|
||||
material: this.batchMaterial
|
||||
}
|
||||
])
|
||||
|
||||
this.mesh.material = this.batchMaterial
|
||||
this.mesh.visible = true
|
||||
this.batchMaterial.transparent = this.batchTransparent
|
||||
this.batchMaterial.opacity = this.batchOpacity
|
||||
this.visibilityRanges = { 0: this.batchMaterial.visible }
|
||||
}
|
||||
|
||||
public buildBatch() {
|
||||
@@ -304,6 +315,9 @@ export default class LineBatch implements Batch {
|
||||
|
||||
this.mesh.uuid = this.id
|
||||
this.mesh.layers.set(ObjectLayers.STREAM_CONTENT_LINE)
|
||||
|
||||
this.visibilityRanges = { 0: this.batchMaterial.visible }
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
|
||||
@@ -168,16 +168,19 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
)
|
||||
|
||||
if (transparentDepthHiddenGroup) {
|
||||
for (
|
||||
let k = this.groups.indexOf(transparentDepthHiddenGroup);
|
||||
k < this.groups.length;
|
||||
k++
|
||||
) {
|
||||
const material = this.materials[this.groups[k].materialIndex]
|
||||
if (material.visible) {
|
||||
if (!material.transparent || material.colorWrite) {
|
||||
this.needsShuffle = true
|
||||
break
|
||||
if (this.groups.length === 1) this.needsShuffle = true
|
||||
else {
|
||||
for (
|
||||
let k = this.groups.indexOf(transparentDepthHiddenGroup);
|
||||
k < this.groups.length;
|
||||
k++
|
||||
) {
|
||||
const material = this.materials[this.groups[k].materialIndex]
|
||||
if (material.visible) {
|
||||
if (!material.transparent || material.colorWrite) {
|
||||
this.needsShuffle = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
GeometryType,
|
||||
isNoneBatchUpdateRange,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch.js'
|
||||
import { type DrawGroup } from './Batch.js'
|
||||
@@ -85,7 +86,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
/** Entire batch needs to NOT be drawn */
|
||||
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
|
||||
if (ranges.length === 1 && isNoneBatchUpdateRange(ranges[0])) {
|
||||
this.primitive.geometry.setDrawRange(0, 0)
|
||||
/** We unset the 'visible' flag, otherwise three.js will still run pointless buffer binding commands*/
|
||||
this.primitive.visible = false
|
||||
|
||||
@@ -112,10 +112,19 @@ export default class TextBatch implements Batch {
|
||||
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
ranges
|
||||
// TO DO
|
||||
}
|
||||
|
||||
/* I hate how brittle Troika is. **Everything** you touch breaks shit
|
||||
* We can't actually use the 'visible' property inherited from Mesh, because it just breaks the text batch
|
||||
*/
|
||||
public getVisibleRange(): BatchUpdateRange {
|
||||
if (this.mesh.groups.length === 1) {
|
||||
const group = this.mesh.groups[0]
|
||||
if (!this.materials[group.materialIndex].visible) {
|
||||
return NoneBatchUpdateRange
|
||||
}
|
||||
}
|
||||
|
||||
return AllBatchUpdateRange
|
||||
}
|
||||
|
||||
@@ -144,7 +153,7 @@ export default class TextBatch implements Batch {
|
||||
|
||||
/** Text batches are mix between how mesh and line batches work.
|
||||
* - They still keep track of various draw groups each with it's material
|
||||
* - However that material is not really being used, bur rather the properies are copied over to the batch fp32 data texture
|
||||
* - However that material is not really being used, but rather the properies are copied over to the batch fp32 data texture
|
||||
* - For filtering we cheat and use `SpeckleTextColoredMaterial` only to store the gradient/ramp texture + gradient indices for each text in the batch
|
||||
* - The color from the gradient/ramp texture will be used only if the gradient index > 0, otherwise the regular color will be used
|
||||
* - The gradient index is stored in each text object in it's `userData` and written to the 27'th float in the batch data texture, where the shader reads if from
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Vector3 } from 'three'
|
||||
import { Box3, Vector3 } from 'three'
|
||||
import { Extension } from './Extension.js'
|
||||
import { UpdateFlags } from '../../IViewer.js'
|
||||
|
||||
@@ -20,8 +20,44 @@ export class ExplodeExtension extends Extension {
|
||||
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 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) {
|
||||
bounds.union(s.aabb)
|
||||
}
|
||||
}
|
||||
}
|
||||
return bounds
|
||||
}
|
||||
|
||||
private explodeTime = -1
|
||||
private explodeRange = 0
|
||||
private explodeOrigin: Vector3 = new Vector3()
|
||||
|
||||
public onEarlyUpdate() {
|
||||
if (!this._enabled) return
|
||||
@@ -32,9 +68,11 @@ export class ExplodeExtension extends Extension {
|
||||
}
|
||||
}
|
||||
public setExplode(time: number) {
|
||||
const size = this.viewer.World.worldSize
|
||||
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) {
|
||||
@@ -42,7 +80,7 @@ export class ExplodeExtension extends Extension {
|
||||
const vecBuff = new Vector3()
|
||||
for (let i = 0; i < objects.length; i++) {
|
||||
const center = objects[i].aabb.getCenter(vecBuff)
|
||||
const dir = center.sub(this.viewer.World.worldOrigin)
|
||||
const dir = center.sub(this.explodeOrigin)
|
||||
dir.normalize().multiplyScalar(time * range)
|
||||
|
||||
objects[i].transformTRS(dir, undefined, undefined, undefined)
|
||||
|
||||
Reference in New Issue
Block a user