Viewer API Improvements (#2072)
* Fix some monstrous bugs with index buffer shuffling now that we've changed our approach a bit.~ * Finished with the new material management approach for mesh batches. SelectionExtension now uses this approach and also considers existing material opacities when setting select and hover materials * Updated LineBatch to work with the new material management approach * Implemented the required draw range related changes to the point batch * Text batche now work with the new material management * SpeckleLineMaterial and SpecklePointsMaterial are now SpeckleMaterial as well. Had to rename two properties of SpeckleMaterial due to some typescript named property clash thing, but nothing really changed * Removed eslint-disable clauses in materials where they were no longer needed. Removed unused imports and overrides * Added the RTE define for some materials by default. It can still be overriden if users want to * Stencil outlines is now an toggle-able option for any SpeckleMaterial. Restricted to meshes * Implemented setting desired material for all geometry types via RenderMaterial and DisplayStyle data. SpeckleRenderer now has three overloaded setMaterial function. One for a material instance, one for a filer material and one for RenderData&DisplayStyle. Moved material hash related functionality from NodeRendeView into Materials * Added MaterialOptions which can be used when setting materials based on RenderMaterial/DisplayStyle to toggle various material features like stencilOutlines, pointSize. SelectionExtension now uses data material to set materials, and things are so much more simpler and nicer * Added public method for setting seletion extension options * After some profiling, realized three.js was doing a lot of pointless work each frame so now we're caching materials created from RenderMaterial/DisplayStyle to avoid this. Perf is nice and sharp now * addRenderTreeAsync is now a generator. Handled automatic zooming on viewer loading. Disabled section tool by default * Centralized RTE and shadow RTE buffer in an extended webglrenderer. This avoids re-computing rte data for each material over and over. Also, rte data is now centralized and available to materials * SpeckleMeshes now use a cached material clone as their batch material like any other material they use * Cleaned up Materils. Updated the debug show batches function use the new material manipulation system. So much easier now * Real time measurement exist now as a separate extension. Existing functionality preserved, besides one or two small additions. Renamed the MEASUREMENTS object layer to OVERLAY for a more generic usage. SelectionExtension can now be enabled/disabled. Added an additional overload to the setCameraView method in ICameraController which takes a box3 as target to focus the camera on * Removed viewer related events from input and replaced them everywhere with the proper input event types where required * Fixed two issues with the shadowcatcher. One was a regression introduced after we centralized the RTE data. The other was a super old one one and was essentially causing the shadowcatcher to generate the contact shadows incorrectly because the correct transform textures were not bound (this is three.js being a pain) * WIP on the filtering extension * FilteringExtension is done. Kept the same implementation * Added filter reset function * Removed uniform texture and batch count binding from each material's OnBeforeRender function. Additionallit our material override function from SpeckleMesh now uses our fast copy instead of three's copy function for materials. This decreased CPU overhead each frame by 20+% and also eliminated the ugly call to SpeckleMesh's function for updating the material with the transform texture and batch object count from each material's render callback which was not supposed to be there * Update RenderingStats to measure CPU render time per frame. * Added early and late update functions for extensions. First gets called before the core's update, and the latter afterwards * All speckle materials now use the centralized RTE data. Updated the fasts copy method to copy userdata defined uniforms where needed * Added explosion extension. Additional cleanup * Dirty transforms are marked on a per batch object basis, whenever their transforms gets changed. This automates the transform texture update execution, so we no longer need to manually mark entire batches as dirty * Added getObjects which returns all batch objects in the scene and getObjects which takes an rv an returns only the batch object associated with that rv. Both of these methods are availale on the renderer * Added setters for position, rotiation and scale in batch object * Diff extension is complete. We stuck with creating material instances and using those for coloring since it ensures maximum draw call efficiency. Fixed an issue in MeshBatch where transparent draw groups were not always sorted at the end of the group list leading to incorect opauque object selection during the depth pass * RV batch materials are obtained via the viewer-core API based on the RV itslef. This removed the need to get all batch materials for the differ * Small cleanup * Removed all circular dependencies besides one, like I predicted. The final one will dissapaear on it's own in the near future when we'll gracefull make DataTree obsolete. As a note, the circular dependencies were very shallow, reffering to enums/interfaces/statics declared in specific files. There was no real circular dependency on a class level * Removed last circular dependency just for the sake of completion * SpeckleRendere now has a clipping volume which is used internally to reject picks outside of it, and it's also exposed to the outside world to be used however * Implemented the SpeckleLoader along with it's abstract supertype. Data loading is now done through this loader which handles tree population with raw data as well as render view data. * Working minimal obj loader * Added total node counting and displaying * Viewer's load object now takes a loader of any speckle loader base class and uses that to load, instead of taking urls and tokens. This allows for any kind of loader to work with the vieweer's load function. Moved indexing of obj geometry to the obj geometry convertor. Loaders now take the target world tree instance instead of a viewer instance * Loaders can now load from string and array buffer data where implemented. ObjLoader can now load from a string payload. Sandbox can now load obj files from the UI using a file picker * alex/API2.0-core * Started on #1673. Fixed an issue with walkAsync where the recursive generator would waste too much time idle. * Solution for #1673. Replaced the old async pausing approach with a better version that has true variable wait time, and does not add additional dead wait time. Render tree building is now several times faster * Cut down some more on load time by using a lookup table for determined speckle types. For a very large number of objects, getting the actual speckle type was quite slow. For our reference stream with 1kk objects we cut down around 2 seconds of load time * BoxFromObjects now returns the correctly transformed aabb * Implemented optimisations for batch building step from the loading process. Reduced the step's time by around 50% * Implemented a NodeMap which allows us to search for nodes very very fast. * Added a dynamic pause in the loader which stops the converter from blocking. Paramater object types are not added as nodes anymore. Callback from converter is not without arguments * Replaced some internal walks with the newer and much faster id finding approach * Disable object shallow cloning acrss the speckle converter * Fixed the issues with block instances and revit instances caused by not duplicating speckle objects in the converter * getObjectPropertis is now async and slightly improved the execution time of the flatten function which it uses * Set caching to true by default * Fix for display style hashing * Implemented legacy viewer as a wrapper around the old viewer API * Started testing FE1 with API 2.0. Fixed some legacy issues. Also the camera controller extension now exposes it's underlying controls object for the sake of not messing around with unwatend changes in FE1 * Updated FE1 with API2.0 selection changes * Fixing selection bugs * The viewer now ignores duplicate id nodes * Fixes to object properties population, camera zooming and adding subtrees in core * Fixed some filtering issues. Added UpdateFlags to the viewer's requestRender function * Fixed an issue where section boxes were incorrectly added to the URL * Objects with no id are not given nodes into the tree * Updates to FE2 for API2.0. Also fixed a really really obscure bug in viewer-core where a material an incorrect material was set when reseting the batch to the default material * We now store separate node maps for each model loaded. Each node now holds it's subtree id (as a small number for memory considerations) * Render request after updating the visual diff * Fixed some missing update calls on shadows * Reverted FE1 changes and pinned the viewer library to a specific (latest) version pre-API2.0 * We're adding a viewer node for loaded models as subtree parent. This is because we're no longer spoofing ids and the model parent id needs to be preserved * Fixed an issue where clicking on a comment bubble made the pipeline use accumulation improperly, leading to dark blight * Null check for setMaterial. Fixed another case of dark blight. Hack required by frontend * Fixed the issue with filtering state not propagating in the FE * Updated selection event changes * Fixed an issue where an undefined subtree id would yield an incorrect render tree upon requesting it * Fixed an issue where undefined nodes were returned as valid when searching of ids * NodeRenderView now holds it's subtree id along the speckle id. This allows for precise node matching when looking for specific nodes * Fixed an issue with BlockInstances not instancing underlying meshes from breps * Some fixes to diffing. Some older than API2.0, some new. Render views now have guid composed of their id and subtreeid. * Update node render view id with guid where needed * Unload function now checks for requested resource to unload before trying to unload it * Check for the existence of a batch before applying draw ranges. Inthe FE, extension can temporarily keep dead rvs in their state when switching between streams leading to errors * Fixed an issue for filtering where subtree roots would cause incorrect rv additions to the ghosted rv lists * Unified block instance and revit instance conversion implementation * Separted instanced from non-instanced rvs. Working on InstancedMeshBatch * WIP on instanced types * Implemented visual for box draging * BoxSelection extension beautified and documented along with some small but welcomed changes to the viewer-core * Viewer's getExtension now looks in the prototype chain before returning an undefined extension * Added the extended Selection extension here for possible later use * First iteration on instancing. General idea works. Still WIP on several fronts * Fixed issues related to incorrect transform being calculated for instanced geometry. Fixed an issue with incorrect bounds being calculated * Fixed a few issues with instanced vs non instanced render view gathering * Disabled box selection extension * Fixed some linter errors * Disabled a lint 'error' * Fixed issues with depth rendering and instanced objects and fixed draw range visibility for instanced batches in a minimalistic way * Updated the measurements extension with the visiblity option * Restructed a bit our implementation for the acceleration structure because now the BLAS needs to aggregate an instance of three-mesh-bvh not extend it. So that instanced batch objects can share a single bvh instance -> no redundancy * WIP * Revert "WIP" This reverts commit 20d4bbf6210b0d37b956cc5b41fdb06f29845b4f. * More WIP on trying to make instanced geometry TAS and BAS work properly * Both Tas and Bas intersection testing seem to working fine. Now we need to implemented draw grouping for instanced mesh batches * Added draw group management to InstancedMeshBatch. It works in the same manner as for non instanced batches, but the offset value refers to instances not triangles. I believe it could be simplified, but I'd like to get it up and working first * Draw groups need to hold the index and count from the instanced buffer attribute * Added array shuffling to the instanced mesh batch using the same approach we used for the non instanced one. * Instanced batches now dynamically add InstancedMesh objects based on draw groups and a computed transform buffer * Applying draw range updates for instanced batches now works. Still some issues to handle. It aint much, but it's honest work * Disabled some more RTE until setting visibility for draw ranges is finished * Moved getting opaque, transparent and stencil draw ranges out from Batcher and on to a per-batch basis. Now instanced batches correctly apply draw ranges and get their opaque, transparent and stecil ranges * Fixed an issue with setting the visibility ranges for instanced mesh batches * Instanced attributes for instanced meshes now no longer allocate * Shadow depth material for instances is now set in the speckle renderer * R-enabled RTE globally. Made instancing work with RTE. Made instancing work with both RTE and shadowmapping. * Fixed an issue with materials building up in the mesh batch's cache incorrectly * Implemented gradient indexs attributes for instanced batches. Thismeans, any color ramp based material like the ones we use for filtering now works * Changed the way compound ids are created for instances, so that less memory is required. i.e chrome is not crashing anymore on particular streams * Implemented reseting the draw ranges for instanced mesh batches. We no longer double buffer the gradient index buffer. We just create a new one when shuffling, populate it, then copy it over at the end. We're still double buffering the transforms buffer since that one is larger and we might not want to allocate it each time we shuffle * Removed references of draw groups with ids since we're not doing that anymore. Fixed an issue with mesh batches where the material cache would keep piling materials incorrectly * Several issues with selective transformations on instanced batches fixed. * Added default null materials for instanced meshes both with and without vertex colors * Got rid of the patched InstancedMesh because it was ridiculously slow. Instead we're now computing the scene box using our acceleration structures where available and three.s boundingbox where not available * Minor, yet big regression fix * Fixed regression * Exported some extra types * WIP on instanced balancing * Instanced objects under a certain threshold now get batched together as regular mesh batches * Forgot to update the rvs aabb * Unified instanced and non instanced batch creation. Instances which do not qualify for instanced batching anre now mixed together with the rest of the non instanced batched objects * Added some timing information to instanced batches * Fixed an issue with zooming in on objects not working in the selection extension. Fixed an issue with object picking failing due to fp precision for objects right at the edge of the clipping volume. * Removed logs * Update stream moving via UI * Removed the priority argument from loadObject since it's not needed anymore * Updated LegacyViewer * Fixed an issue with API 2.0 where the legacy form of transforms with only an array of values as the matrix would not work * Updated FE1 viewer package to latest before API 2.0 * Disabled selection when measurement mode is on * Updated ibl params updating * Fixed an issue with measurement text not showing up * Logs * lockfile * Made DataTree obsolete. Removed unused 'input' property from viewer * Fixed circular dependencies * Removed DebugViewer, other small changes * Small changes * Small fixes * Added id to NodeData * Removed unused bounds property in rendertree * Notes to future self + ExtendedExtension is now exported * Removed an unused function from renderer. Removed the parent argumetn from getRenderViewerForNode... since it was legacy and has no meaning anymore. * Removed the instance type check in getRenderViewesForNode method. Should not be needed anymore and it was bad to begin with anyway * Removed some test code * Small changes * Removed pointless bounds calculation. Added note * Added return types * Ingestigating large group operations * Nested nodes from TreeNode are now optional. Small update to node render view. * #1818 Remove the concept of speckle data existing behind a 'data' field * Removed unneeded property * made arguments options in transformTRS * getBoundingBox from acceleration structures have an optional argument now * Several Batch methods no longer take variable numbe of arguments, but arrays now * Added note * Note * Removed unused things * Note and made the raycaster protected in intersections * Added some typings * Replaceds some functions with accessors. Added typings. Renamed stuff * Removed some unused properties. * Typings for measurements * Small typing changes * Fixed some more compile errors * Sources from 'batching' folder are now strict compile compliant * Sources from 'materials' folder are now strict compile compliant * Sources from 'extensions' folder are now strict compile compliant * Sources from 'tree' folder are now strict compile compliant * Viewer interface, implementation, legacy implementation and the exports now comply with the strict compilation flag. Also added the new tsconfig * Sources from 'objects' folder now comply to the strict compilation rules * Sources from 'pipeline' now satisfy the requirements for strict compilation * Sources from 'loaders' folder are now compiling with strict. That was so much fun * Sources from 'query' folder are now compiling with strict * Another round of correction triggered by previous changes * Update the declaration file in the object loader to contain a member that needs to be public * SpeckleRenderer along with the rest of the surces from the root 'modules' folder are not compiled with strict * Completely deprecated DataTree. Updated the dependencies with @types/underscore. Fixed remaining compilation issues * Fixed a failing build on the CI regarding a timeout id * Fixed compile errors from sandbox * Another fix * All EventEmiter child classes now have mapped event handler argument types, so that when attaching to a specific event, the provided handler has the correct types for it's arguments. Implicitly, got rid of the the unknown types in all event handlers. * Disabled verbatimModuleSyntax because it was messing things up. Fixed an issue with an improper import * Fixed frontend-2 linting errors. Also added all the event payload maps to the viewer export * Some good additions but mostly typescript catering * Some more typescript catering, but also something useful. The intersect function is now overloaded so that when you specify only the ObjectLayers.STREAM_CONTENT_MESH object layer in it's layers argument it will always return a ExtendedMeshIntersection which guarantees to have a batchObject, face and it's object is of type SpeckleMesh | SpeckleInstancedMesh. Generally you mostly raycast against meshes and getting a three.js intersection object which has all it's fileds optional led to some very useless defined checks. With this we can avoid all of them * Continued from yesterday, finished with the changes in intersections. Added MeshIntersection which is returned by all bvh intersections. This eliminates the need to check for face, faceIndex or index on intersection results from intersection meshes. Groups from MeshBatch and InstancedMeshBatch are now always DrawRanges(they always were) * Mostly catering to typescript * Removed underscore and all unused dependancies. Fixed remaining lint and build errors * Minor changes * Added the no-non-null assertion rule to the sandbox * getExtension never returns null, but rather throws an exception if requested extension does not exist. Added hasExtension for (theoretical) situations where you want to check for extension existence but don't want to go by try/catch with getExtension. Fixed remaining lint/build errors * getBatches now returns explicit batch types based on the geometry type that you provide * Adding the lockfile * undoing unnecessary FE2 changes * Merged viewer/fe SpeckleObject types * Fixed two small issues * Minor linting issues --------- Co-authored-by: Kristaps Fabians Geikins <fabians@speckle.systems>
This commit is contained in:
committed by
GitHub
parent
3c91af0a28
commit
ec78d316bb
@@ -1,5 +1,5 @@
|
||||
import { Box3, Material, Object3D, WebGLRenderer } from 'three'
|
||||
import { FilterMaterialOptions } from '../materials/Materials'
|
||||
import { type FilterMaterialOptions } from '../materials/Materials'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
|
||||
export enum GeometryType {
|
||||
@@ -10,6 +10,12 @@ export enum GeometryType {
|
||||
TEXT
|
||||
}
|
||||
|
||||
export interface DrawGroup {
|
||||
start: number
|
||||
count: number
|
||||
materialIndex: number
|
||||
}
|
||||
|
||||
/** TO DO: Unify point and mesh batch implementations */
|
||||
export interface Batch {
|
||||
id: string
|
||||
@@ -31,22 +37,22 @@ export interface Batch {
|
||||
|
||||
getCount(): number
|
||||
setBatchMaterial(material: Material): void
|
||||
setBatchBuffers(...range: BatchUpdateRange[]): void
|
||||
setVisibleRange(...range: BatchUpdateRange[])
|
||||
setBatchBuffers(range: BatchUpdateRange[]): void
|
||||
setVisibleRange(range: BatchUpdateRange[]): void
|
||||
getVisibleRange(): BatchUpdateRange
|
||||
setDrawRanges(...ranges: BatchUpdateRange[])
|
||||
resetDrawRanges()
|
||||
buildBatch()
|
||||
getRenderView(index: number): NodeRenderView
|
||||
getMaterialAtIndex(index: number): Material
|
||||
getMaterial(rv: NodeRenderView): Material
|
||||
setDrawRanges(ranges: BatchUpdateRange[]): void
|
||||
resetDrawRanges(): void
|
||||
buildBatch(): void
|
||||
getRenderView(index: number): NodeRenderView | null
|
||||
getMaterialAtIndex(index: number): Material | null
|
||||
getMaterial(rv: NodeRenderView): Material | null
|
||||
getOpaque(): BatchUpdateRange
|
||||
getTransparent(): BatchUpdateRange
|
||||
getStencil(): BatchUpdateRange
|
||||
getDepth(): BatchUpdateRange
|
||||
onUpdate(deltaTime: number)
|
||||
onRender?(renderer: WebGLRenderer)
|
||||
purge()
|
||||
onUpdate(deltaTime?: number): void
|
||||
onRender?(renderer: WebGLRenderer): void
|
||||
purge(): void
|
||||
}
|
||||
|
||||
export interface BatchUpdateRange {
|
||||
@@ -65,16 +71,6 @@ export const AllBatchUpdateRange = {
|
||||
offset: 0,
|
||||
count: Infinity
|
||||
} as BatchUpdateRange
|
||||
export interface DrawGroup {
|
||||
start: number
|
||||
count: number
|
||||
materialIndex?: number
|
||||
}
|
||||
export interface DrawGroup {
|
||||
start: number
|
||||
count: number
|
||||
materialIndex?: number
|
||||
}
|
||||
|
||||
export const INSTANCE_TRANSFORM_BUFFER_STRIDE = 16
|
||||
export const INSTANCE_GRADIENT_BUFFER_STRIDE = 1
|
||||
|
||||
@@ -12,6 +12,8 @@ export type VectorLike =
|
||||
| { x: number; y: number; z?: number; w?: number }
|
||||
| undefined
|
||||
| null
|
||||
export type Vector3Like = VectorLike & { z: number }
|
||||
export type Vector4Like = Vector3Like & { w: number }
|
||||
|
||||
export class BatchObject {
|
||||
protected _renderView: NodeRenderView
|
||||
@@ -21,8 +23,8 @@ export class BatchObject {
|
||||
public transform: Matrix4
|
||||
public transformInv: Matrix4
|
||||
|
||||
public tasVertIndexStart: number
|
||||
public tasVertIndexEnd: number
|
||||
public tasVertIndexStart!: number
|
||||
public tasVertIndexEnd!: number
|
||||
|
||||
public quaternion: Quaternion = new Quaternion()
|
||||
public eulerValue: Euler = new Euler()
|
||||
@@ -53,14 +55,13 @@ export class BatchObject {
|
||||
return this._batchIndex
|
||||
}
|
||||
|
||||
public get speckleId(): string {
|
||||
return this._renderView.renderData.id
|
||||
}
|
||||
|
||||
public get aabb(): Box3 {
|
||||
const box = new Box3().copy(this.renderView.aabb)
|
||||
box.applyMatrix4(this.transform)
|
||||
return box
|
||||
if (this.renderView.aabb) {
|
||||
const box = new Box3().copy(this.renderView.aabb)
|
||||
box.applyMatrix4(this.transform)
|
||||
return box
|
||||
}
|
||||
return new Box3()
|
||||
}
|
||||
|
||||
public get localOrigin(): Vector3 {
|
||||
@@ -117,11 +118,13 @@ export class BatchObject {
|
||||
transform.invert()
|
||||
|
||||
if (!bvh) {
|
||||
const indices = this._renderView.renderData.geometry.attributes.INDEX
|
||||
const position = this._renderView.renderData.geometry.attributes.POSITION
|
||||
const indices: number[] | undefined =
|
||||
this._renderView.renderData.geometry.attributes?.INDEX
|
||||
const position: number[] | undefined =
|
||||
this._renderView.renderData.geometry.attributes?.POSITION
|
||||
bvh = AccelerationStructure.buildBVH(
|
||||
indices,
|
||||
new Float32Array(position),
|
||||
position,
|
||||
DefaultBVHOptions,
|
||||
transform
|
||||
)
|
||||
@@ -137,10 +140,10 @@ export class BatchObject {
|
||||
}
|
||||
|
||||
public transformTRS(
|
||||
translation: VectorLike,
|
||||
euler: VectorLike,
|
||||
scale: VectorLike,
|
||||
pivot: VectorLike
|
||||
translation: Vector3Like,
|
||||
euler?: Vector3Like,
|
||||
scale?: Vector3Like,
|
||||
pivot?: Vector3Like
|
||||
) {
|
||||
let T: Matrix4 = BatchObject.matBuff0.identity()
|
||||
let R: Matrix4 = BatchObject.matBuff1.identity()
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { MathUtils } from 'three'
|
||||
import LineBatch from './LineBatch'
|
||||
import Materials, { FilterMaterialType } from '../materials/Materials'
|
||||
import Materials, {
|
||||
FilterMaterialType,
|
||||
type DisplayStyle,
|
||||
type RenderMaterial
|
||||
} from '../materials/Materials'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import { Batch, BatchUpdateRange, GeometryType, NoneBatchUpdateRange } from './Batch'
|
||||
import {
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
GeometryType,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch'
|
||||
import { Material, WebGLRenderer } from 'three'
|
||||
import Logger from 'js-logger'
|
||||
import { AsyncPause } from '../World'
|
||||
@@ -10,12 +19,20 @@ import { RenderTree } from '../tree/RenderTree'
|
||||
import TextBatch from './TextBatch'
|
||||
import SpeckleMesh, { TransformStorage } from '../objects/SpeckleMesh'
|
||||
import { SpeckleType } from '../loaders/GeometryConverter'
|
||||
import { TreeNode, WorldTree } from '../..'
|
||||
import { type TreeNode, WorldTree } from '../..'
|
||||
import { InstancedMeshBatch } from './InstancedMeshBatch'
|
||||
import { Geometry } from '../converter/Geometry'
|
||||
import { MeshBatch } from './MeshBatch'
|
||||
import { PointBatch } from './PointBatch'
|
||||
|
||||
type BatchTypeMap = {
|
||||
[GeometryType.MESH]: MeshBatch
|
||||
[GeometryType.LINE]: LineBatch
|
||||
[GeometryType.POINT]: PointBatch
|
||||
[GeometryType.POINT_CLOUD]: PointBatch
|
||||
[GeometryType.TEXT]: TextBatch
|
||||
}
|
||||
|
||||
export default class Batcher {
|
||||
private maxHardwareUniformCount = 0
|
||||
private floatTextures = false
|
||||
@@ -41,7 +58,6 @@ export default class Batcher {
|
||||
speckleType: SpeckleType[],
|
||||
batchType?: GeometryType
|
||||
) {
|
||||
const start = performance.now()
|
||||
let min = Number.MAX_SAFE_INTEGER,
|
||||
max = -1,
|
||||
average = 0,
|
||||
@@ -51,7 +67,6 @@ export default class Batcher {
|
||||
const instancedBatches: { [id: string]: Array<string> } = {}
|
||||
|
||||
const pause = new AsyncPause()
|
||||
const startInstancedGathering = performance.now()
|
||||
for (const g in instanceGroups) {
|
||||
pause.tick(100)
|
||||
if (pause.needsWait) {
|
||||
@@ -81,13 +96,10 @@ export default class Batcher {
|
||||
}
|
||||
instancedBatches[vertCount].push(g)
|
||||
}
|
||||
const instancedGathering = performance.now() - startInstancedGathering
|
||||
|
||||
let deInstancing = 0
|
||||
let instanceBuild = 0
|
||||
for (const v in instancedBatches) {
|
||||
for (let k = 0; k < instancedBatches[v].length; k++) {
|
||||
const nodes = worldTree.findId(instancedBatches[v][k])
|
||||
if (!nodes) continue
|
||||
/** Make sure entire instance set is instanced */
|
||||
let instanced = true
|
||||
nodes.every((node: TreeNode) => (instanced &&= node.model.instanced))
|
||||
@@ -98,7 +110,6 @@ export default class Batcher {
|
||||
.filter((rv) => rv)
|
||||
|
||||
if (Number.parseInt(v) < this.minInstancedBatchVertices || !instanced) {
|
||||
const t0 = performance.now()
|
||||
rvs.forEach((nodeRv) => {
|
||||
const geometry = nodeRv.renderData.geometry
|
||||
geometry.instanced = false
|
||||
@@ -118,28 +129,24 @@ export default class Batcher {
|
||||
Geometry.transformGeometryData(geometry, geometry.transform)
|
||||
nodeRv.computeAABB()
|
||||
})
|
||||
deInstancing += performance.now() - t0
|
||||
continue
|
||||
}
|
||||
|
||||
const t1 = performance.now()
|
||||
const materialHash = rvs[0].renderMaterialHash
|
||||
const instancedBatch = await this.buildInstancedBatch(
|
||||
renderTree,
|
||||
rvs,
|
||||
materialHash
|
||||
)
|
||||
instanceBuild += performance.now() - t1
|
||||
if (!instancedBatch) continue
|
||||
|
||||
this.batches[instancedBatch.id] = instancedBatch
|
||||
min = Math.min(min, instancedBatch.renderViews.length)
|
||||
max = Math.max(max, instancedBatch.renderViews.length)
|
||||
average += instancedBatch.renderViews.length
|
||||
batchCount++
|
||||
yield this.batches[instancedBatch.id]
|
||||
}
|
||||
}
|
||||
const totalInstanced = performance.now() - start
|
||||
|
||||
const renderViews = renderTree
|
||||
.getRenderableNodes(...speckleType)
|
||||
@@ -193,6 +200,8 @@ export default class Batcher {
|
||||
batchType
|
||||
)
|
||||
|
||||
if (!batch) continue
|
||||
|
||||
this.batches[batch.id] = batch
|
||||
min = Math.min(min, batch.renderViews.length)
|
||||
max = Math.max(max, batch.renderViews.length)
|
||||
@@ -206,10 +215,6 @@ export default class Batcher {
|
||||
average / materialHashes.length
|
||||
}`
|
||||
)
|
||||
Logger.warn('Total instanced -> ', totalInstanced)
|
||||
Logger.warn('Instance gathering -> ', instancedGathering)
|
||||
Logger.warn('De-instancing -> ', deInstancing)
|
||||
Logger.warn('Instanced build -> ', instanceBuild)
|
||||
}
|
||||
|
||||
private splitBatch(
|
||||
@@ -217,19 +222,29 @@ export default class Batcher {
|
||||
vertCount: number
|
||||
): NodeRenderView[][] {
|
||||
/** We're first splitting based on the batch's max vertex count */
|
||||
const vSplit = []
|
||||
const vSplit: Array<Array<NodeRenderView>> = []
|
||||
const vDiv = Math.floor(vertCount / this.maxBatchVertices)
|
||||
if (vDiv > 0) {
|
||||
let count = 0
|
||||
let index = 0
|
||||
vSplit.push([])
|
||||
for (let k = 0; k < renderViews.length; k++) {
|
||||
/** Catering to typescript.
|
||||
* RenderViews are prefiltered based on valid geometry before reaching this point
|
||||
*/
|
||||
const ervee = renderViews[k]
|
||||
const nextErvee = renderViews[k + 1]
|
||||
if (!ervee.renderData.geometry.attributes) {
|
||||
throw new Error(
|
||||
`Invalid geometry on render view ${renderViews[k].renderData.id}`
|
||||
)
|
||||
}
|
||||
vSplit[index].push(renderViews[k])
|
||||
count += renderViews[k].renderData.geometry.attributes.POSITION.length / 3
|
||||
count += ervee.renderData.geometry.attributes.POSITION.length / 3
|
||||
const nexCount =
|
||||
count +
|
||||
(renderViews[k + 1]
|
||||
? renderViews[k + 1].renderData.geometry.attributes.POSITION.length / 3
|
||||
(nextErvee && nextErvee.renderData.geometry.attributes
|
||||
? nextErvee.renderData.geometry.attributes.POSITION.length / 3
|
||||
: 0)
|
||||
if (nexCount >= this.maxBatchVertices && renderViews[k + 1]) {
|
||||
vSplit.push([])
|
||||
@@ -267,7 +282,7 @@ export default class Batcher {
|
||||
renderTree: RenderTree,
|
||||
renderViews: NodeRenderView[],
|
||||
materialHash: number
|
||||
): Promise<Batch> {
|
||||
): Promise<Batch | null> {
|
||||
if (!renderViews.length) {
|
||||
/** This is for the case when all renderviews have invalid geometries, and it generally
|
||||
* means there is something wrong with the stream
|
||||
@@ -294,7 +309,7 @@ export default class Batcher {
|
||||
renderViews: NodeRenderView[],
|
||||
materialHash: number,
|
||||
batchType?: GeometryType
|
||||
): Promise<Batch> {
|
||||
): Promise<Batch | null> {
|
||||
if (!renderViews.length) {
|
||||
/** This is for the case when all renderviews have invalid geometries, and it generally
|
||||
* means there is something wrong with the stream
|
||||
@@ -308,7 +323,8 @@ export default class Batcher {
|
||||
|
||||
const geometryType =
|
||||
batchType !== undefined ? batchType : renderViews[0].geometryType
|
||||
let matRef = null
|
||||
let matRef: RenderMaterial | DisplayStyle | null =
|
||||
renderViews[0].renderData.renderMaterial
|
||||
|
||||
if (geometryType === GeometryType.MESH) {
|
||||
matRef = renderViews[0].renderData.renderMaterial
|
||||
@@ -325,7 +341,7 @@ export default class Batcher {
|
||||
const material = this.materials.getMaterial(materialHash, matRef, geometryType)
|
||||
|
||||
const batchID = MathUtils.generateUUID()
|
||||
let geometryBatch: Batch = null
|
||||
let geometryBatch: Batch | null = null
|
||||
switch (geometryType) {
|
||||
case GeometryType.MESH:
|
||||
geometryBatch = new MeshBatch(
|
||||
@@ -365,12 +381,13 @@ export default class Batcher {
|
||||
|
||||
public render(renderer: WebGLRenderer) {
|
||||
for (const batchId in this.batches) {
|
||||
if (this.batches[batchId].onRender) this.batches[batchId].onRender(renderer)
|
||||
const batch = this.batches[batchId]
|
||||
if (batch.onRender) batch.onRender(renderer)
|
||||
}
|
||||
}
|
||||
|
||||
public saveVisiblity(): Record<string, BatchUpdateRange> {
|
||||
const visibilityRanges = {}
|
||||
const visibilityRanges: Record<string, BatchUpdateRange> = {}
|
||||
for (const k in this.batches) {
|
||||
const batch: Batch = this.batches[k]
|
||||
visibilityRanges[k] = batch.getVisibleRange()
|
||||
@@ -383,15 +400,15 @@ export default class Batcher {
|
||||
const batch: Batch = this.batches[k]
|
||||
const range = ranges[k]
|
||||
if (!range) {
|
||||
batch.setVisibleRange(NoneBatchUpdateRange)
|
||||
batch.setVisibleRange([NoneBatchUpdateRange])
|
||||
} else {
|
||||
batch.setVisibleRange(range)
|
||||
batch.setVisibleRange([range])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getTransparent(): Record<string, BatchUpdateRange> {
|
||||
const visibilityRanges = {}
|
||||
const visibilityRanges: Record<string, BatchUpdateRange> = {}
|
||||
for (const k in this.batches) {
|
||||
visibilityRanges[k] = this.batches[k].getTransparent()
|
||||
}
|
||||
@@ -399,7 +416,7 @@ export default class Batcher {
|
||||
}
|
||||
|
||||
public getStencil(): Record<string, BatchUpdateRange> {
|
||||
const visibilityRanges = {}
|
||||
const visibilityRanges: Record<string, BatchUpdateRange> = {}
|
||||
for (const k in this.batches) {
|
||||
visibilityRanges[k] = this.batches[k].getStencil()
|
||||
}
|
||||
@@ -407,7 +424,7 @@ export default class Batcher {
|
||||
}
|
||||
|
||||
public getOpaque(): Record<string, BatchUpdateRange> {
|
||||
const visibilityRanges = {}
|
||||
const visibilityRanges: Record<string, BatchUpdateRange> = {}
|
||||
for (const k in this.batches) {
|
||||
visibilityRanges[k] = this.batches[k].getOpaque()
|
||||
}
|
||||
@@ -415,7 +432,7 @@ export default class Batcher {
|
||||
}
|
||||
|
||||
public getDepth(): Record<string, BatchUpdateRange> {
|
||||
const visibilityRanges = {}
|
||||
const visibilityRanges: Record<string, BatchUpdateRange> = {}
|
||||
for (const k in this.batches) {
|
||||
visibilityRanges[k] = this.batches[k].getDepth()
|
||||
}
|
||||
@@ -450,13 +467,38 @@ export default class Batcher {
|
||||
}
|
||||
}
|
||||
|
||||
public getBatches(subtreeId?: string, geometryType?: GeometryType) {
|
||||
return Object.values(this.batches).filter((value: Batch) => {
|
||||
public getBatches<K extends GeometryType>(
|
||||
subtreeId?: string,
|
||||
geometryType?: K
|
||||
): BatchTypeMap[K][] {
|
||||
const batches: Batch[] = Object.values(this.batches)
|
||||
return batches.filter((value: Batch) => {
|
||||
const subtree = subtreeId !== undefined ? value.subtreeId === subtreeId : true
|
||||
const type =
|
||||
geometryType !== undefined ? value.geometryType === geometryType : true
|
||||
geometryType !== undefined ? this.isBatchType(value, geometryType) : true
|
||||
return subtree && type
|
||||
})
|
||||
}) as BatchTypeMap[K][]
|
||||
}
|
||||
|
||||
private isBatchType<K extends GeometryType>(
|
||||
batch: Batch,
|
||||
geometryType?: K
|
||||
): batch is BatchTypeMap[K] {
|
||||
if (geometryType === undefined) return true
|
||||
switch (geometryType) {
|
||||
case GeometryType.MESH:
|
||||
return batch instanceof MeshBatch
|
||||
case GeometryType.LINE:
|
||||
return batch instanceof LineBatch
|
||||
case GeometryType.POINT:
|
||||
return batch instanceof PointBatch
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return batch instanceof PointBatch
|
||||
case GeometryType.TEXT:
|
||||
return batch instanceof TextBatch
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public getBatch(rv: NodeRenderView) {
|
||||
@@ -490,32 +532,18 @@ export default class Batcher {
|
||||
/**
|
||||
* Used for debuggin only
|
||||
*/
|
||||
|
||||
public async isolateRenderViewBatch(id: string, renderTree: RenderTree) {
|
||||
const rv = renderTree.getRenderViewForNodeId(id)
|
||||
for (const k in this.batches) {
|
||||
if (k !== rv.batchId) {
|
||||
this.batches[k].setDrawRanges({
|
||||
offset: 0,
|
||||
count: this.batches[k].getCount(),
|
||||
material: this.materials.getFilterMaterial(this.batches[k].renderViews[0], {
|
||||
filterType: FilterMaterialType.GHOST
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async isolateBatch(batchId: string) {
|
||||
for (const k in this.batches) {
|
||||
if (k !== batchId) {
|
||||
this.batches[k].setDrawRanges({
|
||||
offset: 0,
|
||||
count: this.batches[k].getCount(),
|
||||
material: this.materials.getFilterMaterial(this.batches[k].renderViews[0], {
|
||||
filterType: FilterMaterialType.GHOST
|
||||
})
|
||||
})
|
||||
this.batches[k].setDrawRanges([
|
||||
{
|
||||
offset: 0,
|
||||
count: this.batches[k].getCount(),
|
||||
material: this.materials.getFilterMaterial(this.batches[k].renderViews[0], {
|
||||
filterType: FilterMaterialType.GHOST
|
||||
}) as Material
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Material } from 'three'
|
||||
import { BatchUpdateRange } from './Batch'
|
||||
import { DrawGroup } from './Batch'
|
||||
import { type BatchUpdateRange } from './Batch'
|
||||
import { type DrawGroup } from './Batch'
|
||||
|
||||
export class DrawRanges {
|
||||
public integrateRanges(
|
||||
@@ -12,14 +12,14 @@ export class DrawRanges {
|
||||
groups.sort((a, b) => a.start - b.start)
|
||||
ranges.sort((a, b) => a.offset - b.offset)
|
||||
|
||||
const edgesForward = {}
|
||||
const edgesBackwards = {}
|
||||
const edgesForward: { [key: number]: number } = {}
|
||||
const edgesBackwards: { [key: number]: number } = {}
|
||||
for (let k = 0, l = groups.length - 1; k < groups.length; k++, l--) {
|
||||
const groupForward = groups[k]
|
||||
const groupBackwards = groups[l]
|
||||
edgesForward[groupForward.start] = groupForward.materialIndex
|
||||
edgesForward[groupForward.start] = groupForward.materialIndex as number
|
||||
edgesBackwards[groupBackwards.start + groupBackwards.count] =
|
||||
groupBackwards.materialIndex
|
||||
groupBackwards.materialIndex as number
|
||||
}
|
||||
|
||||
_flatRanges = groups.map((group: DrawGroup) => {
|
||||
@@ -43,7 +43,7 @@ export class DrawRanges {
|
||||
})
|
||||
_flatRanges = [...new Set(_flatRanges)]
|
||||
|
||||
const materialIndex = materials.indexOf(range.material)
|
||||
const materialIndex = materials.indexOf(range.material as Material)
|
||||
edgesForward[r0] = materialIndex
|
||||
edgesForward[r1] = r1 >= next ? edgesForward[next] : edgesBackwards[next]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { BatchObject, VectorLike } from './BatchObject'
|
||||
import { BatchObject, type Vector3Like } from './BatchObject'
|
||||
|
||||
import { Matrix4 } from 'three'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
@@ -9,17 +9,18 @@ export class InstancedBatchObject extends BatchObject {
|
||||
|
||||
public constructor(renderView: NodeRenderView, batchIndex: number) {
|
||||
super(renderView, batchIndex)
|
||||
this.instanceTransform.copy(renderView.renderData.geometry.transform)
|
||||
if (renderView.renderData.geometry.transform)
|
||||
this.instanceTransform.copy(renderView.renderData.geometry.transform)
|
||||
this.transform.copy(this.instanceTransform)
|
||||
this.transformInv.copy(new Matrix4().copy(this.instanceTransform).invert())
|
||||
this.transformDirty = false
|
||||
}
|
||||
|
||||
public transformTRS(
|
||||
translation: VectorLike,
|
||||
euler: VectorLike,
|
||||
scale: VectorLike,
|
||||
pivot: VectorLike
|
||||
translation: Vector3Like,
|
||||
euler: Vector3Like,
|
||||
scale: Vector3Like,
|
||||
pivot: Vector3Like
|
||||
) {
|
||||
super.transformTRS(translation, euler, scale, pivot)
|
||||
this.transform.multiply(this.instanceTransform)
|
||||
|
||||
@@ -14,8 +14,9 @@ import { Geometry } from '../converter/Geometry'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import {
|
||||
AllBatchUpdateRange,
|
||||
Batch,
|
||||
BatchUpdateRange,
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
type DrawGroup,
|
||||
GeometryType,
|
||||
INSTANCE_TRANSFORM_BUFFER_STRIDE,
|
||||
NoneBatchUpdateRange
|
||||
@@ -31,7 +32,6 @@ import Logger from 'js-logger'
|
||||
import Materials from '../materials/Materials'
|
||||
import { DrawRanges } from './DrawRanges'
|
||||
import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial'
|
||||
import { DrawGroup } from './Batch'
|
||||
|
||||
export class InstancedMeshBatch implements Batch {
|
||||
public id: string
|
||||
@@ -40,12 +40,12 @@ export class InstancedMeshBatch implements Batch {
|
||||
private geometry: BufferGeometry
|
||||
public batchMaterial: Material
|
||||
public mesh: SpeckleInstancedMesh
|
||||
private drawRanges: DrawRanges = new DrawRanges()
|
||||
protected drawRanges: DrawRanges = new DrawRanges()
|
||||
|
||||
private instanceTransformBuffer0: Float32Array = null
|
||||
private instanceTransformBuffer1: Float32Array = null
|
||||
private instanceTransformBuffer0!: Float32Array
|
||||
private instanceTransformBuffer1!: Float32Array
|
||||
private transformBufferIndex: number = 0
|
||||
private instanceGradientBuffer: Float32Array = null
|
||||
private instanceGradientBuffer!: Float32Array
|
||||
|
||||
private needsShuffle = false
|
||||
|
||||
@@ -67,7 +67,11 @@ export class InstancedMeshBatch implements Batch {
|
||||
}
|
||||
|
||||
public get triCount(): number {
|
||||
return (this.geometry.index.count / 3) * this.renderViews.length
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where the geometry is non-indexed. We're **explicitly** setting the index at creation time
|
||||
*/
|
||||
const indexCount = this.geometry.index ? this.geometry.index.count : 0
|
||||
return (indexCount / 3) * this.renderViews.length
|
||||
}
|
||||
|
||||
public get vertCount(): number {
|
||||
@@ -125,7 +129,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
}
|
||||
|
||||
/** Note: You can only set visibility on ranges that exist as draw groups! */
|
||||
public setVisibleRange(...ranges: BatchUpdateRange[]) {
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
/** Entire batch needs to NOT be drawn */
|
||||
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
|
||||
this.mesh.children.forEach((instance) => (instance.visible = false))
|
||||
@@ -139,14 +143,15 @@ export class InstancedMeshBatch implements Batch {
|
||||
|
||||
this.mesh.children.forEach((instance) => (instance.visible = false))
|
||||
ranges.forEach((range) => {
|
||||
const instanceIndex = this.groups.indexOf(
|
||||
this.groups.find(
|
||||
(group: DrawGroup) =>
|
||||
range.offset === group.start &&
|
||||
range.offset + range.count === group.start + group.count
|
||||
)
|
||||
const foundInstance = this.groups.find(
|
||||
(group: DrawGroup) =>
|
||||
range.offset === group.start &&
|
||||
range.offset + range.count === group.start + group.count
|
||||
)
|
||||
if (instanceIndex !== -1) this.mesh.children[instanceIndex].visible = true
|
||||
if (foundInstance) {
|
||||
const instanceIndex = this.groups.indexOf(foundInstance)
|
||||
if (instanceIndex !== -1) this.mesh.children[instanceIndex].visible = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -166,6 +171,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
public getOpaque(): BatchUpdateRange {
|
||||
/** If there is any transparent or hidden group return the update range up to it's offset */
|
||||
const transparentOrHiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return (
|
||||
Materials.isTransparent(this.materials[value.materialIndex]) ||
|
||||
this.materials[value.materialIndex].visible === false
|
||||
@@ -185,6 +191,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
public getDepth(): BatchUpdateRange {
|
||||
/** If there is any transparent or hidden group return the update range up to it's offset */
|
||||
const transparentOrHiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return (
|
||||
Materials.isTransparent(this.materials[value.materialIndex]) ||
|
||||
this.materials[value.materialIndex].visible === false ||
|
||||
@@ -205,10 +212,12 @@ export class InstancedMeshBatch implements Batch {
|
||||
public getTransparent(): BatchUpdateRange {
|
||||
/** Look for a transparent group */
|
||||
const transparentGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return Materials.isTransparent(this.materials[value.materialIndex])
|
||||
})
|
||||
/** Look for a hidden group */
|
||||
const hiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return this.materials[value.materialIndex].visible === false
|
||||
})
|
||||
/** If there is a transparent group return it's range */
|
||||
@@ -234,6 +243,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
if (this.materials[0].stencilWrite === true) return AllBatchUpdateRange
|
||||
}
|
||||
const stencilGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return this.materials[value.materialIndex].stencilWrite === true
|
||||
})
|
||||
if (stencilGroup) {
|
||||
@@ -246,28 +256,33 @@ export class InstancedMeshBatch implements Batch {
|
||||
return NoneBatchUpdateRange
|
||||
}
|
||||
|
||||
public setBatchBuffers(...range: BatchUpdateRange[]): void {
|
||||
for (let k = 0; k < range.length; k++) {
|
||||
if (range[k].materialOptions) {
|
||||
if (range[k].materialOptions.rampIndex !== undefined) {
|
||||
const start = range[k].offset
|
||||
public setBatchBuffers(ranges: BatchUpdateRange[]): void {
|
||||
for (let k = 0; k < ranges.length; k++) {
|
||||
const range = ranges[k]
|
||||
if (range.materialOptions) {
|
||||
if (
|
||||
range.materialOptions.rampIndex !== undefined &&
|
||||
range.materialOptions.rampWidth !== undefined
|
||||
) {
|
||||
const start = ranges[k].offset
|
||||
/** The ramp indices specify the *begining* of each ramp color. When sampling with Nearest filter (since we don't want filtering)
|
||||
* we'll always be sampling right at the edge between texels. Most GPUs will sample consistently, but some won't and we end up with
|
||||
* a ton of artifacts. To avoid this, we are shifting the sampling indices so they're right on the center of each texel, so no inconsistent
|
||||
* sampling can occur.
|
||||
*/
|
||||
const shiftedIndex =
|
||||
range[k].materialOptions.rampIndex +
|
||||
0.5 / range[k].materialOptions.rampWidth
|
||||
this.updateGradientIndexBufferData(start / 16, shiftedIndex)
|
||||
if (range.materialOptions.rampIndex && range.materialOptions.rampWidth) {
|
||||
const shiftedIndex =
|
||||
range.materialOptions.rampIndex + 0.5 / range.materialOptions.rampWidth
|
||||
this.updateGradientIndexBufferData(start / 16, shiftedIndex)
|
||||
}
|
||||
}
|
||||
/** We need to update the texture here, because each batch uses it's own clone for any material we use on it
|
||||
* because otherwise three.js won't properly update our custom uniforms
|
||||
*/
|
||||
if (range[k].materialOptions.rampTexture !== undefined) {
|
||||
if (range[k].material instanceof SpeckleStandardColoredMaterial) {
|
||||
;(range[k].material as SpeckleStandardColoredMaterial).setGradientTexture(
|
||||
range[k].materialOptions.rampTexture
|
||||
if (range.materialOptions.rampTexture !== undefined) {
|
||||
if (range.material instanceof SpeckleStandardColoredMaterial) {
|
||||
;(range.material as SpeckleStandardColoredMaterial).setGradientTexture(
|
||||
range.materialOptions.rampTexture
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -275,15 +290,15 @@ export class InstancedMeshBatch implements Batch {
|
||||
}
|
||||
}
|
||||
|
||||
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
||||
public setDrawRanges(ranges: BatchUpdateRange[]) {
|
||||
ranges.forEach((value: BatchUpdateRange) => {
|
||||
if (value.material) {
|
||||
value.material = this.mesh.getCachedMaterial(value.material)
|
||||
}
|
||||
})
|
||||
|
||||
const materials = ranges.map((val) => {
|
||||
return val.material
|
||||
const materials: Array<Material> = ranges.map((val: BatchUpdateRange) => {
|
||||
return val.material as Material
|
||||
})
|
||||
const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))]
|
||||
|
||||
@@ -303,7 +318,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
if (count !== this.renderViews.length * 16) {
|
||||
Logger.error(`Draw groups invalid on ${this.id}`)
|
||||
}
|
||||
this.setBatchBuffers(...ranges)
|
||||
this.setBatchBuffers(ranges)
|
||||
this.cleanMaterials()
|
||||
/** We shuffle only when above a certain fragmentation threshold. We don't want to be shuffling every single time */
|
||||
if (this.drawCalls > this.maxDrawCalls) {
|
||||
@@ -335,7 +350,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
}
|
||||
}
|
||||
|
||||
private shuffleDrawGroups() {
|
||||
private shuffleDrawGroups(): void {
|
||||
const groups = this.groups
|
||||
.sort((a, b) => {
|
||||
return a.start - b.start
|
||||
@@ -354,10 +369,12 @@ export class InstancedMeshBatch implements Batch {
|
||||
return transparentOrder
|
||||
})
|
||||
|
||||
const materialOrder = []
|
||||
const materialOrder: Array<number> = []
|
||||
groups.reduce((previousValue, currentValue) => {
|
||||
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
|
||||
previousValue.push(currentValue.materialIndex)
|
||||
if (currentValue.materialIndex !== undefined) {
|
||||
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
|
||||
previousValue.push(currentValue.materialIndex)
|
||||
}
|
||||
}
|
||||
return previousValue
|
||||
}, materialOrder)
|
||||
@@ -438,17 +455,20 @@ export class InstancedMeshBatch implements Batch {
|
||||
|
||||
/** Solve hidden groups */
|
||||
const hiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return this.materials[value.materialIndex].visible === false
|
||||
})
|
||||
if (hiddenGroup) {
|
||||
this.setVisibleRange({
|
||||
offset: 0,
|
||||
count: hiddenGroup.start
|
||||
})
|
||||
this.setVisibleRange([
|
||||
{
|
||||
offset: 0,
|
||||
count: hiddenGroup.start
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
public resetDrawRanges() {
|
||||
public resetDrawRanges(): void {
|
||||
this.groups.length = 0
|
||||
this.materials.length = 0
|
||||
this.groups.push({
|
||||
@@ -457,7 +477,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
materialIndex: 0
|
||||
})
|
||||
this.materials.push(this.batchMaterial)
|
||||
this.setVisibleRange(AllBatchUpdateRange)
|
||||
this.setVisibleRange([AllBatchUpdateRange])
|
||||
this.mesh.updateDrawGroups(
|
||||
this.getCurrentTransformBuffer(),
|
||||
this.getCurrentGradientBuffer()
|
||||
@@ -480,7 +500,7 @@ export class InstancedMeshBatch implements Batch {
|
||||
return this.instanceGradientBuffer
|
||||
}
|
||||
|
||||
public buildBatch() {
|
||||
public buildBatch(): void {
|
||||
const batchObjects = []
|
||||
let instanceBVH = null
|
||||
this.instanceTransformBuffer0 = new Float32Array(
|
||||
@@ -492,7 +512,17 @@ export class InstancedMeshBatch implements Batch {
|
||||
const targetInstanceTransformBuffer = this.getCurrentTransformBuffer()
|
||||
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
this.renderViews[k].renderData.geometry.transform.toArray(
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where an instanced render view does not have a transform
|
||||
* It's against it's definition
|
||||
*/
|
||||
const ervee = this.renderViews[k]
|
||||
if (!ervee.renderData.geometry.transform) {
|
||||
throw new Error(
|
||||
`Instanced Render view with id ${ervee.renderData.id} has null transform!`
|
||||
)
|
||||
}
|
||||
ervee.renderData.geometry.transform.toArray(
|
||||
targetInstanceTransformBuffer,
|
||||
k * INSTANCE_TRANSFORM_BUFFER_STRIDE
|
||||
)
|
||||
@@ -509,11 +539,13 @@ export class InstancedMeshBatch implements Batch {
|
||||
batchObject.localOrigin.z
|
||||
)
|
||||
transform.invert()
|
||||
const indices = this.renderViews[k].renderData.geometry.attributes.INDEX
|
||||
const position = this.renderViews[k].renderData.geometry.attributes.POSITION
|
||||
const indices: number[] | undefined =
|
||||
this.renderViews[k].renderData.geometry.attributes?.INDEX
|
||||
const position: number[] | undefined = this.renderViews[k].renderData.geometry
|
||||
.attributes?.POSITION as number[]
|
||||
instanceBVH = AccelerationStructure.buildBVH(
|
||||
indices,
|
||||
new Float32Array(position),
|
||||
position,
|
||||
DefaultBVHOptions,
|
||||
transform
|
||||
)
|
||||
@@ -524,25 +556,32 @@ export class InstancedMeshBatch implements Batch {
|
||||
batchObjects.push(batchObject)
|
||||
}
|
||||
|
||||
const indices = new Uint32Array(
|
||||
this.renderViews[0].renderData.geometry.attributes.INDEX
|
||||
)
|
||||
const positions = new Float64Array(
|
||||
this.renderViews[0].renderData.geometry.attributes.POSITION
|
||||
)
|
||||
const colors = new Float32Array(
|
||||
this.renderViews[0].renderData.geometry.attributes.COLOR
|
||||
)
|
||||
const indices: number[] | undefined =
|
||||
this.renderViews[0].renderData.geometry.attributes?.INDEX
|
||||
|
||||
this.makeInstancedMeshGeometry(indices, positions, colors)
|
||||
const positions: number[] | undefined =
|
||||
this.renderViews[0].renderData.geometry.attributes?.POSITION
|
||||
|
||||
const colors: number[] | undefined =
|
||||
this.renderViews[0].renderData.geometry.attributes?.COLOR
|
||||
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where indices or positions are undefined at this point
|
||||
*/
|
||||
if (!indices || !positions) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Undefined indices or positions`)
|
||||
}
|
||||
this.makeInstancedMeshGeometry(
|
||||
positions.length >= 65535 || indices.length >= 65535
|
||||
? new Uint32Array(indices)
|
||||
: new Uint16Array(indices),
|
||||
new Float64Array(positions),
|
||||
colors ? new Float32Array(colors) : undefined
|
||||
)
|
||||
this.mesh = new SpeckleInstancedMesh(this.geometry)
|
||||
this.mesh.setBatchObjects(batchObjects)
|
||||
this.mesh.setBatchMaterial(this.batchMaterial)
|
||||
this.mesh.buildTAS()
|
||||
const bounds = new Box3()
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
bounds.union(this.renderViews[k].aabb)
|
||||
}
|
||||
|
||||
this.geometry.boundingBox = this.mesh.TAS.getBoundingBox(new Box3())
|
||||
this.geometry.boundingSphere = this.geometry.boundingBox.getBoundingSphere(
|
||||
@@ -564,19 +603,19 @@ export class InstancedMeshBatch implements Batch {
|
||||
)
|
||||
}
|
||||
|
||||
public getRenderView(index: number): NodeRenderView {
|
||||
public getRenderView(index: number): NodeRenderView | null {
|
||||
index
|
||||
Logger.warn('Deprecated! Use InstancedBatchObject')
|
||||
return null
|
||||
}
|
||||
|
||||
public getMaterialAtIndex(index: number): Material {
|
||||
public getMaterialAtIndex(index: number): Material | null {
|
||||
index
|
||||
Logger.warn('Deprecated! Use InstancedBatchObject')
|
||||
return null
|
||||
}
|
||||
|
||||
public getMaterial(rv: NodeRenderView): Material {
|
||||
public getMaterial(rv: NodeRenderView): Material | null {
|
||||
const group = this.groups.find((value) => {
|
||||
return (
|
||||
rv.batchStart >= value.start &&
|
||||
@@ -629,10 +668,9 @@ export class InstancedMeshBatch implements Batch {
|
||||
data[index] = value
|
||||
}
|
||||
|
||||
public purge() {
|
||||
public purge(): void {
|
||||
this.renderViews.length = 0
|
||||
this.geometry.dispose()
|
||||
this.batchMaterial.dispose()
|
||||
this.mesh = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
Box3,
|
||||
Color,
|
||||
DynamicDrawUsage,
|
||||
InstancedInterleavedBuffer,
|
||||
InterleavedBufferAttribute,
|
||||
Line,
|
||||
Material,
|
||||
Object3D,
|
||||
Vector4,
|
||||
@@ -16,28 +16,28 @@ import SpeckleLineMaterial from '../materials/SpeckleLineMaterial'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import {
|
||||
AllBatchUpdateRange,
|
||||
Batch,
|
||||
BatchUpdateRange,
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
type DrawGroup,
|
||||
GeometryType,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch'
|
||||
import { ObjectLayers } from '../../IViewer'
|
||||
import { DrawGroup } from './Batch'
|
||||
import Materials from '../materials/Materials'
|
||||
|
||||
export default class LineBatch implements Batch {
|
||||
public id: string
|
||||
public subtreeId: string
|
||||
public renderViews: NodeRenderView[]
|
||||
private geometry: LineSegmentsGeometry
|
||||
protected geometry: LineSegmentsGeometry
|
||||
public batchMaterial: SpeckleLineMaterial
|
||||
private mesh: LineSegments2 | Line
|
||||
public colorBuffer: InstancedInterleavedBuffer
|
||||
protected mesh: LineSegments2
|
||||
public colorBuffer!: InstancedInterleavedBuffer
|
||||
private static readonly vector4Buffer: Vector4 = new Vector4()
|
||||
|
||||
public get bounds() {
|
||||
public get bounds(): Box3 {
|
||||
if (!this.geometry.boundingBox) this.geometry.computeBoundingBox()
|
||||
return this.geometry.boundingBox
|
||||
return this.geometry.boundingBox ? this.geometry.boundingBox : new Box3()
|
||||
}
|
||||
|
||||
public get drawCalls(): number {
|
||||
@@ -65,7 +65,11 @@ export default class LineBatch implements Batch {
|
||||
return 0
|
||||
}
|
||||
public get lineCount(): number {
|
||||
return (this.geometry.index.count / 3) * this.geometry['_maxInstanceCount']
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where the geometry is non-indexed. LineSegments2 are **explicitly** indexed
|
||||
*/
|
||||
const indexCount = this.geometry.index ? this.geometry.index.count : 0
|
||||
return (indexCount / 3) * (this.geometry as never)['_maxInstanceCount']
|
||||
}
|
||||
|
||||
public get renderObject(): Object3D {
|
||||
@@ -73,11 +77,11 @@ export default class LineBatch implements Batch {
|
||||
}
|
||||
|
||||
public get geometryType(): GeometryType {
|
||||
return this.renderViews[0].geometryType
|
||||
return GeometryType.LINE
|
||||
}
|
||||
|
||||
public get materials(): Material[] {
|
||||
return this.mesh.material as Material[]
|
||||
return this.mesh.material as unknown as Material[]
|
||||
}
|
||||
|
||||
public get groups(): DrawGroup[] {
|
||||
@@ -100,7 +104,7 @@ export default class LineBatch implements Batch {
|
||||
renderer.getDrawingBufferSize(this.batchMaterial.resolution)
|
||||
}
|
||||
|
||||
public setVisibleRange(...ranges: BatchUpdateRange[]) {
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
if (
|
||||
ranges.length === 1 &&
|
||||
ranges[0].offset === NoneBatchUpdateRange.offset &&
|
||||
@@ -163,7 +167,7 @@ export default class LineBatch implements Batch {
|
||||
return NoneBatchUpdateRange
|
||||
}
|
||||
|
||||
public setBatchBuffers(...ranges: BatchUpdateRange[]): void {
|
||||
public setBatchBuffers(ranges: BatchUpdateRange[]): void {
|
||||
const data = this.colorBuffer.array as number[]
|
||||
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
@@ -193,16 +197,18 @@ export default class LineBatch implements Batch {
|
||||
this.geometry.attributes['instanceColorEnd'].needsUpdate = true
|
||||
}
|
||||
|
||||
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
||||
this.setBatchBuffers(...ranges)
|
||||
public setDrawRanges(ranges: BatchUpdateRange[]) {
|
||||
this.setBatchBuffers(ranges)
|
||||
}
|
||||
|
||||
public resetDrawRanges() {
|
||||
this.setDrawRanges({
|
||||
offset: 0,
|
||||
count: Infinity,
|
||||
material: this.batchMaterial
|
||||
})
|
||||
this.setDrawRanges([
|
||||
{
|
||||
offset: 0,
|
||||
count: Infinity,
|
||||
material: this.batchMaterial
|
||||
}
|
||||
])
|
||||
this.mesh.material = this.batchMaterial
|
||||
this.mesh.visible = true
|
||||
this.batchMaterial.transparent = false
|
||||
@@ -210,17 +216,22 @@ export default class LineBatch implements Batch {
|
||||
|
||||
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)
|
||||
)
|
||||
this.renderViews.forEach((val: NodeRenderView) => {
|
||||
if (!val.renderData.geometry.attributes) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Invalid geometry`)
|
||||
}
|
||||
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
|
||||
if (!geometry.attributes) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Invalid geometry`)
|
||||
}
|
||||
let points: Array<number>
|
||||
/** 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
|
||||
@@ -239,7 +250,7 @@ export default class LineBatch implements Batch {
|
||||
points[2 * i + 5] = geometry.attributes.POSITION[i + 5]
|
||||
}
|
||||
} else {
|
||||
points = geometry.attributes.POSITION
|
||||
points = geometry.attributes.POSITION as number[]
|
||||
}
|
||||
|
||||
position.set(points, offset)
|
||||
@@ -259,7 +270,7 @@ export default class LineBatch implements Batch {
|
||||
this.mesh.layers.set(ObjectLayers.STREAM_CONTENT_LINE)
|
||||
}
|
||||
|
||||
public getRenderView(index: number): NodeRenderView {
|
||||
public getRenderView(index: number): NodeRenderView | null {
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
if (
|
||||
index >= this.renderViews[k].batchStart &&
|
||||
@@ -270,6 +281,7 @@ export default class LineBatch implements Batch {
|
||||
return this.renderViews[k]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public getMaterialAtIndex(index: number): Material {
|
||||
@@ -336,7 +348,6 @@ export default class LineBatch implements Batch {
|
||||
this.renderViews.length = 0
|
||||
this.geometry.dispose()
|
||||
this.batchMaterial.dispose()
|
||||
this.mesh = null
|
||||
this.colorBuffer.length = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,37 +9,33 @@ import {
|
||||
DynamicDrawUsage,
|
||||
Sphere
|
||||
} from 'three'
|
||||
import { GeometryType, BatchUpdateRange } from './Batch'
|
||||
import { DrawGroup } from './Batch'
|
||||
import { PrimitiveBatch } from './PrimitiveBatch'
|
||||
import SpeckleMesh, { TransformStorage } from '../objects/SpeckleMesh'
|
||||
import Logger from 'js-logger'
|
||||
import { DrawRanges } from './DrawRanges'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import { type BatchUpdateRange, type DrawGroup, GeometryType } from './Batch'
|
||||
import { BatchObject } from './BatchObject'
|
||||
import { Geometry } from '../converter/Geometry'
|
||||
import { ObjectLayers } from '../../IViewer'
|
||||
|
||||
export class MeshBatch extends PrimitiveBatch {
|
||||
protected primitive: SpeckleMesh
|
||||
protected primitive!: SpeckleMesh
|
||||
protected transformStorage: TransformStorage
|
||||
|
||||
private indexBuffer0: BufferAttribute
|
||||
private indexBuffer1: BufferAttribute
|
||||
private indexBuffer0!: BufferAttribute
|
||||
private indexBuffer1!: BufferAttribute
|
||||
private indexBufferIndex = 0
|
||||
|
||||
private drawRanges: DrawRanges = new DrawRanges()
|
||||
protected drawRanges: DrawRanges = new DrawRanges()
|
||||
|
||||
get bounds(): Box3 {
|
||||
return this.primitive.TAS.getBoundingBox(new Box3())
|
||||
}
|
||||
|
||||
get minDrawCalls(): number {
|
||||
return [
|
||||
...Array.from(
|
||||
new Set(this.primitive.geometry.groups.map((value) => value.materialIndex))
|
||||
)
|
||||
].length
|
||||
return [...Array.from(new Set(this.groups.map((value) => value.materialIndex)))]
|
||||
.length
|
||||
}
|
||||
|
||||
get triCount(): number {
|
||||
@@ -100,6 +96,9 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
end: number,
|
||||
value: number
|
||||
): { minIndex: number; maxIndex: number } {
|
||||
if (!this.primitive.geometry.index) {
|
||||
throw new Error(`Invalid geometry on batch ${this.id}`)
|
||||
}
|
||||
const index = this.primitive.geometry.index.array as number[]
|
||||
const data = this.gradientIndexBuffer.array as number[]
|
||||
let minVertexIndex = Infinity
|
||||
@@ -122,7 +121,7 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
}
|
||||
}
|
||||
|
||||
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
||||
public setDrawRanges(ranges: BatchUpdateRange[]) {
|
||||
// const current = this.groups.slice()
|
||||
// const incoming = ranges.slice()
|
||||
ranges.forEach((value: BatchUpdateRange) => {
|
||||
@@ -130,24 +129,22 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
value.material = this.primitive.getCachedMaterial(value.material)
|
||||
}
|
||||
})
|
||||
const materials = ranges.map((val) => {
|
||||
return val.material
|
||||
const materials: Array<Material> = ranges.map((val: BatchUpdateRange) => {
|
||||
return val.material as Material
|
||||
})
|
||||
const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))]
|
||||
const uniqueMaterials: Array<Material> = [
|
||||
...Array.from(new Set(materials.map((value: Material) => value)))
|
||||
]
|
||||
|
||||
for (let k = 0; k < uniqueMaterials.length; k++) {
|
||||
if (!this.materials.includes(uniqueMaterials[k]))
|
||||
this.materials.push(uniqueMaterials[k])
|
||||
}
|
||||
|
||||
this.primitive.geometry.groups = this.drawRanges.integrateRanges(
|
||||
this.groups,
|
||||
this.materials,
|
||||
ranges
|
||||
)
|
||||
this.groups = this.drawRanges.integrateRanges(this.groups, this.materials, ranges)
|
||||
|
||||
let count = 0
|
||||
this.primitive.geometry.groups.forEach((value) => (count += value.count))
|
||||
this.groups.forEach((value) => (count += value.count))
|
||||
if (count !== this.getCount()) {
|
||||
// Logger.error('Current -> ', current)
|
||||
// Logger.error('Incoming -> ', incoming)
|
||||
@@ -158,7 +155,7 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
}, ${this.getCount()}, ${this.getCount() - count}`
|
||||
)
|
||||
}
|
||||
this.setBatchBuffers(...ranges)
|
||||
this.setBatchBuffers(ranges)
|
||||
this.cleanMaterials()
|
||||
|
||||
if (this.drawCalls > this.minDrawCalls + 2) {
|
||||
@@ -196,13 +193,22 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
let indicesCount = 0
|
||||
let attributeCount = 0
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
indicesCount += this.renderViews[k].renderData.geometry.attributes.INDEX.length
|
||||
attributeCount +=
|
||||
this.renderViews[k].renderData.geometry.attributes.POSITION.length
|
||||
const ervee = this.renderViews[k]
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where indices or positions are undefined at this point
|
||||
*/
|
||||
if (
|
||||
!ervee.renderData.geometry.attributes ||
|
||||
!ervee.renderData.geometry.attributes.INDEX
|
||||
) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
|
||||
}
|
||||
indicesCount += ervee.renderData.geometry.attributes.INDEX.length
|
||||
attributeCount += ervee.renderData.geometry.attributes.POSITION.length
|
||||
}
|
||||
|
||||
const hasVertexColors =
|
||||
this.renderViews[0].renderData.geometry.attributes.COLOR !== undefined
|
||||
this.renderViews[0].renderData.geometry.attributes?.COLOR !== undefined
|
||||
const indices = new Uint32Array(indicesCount)
|
||||
const position = new Float64Array(attributeCount)
|
||||
const color = new Float32Array(hasVertexColors ? attributeCount : 0)
|
||||
@@ -215,6 +221,12 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
const geometry = this.renderViews[k].renderData.geometry
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where indices or positions are undefined at this point
|
||||
*/
|
||||
if (!geometry.attributes || !geometry.attributes.INDEX) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
|
||||
}
|
||||
indices.set(
|
||||
geometry.attributes.INDEX.map((val) => val + offset / 3),
|
||||
arrayOffset
|
||||
@@ -246,7 +258,7 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
indices,
|
||||
position,
|
||||
batchIndices,
|
||||
hasVertexColors ? color : null
|
||||
hasVertexColors ? color : undefined
|
||||
)
|
||||
|
||||
this.primitive = new SpeckleMesh(geometry)
|
||||
@@ -310,12 +322,12 @@ export class MeshBatch extends PrimitiveBatch {
|
||||
return geometry
|
||||
}
|
||||
|
||||
public getRenderView(index: number): NodeRenderView {
|
||||
public getRenderView(index: number): NodeRenderView | null {
|
||||
index
|
||||
Logger.warn('Deprecated! Use BatchObject')
|
||||
return null
|
||||
}
|
||||
public getMaterialAtIndex(index: number): Material {
|
||||
public getMaterialAtIndex(index: number): Material | null {
|
||||
index
|
||||
Logger.warn('Deprecated! Use BatchObject')
|
||||
return null
|
||||
|
||||
@@ -9,17 +9,16 @@ import {
|
||||
Uint16BufferAttribute,
|
||||
DynamicDrawUsage
|
||||
} from 'three'
|
||||
import { NodeRenderView } from '../..'
|
||||
import { GeometryType, BatchUpdateRange } from './Batch'
|
||||
import { DrawGroup } from './Batch'
|
||||
import { Geometry } from '../converter/Geometry'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import { type BatchUpdateRange, type DrawGroup, GeometryType } from './Batch'
|
||||
import { PrimitiveBatch } from './PrimitiveBatch'
|
||||
import { DrawRanges } from './DrawRanges'
|
||||
import Logger from 'js-logger'
|
||||
import { Geometry } from '../converter/Geometry'
|
||||
import { ObjectLayers } from '../../IViewer'
|
||||
|
||||
export class PointBatch extends PrimitiveBatch {
|
||||
protected primitive: Points
|
||||
protected primitive!: Points
|
||||
protected drawRanges: DrawRanges = new DrawRanges()
|
||||
|
||||
public get geometryType(): GeometryType {
|
||||
@@ -29,6 +28,8 @@ export class PointBatch extends PrimitiveBatch {
|
||||
if (!this.primitive.geometry.boundingBox)
|
||||
this.primitive.geometry.computeBoundingBox()
|
||||
return this.primitive.geometry.boundingBox
|
||||
? this.primitive.geometry.boundingBox
|
||||
: new Box3()
|
||||
}
|
||||
|
||||
public get minDrawCalls(): number {
|
||||
@@ -54,9 +55,9 @@ export class PointBatch extends PrimitiveBatch {
|
||||
this.renderViews = renderViews
|
||||
}
|
||||
|
||||
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
||||
const materials = ranges.map((val) => {
|
||||
return val.material
|
||||
public setDrawRanges(ranges: BatchUpdateRange[]) {
|
||||
const materials: Array<Material> = ranges.map((val: BatchUpdateRange) => {
|
||||
return val.material as Material
|
||||
})
|
||||
const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))]
|
||||
|
||||
@@ -65,18 +66,14 @@ export class PointBatch extends PrimitiveBatch {
|
||||
this.materials.push(uniqueMaterials[k])
|
||||
}
|
||||
|
||||
this.primitive.geometry.groups = this.drawRanges.integrateRanges(
|
||||
this.groups,
|
||||
this.materials,
|
||||
ranges
|
||||
)
|
||||
this.groups = this.drawRanges.integrateRanges(this.groups, this.materials, ranges)
|
||||
|
||||
let count = 0
|
||||
this.groups.forEach((value) => (count += value.count))
|
||||
if (count !== this.getCount()) {
|
||||
Logger.error(`Draw groups invalid on ${this.id}`)
|
||||
}
|
||||
this.setBatchBuffers(...ranges)
|
||||
this.setBatchBuffers(ranges)
|
||||
this.cleanMaterials()
|
||||
|
||||
if (this.drawCalls > this.minDrawCalls + 2) {
|
||||
@@ -108,10 +105,22 @@ export class PointBatch extends PrimitiveBatch {
|
||||
}
|
||||
|
||||
protected getCurrentIndexBuffer(): BufferAttribute {
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where the geometry is non-indexed. We're **explicitly** setting the index at creation time
|
||||
*/
|
||||
if (!this.primitive.geometry.index) {
|
||||
throw new Error(`Invalid index buffer for batch ${this.id}`)
|
||||
}
|
||||
return this.primitive.geometry.index
|
||||
}
|
||||
|
||||
protected getNextIndexBuffer(): BufferAttribute {
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where the geometry is non-indexed. We're **explicitly** setting the index at creation time
|
||||
*/
|
||||
if (!this.primitive.geometry.index) {
|
||||
throw new Error(`Invalid index buffer for batch ${this.id}`)
|
||||
}
|
||||
return new BufferAttribute(
|
||||
(this.primitive.geometry.index.array as Uint16Array | Uint32Array).slice(),
|
||||
this.primitive.geometry.index.itemSize
|
||||
@@ -149,8 +158,14 @@ export class PointBatch extends PrimitiveBatch {
|
||||
public buildBatch(): void {
|
||||
let attributeCount = 0
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
attributeCount +=
|
||||
this.renderViews[k].renderData.geometry.attributes.POSITION.length
|
||||
const ervee = this.renderViews[k]
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where indices or positions are undefined at this point
|
||||
*/
|
||||
if (!ervee.renderData.geometry.attributes) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
|
||||
}
|
||||
attributeCount += ervee.renderData.geometry.attributes.POSITION.length
|
||||
}
|
||||
const position = new Float64Array(attributeCount)
|
||||
const color = new Float32Array(attributeCount).fill(1)
|
||||
@@ -159,11 +174,14 @@ export class PointBatch extends PrimitiveBatch {
|
||||
let indexOffset = 0
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
const geometry = this.renderViews[k].renderData.geometry
|
||||
if (!geometry.attributes) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
|
||||
}
|
||||
position.set(geometry.attributes.POSITION, offset)
|
||||
if (geometry.attributes.COLOR) color.set(geometry.attributes.COLOR, offset)
|
||||
index.set(
|
||||
new Int32Array(geometry.attributes.POSITION.length / 3).map(
|
||||
(value, index) => index + indexOffset
|
||||
(_value, index) => index + indexOffset
|
||||
),
|
||||
indexOffset
|
||||
)
|
||||
@@ -218,7 +236,7 @@ export class PointBatch extends PrimitiveBatch {
|
||||
return geometry
|
||||
}
|
||||
|
||||
public getRenderView(index: number): NodeRenderView {
|
||||
public getRenderView(index: number): NodeRenderView | null {
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
if (
|
||||
index >= this.renderViews[k].batchStart &&
|
||||
@@ -227,8 +245,9 @@ export class PointBatch extends PrimitiveBatch {
|
||||
return this.renderViews[k]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
public getMaterialAtIndex(index: number): Material {
|
||||
public getMaterialAtIndex(index: number): Material | null {
|
||||
for (let k = 0; k < this.renderViews.length; k++) {
|
||||
if (
|
||||
index >= this.renderViews[k].batchStart &&
|
||||
@@ -248,5 +267,6 @@ export class PointBatch extends PrimitiveBatch {
|
||||
return this.materials[group.materialIndex]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,32 @@ import { Material, Object3D, BufferGeometry, BufferAttribute, Box3 } from 'three
|
||||
import { NodeRenderView } from '../..'
|
||||
import {
|
||||
AllBatchUpdateRange,
|
||||
Batch,
|
||||
BatchUpdateRange,
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
GeometryType,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch'
|
||||
import { DrawGroup } from './Batch'
|
||||
import { type DrawGroup } from './Batch'
|
||||
import Materials from '../materials/Materials'
|
||||
import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial'
|
||||
import Logger from 'js-logger'
|
||||
|
||||
export abstract class Primitive<
|
||||
TGeometry extends BufferGeometry = BufferGeometry,
|
||||
TMaterial extends Material | Material[] = Material | Material[]
|
||||
> extends Object3D {
|
||||
geometry: TGeometry
|
||||
material: TMaterial
|
||||
visible: boolean
|
||||
geometry!: TGeometry
|
||||
material!: TMaterial
|
||||
visible!: boolean
|
||||
}
|
||||
|
||||
export abstract class PrimitiveBatch implements Batch {
|
||||
public id: string
|
||||
public subtreeId: string
|
||||
public renderViews: NodeRenderView[]
|
||||
public batchMaterial: Material
|
||||
public id!: string
|
||||
public subtreeId!: string
|
||||
public renderViews!: NodeRenderView[]
|
||||
public batchMaterial!: Material
|
||||
|
||||
protected abstract primitive: Primitive
|
||||
protected gradientIndexBuffer: BufferAttribute
|
||||
protected gradientIndexBuffer!: BufferAttribute
|
||||
protected needsShuffle: boolean = false
|
||||
|
||||
abstract get geometryType(): GeometryType
|
||||
@@ -43,7 +42,17 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
}
|
||||
|
||||
public get groups(): DrawGroup[] {
|
||||
return this.primitive.geometry.groups
|
||||
/** We always write to geomtry.groups via the set accessor
|
||||
* which takes a DrawGroup[], so geometry.groups will always
|
||||
* be an array of DrawGroup.
|
||||
* Not to mention that **all our draw groupd are DrawGroup because
|
||||
* they always have a materialIndex defined** by design and convention!!!
|
||||
*/
|
||||
return this.primitive.geometry.groups as DrawGroup[]
|
||||
}
|
||||
|
||||
public set groups(value: DrawGroup[]) {
|
||||
this.primitive.geometry.groups = value
|
||||
}
|
||||
|
||||
public get renderObject(): Object3D {
|
||||
@@ -59,22 +68,21 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
}
|
||||
|
||||
public getCount(): number {
|
||||
return this.primitive.geometry.index.count
|
||||
return this.primitive.geometry.index?.count || 0
|
||||
}
|
||||
|
||||
public setBatchMaterial(material: Material): void {
|
||||
this.batchMaterial = material
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public onUpdate(deltaTime: number) {
|
||||
public onUpdate() {
|
||||
if (this.needsShuffle) {
|
||||
this.shuffleDrawGroups()
|
||||
this.needsShuffle = false
|
||||
}
|
||||
}
|
||||
|
||||
public setVisibleRange(...ranges: BatchUpdateRange[]) {
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
/** Entire batch needs to NOT be drawn */
|
||||
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
|
||||
this.primitive.geometry.setDrawRange(0, 0)
|
||||
@@ -97,17 +105,17 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
maxOffset = Math.max(maxOffset, range.offset)
|
||||
})
|
||||
|
||||
const offset = ranges.find((val) => val.offset === maxOffset)
|
||||
this.primitive.geometry.setDrawRange(
|
||||
minOffset,
|
||||
maxOffset - minOffset + ranges.find((val) => val.offset === maxOffset).count
|
||||
maxOffset - minOffset + (offset ? offset.count : 0)
|
||||
)
|
||||
this.primitive.visible = true
|
||||
}
|
||||
|
||||
public getVisibleRange(): BatchUpdateRange {
|
||||
/** Entire batch is visible */
|
||||
if (this.primitive.geometry.groups.length === 1 && this.primitive.visible)
|
||||
return AllBatchUpdateRange
|
||||
if (this.groups.length === 1 && this.primitive.visible) return AllBatchUpdateRange
|
||||
/** Entire batch is hidden */
|
||||
if (!this.primitive.visible) return NoneBatchUpdateRange
|
||||
/** Parts of the batch are visible */
|
||||
@@ -120,6 +128,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
public getOpaque(): BatchUpdateRange {
|
||||
/** If there is any transparent or hidden group return the update range up to it's offset */
|
||||
const transparentOrHiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return (
|
||||
Materials.isTransparent(this.materials[value.materialIndex]) ||
|
||||
this.materials[value.materialIndex].visible === false
|
||||
@@ -139,6 +148,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
public getDepth(): BatchUpdateRange {
|
||||
/** If there is any transparent or hidden group return the update range up to it's offset */
|
||||
const transparentOrHiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return (
|
||||
Materials.isTransparent(this.materials[value.materialIndex]) ||
|
||||
this.materials[value.materialIndex].visible === false ||
|
||||
@@ -159,10 +169,12 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
public getTransparent(): BatchUpdateRange {
|
||||
/** Look for a transparent group */
|
||||
const transparentGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return Materials.isTransparent(this.materials[value.materialIndex])
|
||||
})
|
||||
/** Look for a hidden group */
|
||||
const hiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return this.materials[value.materialIndex].visible === false
|
||||
})
|
||||
/** If there is a transparent group return it's range */
|
||||
@@ -185,6 +197,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
if (this.materials[0].stencilWrite === true) return AllBatchUpdateRange
|
||||
}
|
||||
const stencilGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return this.materials[value.materialIndex].stencilWrite === true
|
||||
})
|
||||
if (stencilGroup) {
|
||||
@@ -197,39 +210,44 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
return NoneBatchUpdateRange
|
||||
}
|
||||
|
||||
public setBatchBuffers(...range: BatchUpdateRange[]): void {
|
||||
public setBatchBuffers(ranges: BatchUpdateRange[]): void {
|
||||
let minGradientIndex = Infinity
|
||||
let maxGradientIndex = 0
|
||||
for (let k = 0; k < range.length; k++) {
|
||||
if (range[k].materialOptions) {
|
||||
if (range[k].materialOptions.rampIndex !== undefined) {
|
||||
const start = range[k].offset
|
||||
const len = range[k].offset + range[k].count
|
||||
for (let k = 0; k < ranges.length; k++) {
|
||||
const range = ranges[k]
|
||||
if (range.materialOptions) {
|
||||
if (
|
||||
range.materialOptions.rampIndex !== undefined &&
|
||||
range.materialOptions.rampWidth !== undefined
|
||||
) {
|
||||
const start = ranges[k].offset
|
||||
const len = ranges[k].offset + ranges[k].count
|
||||
/** The ramp indices specify the *begining* of each ramp color. When sampling with Nearest filter (since we don't want filtering)
|
||||
* we'll always be sampling right at the edge between texels. Most GPUs will sample consistently, but some won't and we end up with
|
||||
* a ton of artifacts. To avoid this, we are shifting the sampling indices so they're right on the center of each texel, so no inconsistent
|
||||
* sampling can occur.
|
||||
*/
|
||||
const shiftedIndex =
|
||||
range[k].materialOptions.rampIndex +
|
||||
0.5 / range[k].materialOptions.rampWidth
|
||||
const minMaxIndices = this.updateGradientIndexBufferData(
|
||||
start,
|
||||
range[k].count === Infinity
|
||||
? this.primitive.geometry.attributes['gradientIndex'].array.length
|
||||
: len,
|
||||
shiftedIndex
|
||||
)
|
||||
minGradientIndex = Math.min(minGradientIndex, minMaxIndices.minIndex)
|
||||
maxGradientIndex = Math.max(maxGradientIndex, minMaxIndices.maxIndex)
|
||||
if (range.materialOptions.rampIndex && range.materialOptions.rampWidth) {
|
||||
const shiftedIndex =
|
||||
range.materialOptions.rampIndex + 0.5 / range.materialOptions.rampWidth
|
||||
const minMaxIndices = this.updateGradientIndexBufferData(
|
||||
start,
|
||||
range.count === Infinity
|
||||
? this.primitive.geometry.attributes['gradientIndex'].array.length
|
||||
: len,
|
||||
shiftedIndex
|
||||
)
|
||||
minGradientIndex = Math.min(minGradientIndex, minMaxIndices.minIndex)
|
||||
maxGradientIndex = Math.max(maxGradientIndex, minMaxIndices.maxIndex)
|
||||
}
|
||||
}
|
||||
/** We need to update the texture here, because each batch uses it's own clone for any material we use on it
|
||||
* because otherwise three.js won't properly update our custom uniforms
|
||||
*/
|
||||
if (range[k].materialOptions.rampTexture !== undefined) {
|
||||
if (range[k].material instanceof SpeckleStandardColoredMaterial) {
|
||||
;(range[k].material as SpeckleStandardColoredMaterial).setGradientTexture(
|
||||
range[k].materialOptions.rampTexture
|
||||
if (range.materialOptions.rampTexture !== undefined) {
|
||||
if (range.material instanceof SpeckleStandardColoredMaterial) {
|
||||
;(range.material as SpeckleStandardColoredMaterial).setGradientTexture(
|
||||
range.materialOptions.rampTexture
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -242,7 +260,12 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
protected cleanMaterials() {
|
||||
const materialsInUse = [
|
||||
...Array.from(
|
||||
new Set(this.groups.map((value) => this.materials[value.materialIndex]))
|
||||
new Set(
|
||||
this.groups.map((value) => {
|
||||
if (value.materialIndex === undefined) return undefined
|
||||
return this.materials[value.materialIndex]
|
||||
})
|
||||
)
|
||||
)
|
||||
]
|
||||
let k = 0
|
||||
@@ -250,6 +273,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
if (!materialsInUse.includes(this.materials[k])) {
|
||||
this.materials.splice(k, 1)
|
||||
this.groups.forEach((value: DrawGroup) => {
|
||||
if (value.materialIndex === undefined) return
|
||||
if (value.materialIndex > k) value.materialIndex--
|
||||
})
|
||||
k = 0
|
||||
@@ -264,13 +288,15 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
protected abstract shuffleMaterialOrder(a: DrawGroup, b: DrawGroup): number
|
||||
|
||||
private shuffleDrawGroups() {
|
||||
const groups = this.primitive.geometry.groups.slice()
|
||||
const groups = this.groups.slice()
|
||||
groups.sort(this.shuffleMaterialOrder.bind(this))
|
||||
|
||||
const materialOrder = []
|
||||
const materialOrder: Array<number> = []
|
||||
groups.reduce((previousValue, currentValue) => {
|
||||
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
|
||||
previousValue.push(currentValue.materialIndex)
|
||||
if (currentValue.materialIndex !== undefined) {
|
||||
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
|
||||
previousValue.push(currentValue.materialIndex)
|
||||
}
|
||||
}
|
||||
return previousValue
|
||||
}, materialOrder)
|
||||
@@ -332,7 +358,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
materialIndex: materialGroup[0].materialIndex
|
||||
})
|
||||
}
|
||||
this.primitive.geometry.groups = []
|
||||
this.groups = []
|
||||
for (let i = 0; i < newGroups.length; i++) {
|
||||
this.primitive.geometry.addGroup(
|
||||
newGroups[i].offset,
|
||||
@@ -342,16 +368,22 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
}
|
||||
|
||||
this.primitive.geometry.setIndex(targetIBO)
|
||||
this.primitive.geometry.index.needsUpdate = true
|
||||
/** Catering to typescript
|
||||
* The line above literally makes sure the index is set. Absurd
|
||||
*/
|
||||
if (this.primitive.geometry.index) this.primitive.geometry.index.needsUpdate = true
|
||||
|
||||
const hiddenGroup = this.primitive.geometry.groups.find((value) => {
|
||||
return this.primitive.material[value.materialIndex].visible === false
|
||||
const hiddenGroup = this.groups.find((value) => {
|
||||
if (value.materialIndex === undefined) return false
|
||||
return this.materials[value.materialIndex].visible === false
|
||||
})
|
||||
if (hiddenGroup) {
|
||||
this.setVisibleRange({
|
||||
offset: 0,
|
||||
count: hiddenGroup.start
|
||||
})
|
||||
this.setVisibleRange([
|
||||
{
|
||||
offset: 0,
|
||||
count: hiddenGroup.start
|
||||
}
|
||||
])
|
||||
}
|
||||
// console.log('Final -> ', this.id, this.groups.slice())
|
||||
}
|
||||
@@ -372,7 +404,7 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
this.primitive.geometry.attributes['gradientIndex'].needsUpdate = true
|
||||
}
|
||||
|
||||
public abstract setDrawRanges(...ranges: BatchUpdateRange[])
|
||||
public abstract setDrawRanges(ranges: BatchUpdateRange[]): void
|
||||
|
||||
public resetDrawRanges(): void {
|
||||
this.primitive.visible = true
|
||||
@@ -382,29 +414,21 @@ export abstract class PrimitiveBatch implements Batch {
|
||||
}
|
||||
|
||||
public abstract buildBatch(): void
|
||||
public abstract getRenderView(index: number): NodeRenderView
|
||||
public abstract getMaterialAtIndex(index: number): Material
|
||||
public getMaterial(rv: NodeRenderView): Material {
|
||||
for (let k = 0; k < this.primitive.geometry.groups.length; k++) {
|
||||
try {
|
||||
if (
|
||||
rv.batchStart >= this.primitive.geometry.groups[k].start &&
|
||||
rv.batchEnd <=
|
||||
this.primitive.geometry.groups[k].start +
|
||||
this.primitive.geometry.groups[k].count
|
||||
) {
|
||||
return this.materials[this.primitive.geometry.groups[k].materialIndex]
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error('Failed to get material')
|
||||
public abstract getRenderView(index: number): NodeRenderView | null
|
||||
public abstract getMaterialAtIndex(index: number): Material | null
|
||||
public getMaterial(rv: NodeRenderView): Material | null {
|
||||
for (let k = 0; k < this.groups.length; k++) {
|
||||
const group = this.groups[k]
|
||||
if (rv.batchStart >= group.start && rv.batchEnd <= group.start + group.count) {
|
||||
return this.materials[group.materialIndex]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public purge(): void {
|
||||
this.renderViews.length = 0
|
||||
this.primitive.geometry.dispose()
|
||||
this.batchMaterial.dispose()
|
||||
this.primitive = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import { Box3, Material, Object3D, WebGLRenderer } from 'three'
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import {
|
||||
AllBatchUpdateRange,
|
||||
Batch,
|
||||
BatchUpdateRange,
|
||||
type Batch,
|
||||
type BatchUpdateRange,
|
||||
type DrawGroup,
|
||||
GeometryType,
|
||||
NoneBatchUpdateRange
|
||||
} from './Batch'
|
||||
|
||||
import { SpeckleText } from '../objects/SpeckleText'
|
||||
import { ObjectLayers } from '../../IViewer'
|
||||
import { DrawGroup } from './Batch'
|
||||
import Materials from '../materials/Materials'
|
||||
|
||||
export default class TextBatch implements Batch {
|
||||
public id: string
|
||||
public subtreeId: string
|
||||
public renderViews: NodeRenderView[]
|
||||
public batchMaterial: Material
|
||||
public mesh: SpeckleText
|
||||
public batchMaterial!: Material
|
||||
public mesh!: SpeckleText
|
||||
|
||||
public get bounds(): Box3 {
|
||||
return new Box3().setFromObject(this.mesh)
|
||||
@@ -68,7 +68,7 @@ export default class TextBatch implements Batch {
|
||||
public getCount(): number {
|
||||
return (
|
||||
this.mesh.textMesh.geometry.index.count +
|
||||
this.mesh.backgroundMesh?.geometry.index.count
|
||||
this.mesh.backgroundMesh?.geometry.index?.count
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,8 @@ export default class TextBatch implements Batch {
|
||||
renderer
|
||||
}
|
||||
|
||||
public setVisibleRange(...ranges: BatchUpdateRange[]) {
|
||||
public setVisibleRange(ranges: BatchUpdateRange[]) {
|
||||
ranges
|
||||
// TO DO
|
||||
}
|
||||
|
||||
@@ -116,11 +117,12 @@ export default class TextBatch implements Batch {
|
||||
return NoneBatchUpdateRange
|
||||
}
|
||||
|
||||
public setBatchBuffers(...range: BatchUpdateRange[]): void {
|
||||
public setBatchBuffers(range: BatchUpdateRange[]): void {
|
||||
range
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
||||
public setDrawRanges(ranges: BatchUpdateRange[]) {
|
||||
this.mesh.textMesh.material = ranges[0].material
|
||||
if (ranges[0].materialOptions && ranges[0].materialOptions.rampIndexColor) {
|
||||
this.mesh.textMesh.material.color.copy(ranges[0].materialOptions.rampIndexColor)
|
||||
@@ -130,11 +132,15 @@ export default class TextBatch implements Batch {
|
||||
public resetDrawRanges() {
|
||||
this.mesh.textMesh.material = this.batchMaterial
|
||||
this.mesh.textMesh.visible = true
|
||||
// this.geometry.clearGroups()
|
||||
// this.geometry.setDrawRange(0, Infinity)
|
||||
}
|
||||
|
||||
public async buildBatch() {
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where there is no metadata
|
||||
*/
|
||||
if (!this.renderViews[0].renderData.geometry.metaData) {
|
||||
throw new Error(`Cannot build batch ${this.id}. Metadata`)
|
||||
}
|
||||
this.mesh = new SpeckleText(this.id, ObjectLayers.STREAM_CONTENT_TEXT)
|
||||
this.mesh.matrixAutoUpdate = false
|
||||
await this.mesh.update(
|
||||
@@ -142,7 +148,8 @@ export default class TextBatch implements Batch {
|
||||
this.renderViews[0].renderData.geometry.metaData
|
||||
)
|
||||
)
|
||||
this.mesh.matrix.copy(this.renderViews[0].renderData.geometry.bakeTransform)
|
||||
if (this.renderViews[0].renderData.geometry.bakeTransform)
|
||||
this.mesh.matrix.copy(this.renderViews[0].renderData.geometry.bakeTransform)
|
||||
this.renderViews[0].setBatchData(
|
||||
this.id,
|
||||
0,
|
||||
@@ -152,14 +159,17 @@ export default class TextBatch implements Batch {
|
||||
}
|
||||
|
||||
public getRenderView(index: number): NodeRenderView {
|
||||
index
|
||||
return this.renderViews[0]
|
||||
}
|
||||
|
||||
public getMaterialAtIndex(index: number): Material {
|
||||
index
|
||||
return this.batchMaterial
|
||||
}
|
||||
|
||||
public getMaterial(rv: NodeRenderView): Material {
|
||||
rv
|
||||
return this.batchMaterial
|
||||
}
|
||||
|
||||
@@ -167,6 +177,5 @@ export default class TextBatch implements Batch {
|
||||
this.renderViews.length = 0
|
||||
this.batchMaterial.dispose()
|
||||
this.mesh.geometry.dispose()
|
||||
this.mesh = null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user