* Starting to tackle WBX-397 by taking a different approach on computing draw ranges

* WIP

* Range computation is more or less done. Material indices still need a more robust approach

* Range material indices figured out mostly by trating them as edges in a graph. Also some profiling to max out pergf

* Added forward and backwards material index edges in order to be able to directly look them up rather than searching for them in an array

* Extended the new draw range integration approach to instanced batches

* Added vitest and wrote a first test for the DrawRange integration approach. Fixed some cicular dependencie which were messing up vitest runtime.

* A bunch of more tests

* Removed unused dep

* ci(viewer): test the viewer in CircleCI

* Include test-viewer as a dependency of later jobs

* Small updates

* Removed old range integration from all three batches

* Working on making the range update integration work on an array at a time

* Update range integration now works on entire arrays at a time. Almost twice as fast this way. Updates tests

* DrawRanges now also flattens the computed draw ranges, so we got rid of the old ugly way of flattening the ranges. Update MeshBatch, PointBatch and InstancedMeshBatch

* More adjustment to point batch

* Implemented a common PrimitiveBatch which can be used for any typical primitive based rendering (triangles, lines, points) as abstract. Our Mesh and Point batch will use most of the common implementation. Updated MeshBatch to extend PrimitiveBatch

* Updated PointBatch to extend PrimitiveBatch

* Tried making the instaned mesh batch extend the primitive batch but there was no point, they too different to make sense. Got rid of the pointless circular dependency between the instaned batch and mesh

* fixed viewer build

* Removed unused code and execution time measurements

---------

Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com>
Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
This commit is contained in:
Alexandru Popovici 2024-04-02 15:48:01 +03:00 коммит произвёл GitHub
Родитель 752a9197c2
Коммит 9c481a20f5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
31 изменённых файлов: 1823 добавлений и 1745 удалений

Просмотреть файл

@ -25,6 +25,9 @@ workflows:
- test-frontend-2:
filters: *filters-allow-all
- test-viewer:
filters: *filters-allow-all
- test-ui-components:
filters: *filters-allow-all
@ -54,6 +57,7 @@ workflows:
- pre-commit
- deployment-testing-approval
- test-frontend-2
- test-viewer
- test-server
- docker-build-server
- docker-build-frontend
@ -72,6 +76,7 @@ workflows:
- pre-commit
- deployment-testing-approval
- test-frontend-2
- test-viewer
- test-server
- docker-build-server
- docker-build-frontend
@ -163,6 +168,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-frontend:
@ -174,6 +180,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-frontend-2:
@ -185,6 +192,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-webhooks:
@ -196,6 +204,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-file-imports:
@ -207,6 +216,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-previews:
@ -218,6 +228,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-test-container:
@ -229,6 +240,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-monitor-container:
@ -240,6 +252,7 @@ workflows:
- pre-commit
- publish-approval
- test-frontend-2
- test-viewer
- test-server
- docker-publish-docker-compose-ingress:
@ -290,6 +303,7 @@ workflows:
- test-server
- test-ui-components
- test-frontend-2
- test-viewer
jobs:
get-version:
@ -496,6 +510,45 @@ jobs:
command: yarn lint
working_directory: 'packages/frontend-2'
test-viewer:
docker: &docker-node-browsers-image
- image: cimg/node:18.19.0-browsers
resource_class: large
steps:
- checkout
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-server-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn
- run:
name: Install Dependencies v2 (.node files missing bug)
command: yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-server-{{ checksum "yarn.lock" }}
paths:
- .yarn/cache
- .yarn/unplugged
- run:
name: Build public packages
command: yarn build:public
- run:
name: Lint everything
command: yarn lint
working_directory: 'packages/viewer'
- run:
name: Run tests
command: yarn test
working_directory: 'packages/viewer'
test-ui-components:
docker: *docker-node-browsers-image
resource_class: xlarge

Просмотреть файл

@ -5,7 +5,10 @@ import {
SelectionEvent,
ViewerEvent,
DebugViewer,
Viewer
Viewer,
Batch,
DrawRanges,
SpeckleBasicMaterial
} from '@speckle/viewer'
import './style.css'
@ -20,6 +23,10 @@ import {
} from '@speckle/viewer'
import { SectionTool } from '@speckle/viewer'
import { SectionOutlines } from '@speckle/viewer'
import { BoxSelection } from './Extensions/BoxSelection'
import { GeometryType } from '@speckle/viewer'
import { SpeckleStandardMaterial } from '@speckle/viewer'
import { Color, FrontSide } from 'three'
const createViewer = async (containerName: string, stream: string) => {
const container = document.querySelector<HTMLElement>(containerName)
@ -51,7 +58,7 @@ const createViewer = async (containerName: string, stream: string) => {
const filtering = viewer.createExtension(FilteringExtension)
const explode = viewer.createExtension(ExplodeExtension)
const diff = viewer.createExtension(DiffExtension)
// const boxSelect = viewer.createExtension(BoxSelection)
const boxSelect = viewer.createExtension(BoxSelection)
// const rotateCamera = viewer.createExtension(RotateCamera)
cameraController // use it
selection // use it
@ -91,6 +98,97 @@ const createViewer = async (containerName: string, stream: string) => {
Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize)
Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin)
sandbox.refresh()
const meshBatch = viewer
.getRenderer()
.batcher.getBatches(undefined, GeometryType.MESH)
.find((batch: Batch) => batch.renderViews.length > 2)
// const geom = meshBatch.mesh.geometry
// geom.groups.length = 0
// geom.addGroup(0, 216, 0)
// geom.addGroup(216, 1323, 0)
// geom.addGroup(1539, 540, 0)
// geom.addGroup(2079, 32268, 0)
// const material = new SpeckleStandardMaterial(
// {
// color: new Color('#00ff00'),
// emissive: 0x0,
// roughness: 1,
// metalness: 0,
// opacity: 1,
// side: FrontSide
// },
// ['USE_RTE']
// )
// meshBatch.setDrawRanges(
// {
// offset: 36,
// count: 36,
// material
// }
// {
// offset: 180,
// count: 1395,
// material
// },
// {
// offset: 1581,
// count: 32766,
// material
// }
// )
// const material = new SpeckleStandardMaterial(
// {
// color: new Color('#00ff00'),
// emissive: 0x0,
// roughness: 1,
// metalness: 0,
// opacity: 1,
// side: FrontSide
// },
// ['USE_RTE']
// )
// meshBatch.setDrawRanges(
// {
// offset: 9018,
// count: 36,
// material
// },
// {
// offset: 13878,
// count: 36,
// material
// }
// )
// const material0 = new SpeckleBasicMaterial({ color: 0xff0000 })
// const material1 = new SpeckleBasicMaterial({ color: 0x00ff00 })
// let groups = [
// {
// start: 0,
// count: 1350,
// materialIndex: 0
// }
// ]
// const drawRange = new DrawRanges()
// groups = drawRange.integrateRanges(
// groups,
// [material0, material1],
// [
// {
// offset: 0,
// count: 258,
// material: material1
// },
// {
// offset: 258,
// count: 1032,
// material: material1
// }
// ]
// )
})
viewer.on(ViewerEvent.UnloadComplete, () => {
@ -249,7 +347,7 @@ const getStream = () => {
// 'https://latest.speckle.dev/streams/f92e060177/commits/038a587267'
// 'https://latest.speckle.dev/streams/3f895e614f/commits/8a3e424997'
// 'https://latest.speckle.dev/streams/f92e060177/commits/f51ee777d5'
// 'https://latest.speckle.dev/streams/f92e060177/commits/bbd821e3a1'
'https://latest.speckle.dev/streams/f92e060177/commits/bbd821e3a1'
// Big curves
// 'https://latest.speckle.dev/streams/c1faab5c62/commits/49dad07ae2'
// 'https://speckle.xyz/streams/7ce9010d71/commits/afda4ffdf8'
@ -296,6 +394,7 @@ const getStream = () => {
// 'https://speckle.xyz/streams/88307505eb/objects/a232d760059046b81ff97e6c4530c985'
// Airport
// 'https://latest.speckle.dev/streams/92b620fb17/commits/dfb9ca025d'
// 'https://latest.speckle.dev/streams/92b620fb17/objects/cf8838025d9963b342b09da8de0f8b6b'
// 'Blocks with elements
// 'https://latest.speckle.dev/streams/e258b0e8db/commits/00e165cc1c'
// 'https://latest.speckle.dev/streams/e258b0e8db/commits/e48cf53add'
@ -372,7 +471,18 @@ const getStream = () => {
// Rebar
// 'https://speckle.xyz/streams/b4086833f8/commits/94df4c6d16?overlay=c5b9c260ea,e3dc287d61,eaedd7d0a5,7f126ce0dd,02fee34ce3,9bda31611f,110282c4db,533c311e29,bf6814d779,1ba52affcf,cc4e75125e,3fd628e4e3'
'http://127.0.0.1:3000/streams/30b75f0dea/objects/db765ed44ae10176c0bf8ba60d1ce67d'
// Nice towers
// 'https://latest.speckle.dev/streams/f4efe4bd7f/objects/5083dffc2ce54ce64c1fc4fab48ca877'
// 'http://127.0.0.1:3000/streams/30b75f0dea/objects/db765ed44ae10176c0bf8ba60d1ce67d'
// 'https://speckle.xyz/streams/7b253e5c4c/commits/025fcbb9cf'
// BIG railway
// 'https://latest.speckle.dev/streams/a64b432b34/commits/cf7725e404'
// 'https://latest.speckle.dev/streams/a64b432b34/objects/1806cb8082a4202b01d97601b6e19af8'
// 'https://latest.speckle.dev/streams/a64b432b34/objects/a7ab2388948594e89f838f3026b89839'
// 'https://latest.speckle.dev/streams/a64b432b34/commits/99d809460a'
// Bunch a doors
// 'https://latest.speckle.dev/streams/a64b432b34/commits/c184ba7d88'
)
}

Просмотреть файл

@ -37,7 +37,10 @@
"build:dev": "rollup --config",
"dev": "rollup --config --watch",
"prepack": "yarn build",
"lint": "eslint . --ext .js,.ts"
"lint": "eslint . --ext .js,.ts",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run"
},
"author": "AEC Systems",
"license": "Apache-2.0",
@ -74,16 +77,19 @@
"@types/three": "^0.136.0",
"@typescript-eslint/eslint-plugin": "^5.39.0",
"@typescript-eslint/parser": "^5.39.0",
"@vitest/ui": "^1.4.0",
"core-js": "^3.21.1",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"jsdom": "^24.0.0",
"prettier": "^2.5.1",
"regenerator-runtime": "^0.13.7",
"rollup": "^2.70.1",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2",
"typescript": "^4.5.4"
"typescript": "^4.5.4",
"vitest": "^1.4.0"
},
"gitHead": "5627e490f9a3ecadf19cc4686ad15f344d9ad2d3"
}

Просмотреть файл

@ -30,7 +30,8 @@ const config = {
typescript2({
tsconfigOverride: {
sourceMap: sourcemap
}
},
tsconfig: './tsconfig.build.json'
}),
babel({
extensions: [...DEFAULT_EXTENSIONS, '.ts', '.tsx'],

Просмотреть файл

@ -125,6 +125,17 @@ export enum UpdateFlags {
CLIPPING_PLANES = 0b100
}
export interface MaterialOptions {
stencilOutlines?: StencilOutlineType
pointSize?: number
depthWrite?: number
}
export enum StencilOutlineType {
NONE,
OVERLAY,
OUTLINE_ONLY
}
export interface IViewer {
get input(): Input
get Utils(): Utils

Просмотреть файл

@ -64,7 +64,7 @@ import { LegacyViewer } from './modules/LegacyViewer'
import { SpeckleType } from './modules/loaders/GeometryConverter'
import Input, { InputEvent } from './modules/input/Input'
import { GeometryType } from './modules/batching/Batch'
import MeshBatch from './modules/batching/MeshBatch'
import { MeshBatch } from './modules/batching/MeshBatch'
import SpeckleStandardMaterial from './modules/materials/SpeckleStandardMaterial'
import SpeckleTextMaterial from './modules/materials/SpeckleTextMaterial'
import { SpeckleText } from './modules/objects/SpeckleText'

Просмотреть файл

@ -1,40 +1,42 @@
import { MathUtils } from 'three'
import {
Viewer,
BatchObject,
PropertyInfo,
DataTree,
WorldTree,
QueryResult,
SunLightConfiguration,
SpeckleView,
CanonicalView,
InlineView,
VisualDiffMode,
DiffResult,
MeasurementOptions,
CameraController,
DiffExtension,
ExplodeExtension,
MeasurementsExtension,
SectionOutlines,
SectionTool,
SelectionExtension,
TreeNode,
SpeckleLoader,
DefaultViewerParams,
ViewerParams,
SelectionEvent,
IViewer
} from '..'
import { FilteringExtension, FilteringState } from './extensions/FilteringExtension'
import { ICameraProvider, PolarView } from './extensions/core-extensions/Providers'
import {
CanonicalView,
ICameraProvider,
InlineView,
PolarView
} from './extensions/core-extensions/Providers'
import { SpeckleType } from './loaders/GeometryConverter'
import { Queries } from './queries/Queries'
import { Query, QueryArgsResultMap } from './queries/Query'
import { DataTreeBuilder } from './tree/DataTree'
import { SelectionExtensionOptions } from './extensions/SelectionExtension'
import { StencilOutlineType } from './materials/Materials'
import { Query, QueryArgsResultMap, QueryResult } from './queries/Query'
import { DataTree, DataTreeBuilder } from './tree/DataTree'
import {
SelectionExtension,
SelectionExtensionOptions
} from './extensions/SelectionExtension'
import { StencilOutlineType } from '../IViewer'
import {
DefaultViewerParams,
IViewer,
SelectionEvent,
SpeckleView,
SunLightConfiguration,
ViewerParams
} from '../IViewer'
import { TreeNode, WorldTree } from './tree/WorldTree'
import { Viewer } from './Viewer'
import { CameraController } from './extensions/core-extensions/CameraController'
import { SectionTool } from './extensions/SectionTool'
import { SectionOutlines } from './extensions/SectionOutlines'
import {
MeasurementOptions,
MeasurementsExtension
} from './extensions/measurements/MeasurementsExtension'
import { ExplodeExtension } from './extensions/ExplodeExtension'
import { DiffExtension, DiffResult, VisualDiffMode } from './extensions/DiffExtension'
import { PropertyInfo } from './filtering/PropertyManager'
import { BatchObject } from './batching/BatchObject'
import { SpeckleLoader } from './loaders/Speckle/SpeckleLoader'
class LegacySelectionExtension extends SelectionExtension {
/** FE2 'manually' selects objects pon it's own, so we're disabling the extension's event handler

Просмотреть файл

@ -39,7 +39,6 @@ import {
ViewerEvent
} from '../IViewer'
import { DefaultPipelineOptions, Pipeline, PipelineOptions } from './pipeline/Pipeline'
import MeshBatch from './batching/MeshBatch'
import { Shadowcatcher } from './Shadowcatcher'
import SpeckleMesh from './objects/SpeckleMesh'
import { ExtendedIntersection } from './objects/SpeckleRaycaster'
@ -51,15 +50,16 @@ import {
import Materials, {
RenderMaterial,
DisplayStyle,
MaterialOptions,
FilterMaterial
} from './materials/Materials'
import { MaterialOptions } from './materials/MaterialOptions'
import { SpeckleMaterial } from './materials/SpeckleMaterial'
import { SpeckleWebGLRenderer } from './objects/SpeckleWebGLRenderer'
import { SpeckleTypeAllRenderables } from './loaders/GeometryConverter'
import SpeckleInstancedMesh from './objects/SpeckleInstancedMesh'
import { BaseSpecklePass } from './pipeline/SpecklePass'
import { CameraController } from './extensions/core-extensions/CameraController'
import { MeshBatch } from './batching/MeshBatch'
export class RenderingStats {
private renderTimeAcc = 0

Просмотреть файл

@ -1,7 +1,6 @@
import { Box3, Material, Object3D, WebGLRenderer } from 'three'
import { FilterMaterialOptions } from '../materials/Materials'
import { NodeRenderView } from '../tree/NodeRenderView'
import { DrawGroup } from './InstancedMeshBatch'
export enum GeometryType {
MESH,
@ -26,6 +25,8 @@ export interface Batch {
get materials(): Material[]
get groups(): DrawGroup[]
get triCount(): number
get pointCount(): number
get lineCount(): number
get vertCount(): number
getCount(): number
@ -44,7 +45,7 @@ export interface Batch {
getStencil(): BatchUpdateRange
getDepth(): BatchUpdateRange
onUpdate(deltaTime: number)
onRender(renderer: WebGLRenderer)
onRender?(renderer: WebGLRenderer)
purge()
}
@ -64,3 +65,16 @@ 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

Просмотреть файл

@ -1,10 +1,8 @@
import { MathUtils } from 'three'
import MeshBatch from './MeshBatch'
import LineBatch from './LineBatch'
import Materials, { FilterMaterialType } from '../materials/Materials'
import { NodeRenderView } from '../tree/NodeRenderView'
import { Batch, BatchUpdateRange, GeometryType, NoneBatchUpdateRange } from './Batch'
import PointBatch from './PointBatch'
import { Material, WebGLRenderer } from 'three'
import Logger from 'js-logger'
import { AsyncPause } from '../World'
@ -13,8 +11,10 @@ import TextBatch from './TextBatch'
import SpeckleMesh, { TransformStorage } from '../objects/SpeckleMesh'
import { SpeckleType } from '../loaders/GeometryConverter'
import { TreeNode, WorldTree } from '../..'
import InstancedMeshBatch from './InstancedMeshBatch'
import { InstancedMeshBatch } from './InstancedMeshBatch'
import { Geometry } from '../converter/Geometry'
import { MeshBatch } from './MeshBatch'
import { PointBatch } from './PointBatch'
export default class Batcher {
private maxHardwareUniformCount = 0
@ -206,13 +206,6 @@ export default class Batcher {
average / materialHashes.length
}`
)
Logger.warn('Buffer setup -> ', MeshBatch.bufferSetup)
Logger.warn('Array work -> ', MeshBatch.arrayWork)
Logger.warn('Object BVH -> ', MeshBatch.objectBvh)
Logger.warn('Compute normals -> ', MeshBatch.computeNormals)
Logger.warn('Compute box and sphere -> ', MeshBatch.computeBoxAndSphere)
Logger.warn('Compute RTE -> ', MeshBatch.computeRTE)
Logger.warn('Batch BVH -> ', MeshBatch.batchBVH)
Logger.warn('Total instanced -> ', totalInstanced)
Logger.warn('Instance gathering -> ', instancedGathering)
Logger.warn('De-instancing -> ', deInstancing)
@ -372,7 +365,7 @@ export default class Batcher {
public render(renderer: WebGLRenderer) {
for (const batchId in this.batches) {
this.batches[batchId].onRender(renderer)
if (this.batches[batchId].onRender) this.batches[batchId].onRender(renderer)
}
}

Просмотреть файл

@ -0,0 +1,79 @@
import { Material } from 'three'
import { BatchUpdateRange } from './Batch'
import { DrawGroup } from './Batch'
export class DrawRanges {
public integrateRanges(
groups: Array<DrawGroup>,
materials: Array<Material>,
ranges: BatchUpdateRange[]
): Array<DrawGroup> {
let _flatRanges: Array<number> = []
groups.sort((a, b) => a.start - b.start)
ranges.sort((a, b) => a.offset - b.offset)
const edgesForward = {}
const edgesBackwards = {}
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
edgesBackwards[groupBackwards.start + groupBackwards.count] =
groupBackwards.materialIndex
}
_flatRanges = groups.map((group: DrawGroup) => {
return group.start + group.count
})
_flatRanges.unshift(0)
for (let k = 0; k < ranges.length; k++) {
const range = ranges[k]
const r0 = range.offset
const r0Index = _flatRanges.findIndex((value) => value > r0)
const next = _flatRanges[r0Index]
_flatRanges.splice(r0Index, 0, r0)
const r1 = range.offset + range.count
const r1Index = _flatRanges.findIndex((value) => value > r1)
_flatRanges.splice(r1Index, 0, r1)
_flatRanges = _flatRanges.filter((value) => {
return !(value > r0 && value < r1)
})
_flatRanges = [...new Set(_flatRanges)]
const materialIndex = materials.indexOf(range.material)
edgesForward[r0] = materialIndex
edgesForward[r1] =
edgesForward[next] !== undefined ? edgesForward[next] : edgesBackwards[next]
}
const drawRanges = []
let groupStart = -1
let count = 0
for (let k = 0; k < _flatRanges.length - 1; k++) {
const start = _flatRanges[k]
const end = _flatRanges[k + 1]
count += end - start
const materialIndex = edgesForward[start]
const lastGroup = k === _flatRanges.length - 2
if (edgesForward[_flatRanges[k + 1]] === materialIndex || lastGroup) {
if (groupStart === -1) {
groupStart = start
}
if (!lastGroup) continue
}
drawRanges.push({
start: groupStart === -1 ? start : groupStart,
count,
materialIndex
})
groupStart = -1
count = 0
}
return drawRanges
}
}

Просмотреть файл

@ -17,6 +17,7 @@ import {
Batch,
BatchUpdateRange,
GeometryType,
INSTANCE_TRANSFORM_BUFFER_STRIDE,
NoneBatchUpdateRange
} from './Batch'
import SpeckleInstancedMesh from '../objects/SpeckleInstancedMesh'
@ -28,22 +29,18 @@ import {
import { InstancedBatchObject } from './InstancedBatchObject'
import Logger from 'js-logger'
import Materials from '../materials/Materials'
import { DrawRanges } from './DrawRanges'
import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial'
import { DrawGroup } from './Batch'
export interface DrawGroup {
start: number
count: number
materialIndex?: number
}
export default class InstancedMeshBatch implements Batch {
public static readonly INSTANCE_TRANSFORM_BUFFER_STRIDE = 16
public static readonly INSTANCE_GRADIENT_BUFFER_STRIDE = 1
export class InstancedMeshBatch implements Batch {
public id: string
public subtreeId: string
public renderViews: NodeRenderView[]
private geometry: BufferGeometry
public batchMaterial: Material
public mesh: SpeckleInstancedMesh
private drawRanges: DrawRanges = new DrawRanges()
private instanceTransformBuffer0: Float32Array = null
private instanceTransformBuffer1: Float32Array = null
@ -51,7 +48,6 @@ export default class InstancedMeshBatch implements Batch {
private instanceGradientBuffer: Float32Array = null
private needsShuffle = false
private needsFlatten = false
public get bounds(): Box3 {
return this.mesh.TAS.getBoundingBox(new Box3())
@ -78,10 +74,12 @@ export default class InstancedMeshBatch implements Batch {
return this.geometry.attributes.position.count * this.renderViews.length
}
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
this.id = id
this.subtreeId = subtreeId
this.renderViews = renderViews
public get pointCount(): number {
return 0
}
public get lineCount(): number {
return 0
}
public get geometryType(): GeometryType {
@ -104,16 +102,18 @@ export default class InstancedMeshBatch implements Batch {
return this.mesh.groups
}
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
this.id = id
this.subtreeId = subtreeId
this.renderViews = renderViews
}
public setBatchMaterial(material: Material) {
this.batchMaterial = material
}
public onUpdate(deltaTime: number) {
deltaTime
if (this.needsFlatten) {
this.flattenDrawGroups()
this.needsFlatten = false
}
if (this.needsShuffle) {
this.shuffleDrawGroups()
this.needsShuffle = false
@ -261,94 +261,14 @@ export default class InstancedMeshBatch implements Batch {
0.5 / range[k].materialOptions.rampWidth
this.updateGradientIndexBufferData(start / 16, shiftedIndex)
}
}
}
}
private integrateUpdateRange(range: BatchUpdateRange) {
const materialIndex = this.materials.indexOf(range.material)
const collidingGroup = this.getDrawRangeCollision(range)
if (collidingGroup) {
collidingGroup.materialIndex = this.materials.indexOf(range.material)
} else {
const includingGroup = this.geDrawRangeInclusion(range)
if (includingGroup) {
if (includingGroup.materialIndex === materialIndex) return
this.geometry.groups.splice(this.geometry.groups.indexOf(includingGroup), 1)
if (includingGroup.start === range.offset) {
this.geometry.addGroup(range.offset, range.count, materialIndex)
this.geometry.addGroup(
range.offset + range.count,
includingGroup.count - range.count,
includingGroup.materialIndex
)
} else if (
range.offset + range.count ===
includingGroup.start + includingGroup.count
) {
this.geometry.addGroup(
includingGroup.start,
includingGroup.count - range.count,
includingGroup.materialIndex
)
this.geometry.addGroup(range.offset, range.count, materialIndex)
} else {
this.geometry.addGroup(
includingGroup.start,
range.offset - includingGroup.start,
includingGroup.materialIndex
)
this.geometry.addGroup(range.offset, range.count, materialIndex)
this.geometry.addGroup(
range.offset + range.count,
includingGroup.count - (range.count + range.offset - includingGroup.start),
includingGroup.materialIndex
)
}
} else {
const engulfedGroups = this.getDrawRangeEngulfing(range)
if (engulfedGroups) {
for (let k = 0; k < engulfedGroups.length; k++) {
this.geometry.groups.splice(this.groups.indexOf(engulfedGroups[k]), 1)
}
this.integrateUpdateRange(range)
} else {
const intersectedGroupLeft = this.getDrawRangeIntersectionLeft(range)
if (
intersectedGroupLeft &&
intersectedGroupLeft.materialIndex !== materialIndex
) {
this.geometry.groups.splice(
this.geometry.groups.indexOf(intersectedGroupLeft),
1
/** 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
)
this.geometry.addGroup(range.offset, range.count, materialIndex)
this.geometry.addGroup(
range.offset + range.count,
intersectedGroupLeft.start +
intersectedGroupLeft.count -
(range.offset + range.count),
intersectedGroupLeft.materialIndex
)
} else {
const intersectedGroupRight = this.getDrawRangeIntersectionRight(range)
if (
intersectedGroupRight &&
intersectedGroupRight.materialIndex !== materialIndex
) {
this.geometry.groups.splice(
this.geometry.groups.indexOf(intersectedGroupRight),
1
)
this.geometry.addGroup(
intersectedGroupRight.start,
range.offset - intersectedGroupRight.start,
intersectedGroupRight.materialIndex
)
this.geometry.addGroup(range.offset, range.count, materialIndex)
} else {
this.geometry.addGroup(range.offset, range.count, materialIndex)
}
}
}
}
@ -372,13 +292,11 @@ export default class InstancedMeshBatch implements Batch {
this.materials.push(uniqueMaterials[k])
}
const sortedRanges = ranges.sort((a, b) => {
return a.offset - b.offset
})
for (let i = 0; i < sortedRanges.length; i++) {
this.integrateUpdateRange(sortedRanges[i])
}
this.mesh.groups = this.drawRanges.integrateRanges(
this.groups,
this.materials,
ranges
)
let count = 0
this.groups.forEach((value) => (count += value.count))
@ -386,93 +304,18 @@ export default class InstancedMeshBatch implements Batch {
Logger.error(`Draw groups invalid on ${this.id}`)
}
this.setBatchBuffers(...ranges)
this.needsFlatten = true
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) {
this.needsShuffle = true
} else
this.mesh.updateDrawGroups(
this.getCurrentTransformBuffer(),
this.getCurrentGradientBuffer()
)
}
private getDrawRangeCollision(range: BatchUpdateRange): DrawGroup {
if (this.groups.length > 0) {
for (let i = 0; i < this.groups.length; i++) {
if (
range.offset === this.groups[i].start &&
range.count === this.groups[i].count
) {
return this.groups[i]
}
}
return null
}
return null
}
private geDrawRangeInclusion(range: BatchUpdateRange): DrawGroup {
range
if (this.groups.length > 0) {
for (let i = 0; i < this.groups.length; i++) {
if (
range.offset >= this.groups[i].start &&
range.offset + range.count <= this.groups[i].start + this.groups[i].count
) {
return this.groups[i]
}
}
return null
}
return null
}
private getDrawRangeEngulfing(range: BatchUpdateRange): DrawGroup[] | null {
const groups = []
if (this.geometry.groups.length > 0) {
for (let i = 0; i < this.geometry.groups.length; i++) {
if (
range.offset <= this.geometry.groups[i].start &&
range.offset + range.count >=
this.geometry.groups[i].start + this.geometry.groups[i].count
) {
groups.push(this.geometry.groups[i])
}
}
return groups.length ? groups : null
}
return null
}
private getDrawRangeIntersectionLeft(range: BatchUpdateRange): DrawGroup {
if (this.geometry.groups.length > 0) {
for (let i = 0; i < this.geometry.groups.length; i++) {
if (
range.offset < this.geometry.groups[i].start &&
range.offset + range.count > this.geometry.groups[i].start &&
range.offset + range.count <
this.geometry.groups[i].start + this.geometry.groups[i].count
) {
return this.geometry.groups[i]
}
}
return null
}
return null
}
private getDrawRangeIntersectionRight(range: BatchUpdateRange): DrawGroup {
if (this.geometry.groups.length > 0) {
for (let i = 0; i < this.geometry.groups.length; i++) {
if (
range.offset > this.geometry.groups[i].start &&
this.geometry.groups[i].start + this.geometry.groups[i].count >
range.offset &&
range.offset + range.count >
this.geometry.groups[i].start + this.geometry.groups[i].count
) {
return this.geometry.groups[i]
}
}
return null
}
return null
}
private flattenDrawGroups() {
private cleanMaterials() {
const materialsInUse = [
...Array.from(
new Set(this.groups.map((value) => this.materials[value.materialIndex]))
@ -490,65 +333,6 @@ export default class InstancedMeshBatch implements Batch {
}
k++
}
const materialOrder = []
this.groups.reduce((previousValue, currentValue) => {
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
previousValue.push(currentValue.materialIndex)
}
return previousValue
}, materialOrder)
const grouped = []
for (let k = 0; k < materialOrder.length; k++) {
grouped.push(
this.groups.filter((val) => {
return val.materialIndex === materialOrder[k]
})
)
}
this.groups.length = 0
for (let matIndex = 0; matIndex < grouped.length; matIndex++) {
const matGroup = grouped[matIndex].sort((a, b) => {
return a.start - b.start
})
for (let k = 0; k < matGroup.length; ) {
let offset = matGroup[k].start
let count = matGroup[k].count
let runningCount = matGroup[k].count
let n = k + 1
for (; n < matGroup.length; n++) {
if (offset + count === matGroup[n].start) {
offset = matGroup[n].start
count = matGroup[n].count
runningCount += matGroup[n].count
} else {
const group = {
start: matGroup[k].start,
count: runningCount,
materialIndex: matGroup[k].materialIndex
}
this.groups.push(group)
break
}
}
if (n === matGroup.length) {
const group = {
start: matGroup[k].start,
count: runningCount,
materialIndex: matGroup[k].materialIndex
}
this.groups.push(group)
}
k = n
}
}
/** We shuffle only when above a certain fragmentation threshold. We don't want to be shuffling every single time */
if (this.drawCalls > this.maxDrawCalls) {
this.needsShuffle = true
} else
this.mesh.updateDrawGroups(
this.getCurrentTransformBuffer(),
this.getCurrentGradientBuffer()
)
}
private shuffleDrawGroups() {
@ -609,12 +393,12 @@ export default class InstancedMeshBatch implements Batch {
let subArray = sourceTransformBuffer.subarray(start, start + count)
targetTransformBuffer.set(subArray, targetBufferOffset)
subArray = sourceGradientBuffer.subarray(
start / InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE,
(start + count) / InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
start / INSTANCE_TRANSFORM_BUFFER_STRIDE,
(start + count) / INSTANCE_TRANSFORM_BUFFER_STRIDE
)
targetGradientBuffer.set(
subArray,
targetBufferOffset / InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
targetBufferOffset / INSTANCE_TRANSFORM_BUFFER_STRIDE
)
let rvElemCount = 0
for (let m = 0; m < scratchRvs.length; m++) {
@ -669,8 +453,7 @@ export default class InstancedMeshBatch implements Batch {
this.materials.length = 0
this.groups.push({
start: 0,
count:
this.renderViews.length * InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE,
count: this.renderViews.length * INSTANCE_TRANSFORM_BUFFER_STRIDE,
materialIndex: 0
})
this.materials.push(this.batchMaterial)
@ -701,22 +484,22 @@ export default class InstancedMeshBatch implements Batch {
const batchObjects = []
let instanceBVH = null
this.instanceTransformBuffer0 = new Float32Array(
this.renderViews.length * InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
this.renderViews.length * INSTANCE_TRANSFORM_BUFFER_STRIDE
)
this.instanceTransformBuffer1 = new Float32Array(
this.renderViews.length * InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
this.renderViews.length * INSTANCE_TRANSFORM_BUFFER_STRIDE
)
const targetInstanceTransformBuffer = this.getCurrentTransformBuffer()
for (let k = 0; k < this.renderViews.length; k++) {
this.renderViews[k].renderData.geometry.transform.toArray(
targetInstanceTransformBuffer,
k * InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
k * INSTANCE_TRANSFORM_BUFFER_STRIDE
)
this.renderViews[k].setBatchData(
this.id,
k * InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE,
InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
k * INSTANCE_TRANSFORM_BUFFER_STRIDE,
INSTANCE_TRANSFORM_BUFFER_STRIDE
)
const batchObject = new InstancedBatchObject(this.renderViews[k], k)
if (!instanceBVH) {
@ -772,8 +555,7 @@ export default class InstancedMeshBatch implements Batch {
this.groups.push({
start: 0,
count:
this.renderViews.length * InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE,
count: this.renderViews.length * INSTANCE_TRANSFORM_BUFFER_STRIDE,
materialIndex: 0
})
this.mesh.updateDrawGroups(
@ -784,13 +566,13 @@ export default class InstancedMeshBatch implements Batch {
public getRenderView(index: number): NodeRenderView {
index
Logger.warn('Deprecated! Do not call this anymore')
Logger.warn('Deprecated! Use InstancedBatchObject')
return null
}
public getMaterialAtIndex(index: number): Material {
index
Logger.warn('Deprecated! Do not call this anymore')
Logger.warn('Deprecated! Use InstancedBatchObject')
return null
}

Просмотреть файл

@ -22,7 +22,7 @@ import {
NoneBatchUpdateRange
} from './Batch'
import { ObjectLayers } from '../../IViewer'
import { DrawGroup } from './InstancedMeshBatch'
import { DrawGroup } from './Batch'
import Materials from '../materials/Materials'
export default class LineBatch implements Batch {
@ -49,7 +49,7 @@ export default class LineBatch implements Batch {
}
public get triCount(): number {
return (this.geometry.index.count / 3) * this.geometry['_maxInstanceCount']
return 0
}
public get vertCount(): number {
@ -61,6 +61,12 @@ export default class LineBatch implements Batch {
this.subtreeId = subtreeId
this.renderViews = renderViews
}
public get pointCount(): number {
return 0
}
public get lineCount(): number {
return (this.geometry.index.count / 3) * this.geometry['_maxInstanceCount']
}
public get renderObject(): Object3D {
return this.mesh
@ -142,13 +148,16 @@ export default class LineBatch implements Batch {
if (Materials.isOpaque(this.batchMaterial)) return AllBatchUpdateRange
return NoneBatchUpdateRange
}
public getDepth(): BatchUpdateRange {
return this.getOpaque()
}
public getTransparent(): BatchUpdateRange {
if (Materials.isTransparent(this.batchMaterial)) return AllBatchUpdateRange
return NoneBatchUpdateRange
}
public getStencil(): BatchUpdateRange {
if (this.batchMaterial.stencilWrite === true) return AllBatchUpdateRange
return NoneBatchUpdateRange

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,261 +1,57 @@
import {
BufferAttribute,
BufferGeometry,
DynamicDrawUsage,
Float32BufferAttribute,
Material,
Object3D,
Box3,
BufferAttribute,
Points,
Uint16BufferAttribute,
BufferGeometry,
Float32BufferAttribute,
Uint32BufferAttribute,
WebGLRenderer
Uint16BufferAttribute,
DynamicDrawUsage
} from 'three'
import { Geometry } from '../converter/Geometry'
import { NodeRenderView } from '../tree/NodeRenderView'
import {
AllBatchUpdateRange,
Batch,
BatchUpdateRange,
GeometryType,
NoneBatchUpdateRange
} from './Batch'
import { NodeRenderView } from '../..'
import { GeometryType, BatchUpdateRange } from './Batch'
import { DrawGroup } from './Batch'
import { PrimitiveBatch } from './PrimitiveBatch'
import { DrawRanges } from './DrawRanges'
import Logger from 'js-logger'
import { Geometry } from '../converter/Geometry'
import { ObjectLayers } from '../../IViewer'
import { DrawGroup } from './InstancedMeshBatch'
import Materials from '../materials/Materials'
export default class PointBatch implements Batch {
public id: string
public subtreeId: string
public renderViews: NodeRenderView[]
private geometry: BufferGeometry
public batchMaterial: Material
public mesh: Points
private needsFlatten = false
private needsShuffle = false
private gradientIndexBuffer: BufferAttribute
public get bounds() {
if (!this.geometry.boundingBox) this.geometry.computeBoundingBox()
return this.geometry.boundingBox
}
public get drawCalls(): number {
return this.geometry.groups.length
}
public get minDrawCalls(): number {
return (this.mesh.material as Material[]).length
}
public get triCount(): number {
return this.getCount()
}
public get vertCount(): number {
return this.geometry.attributes.position.count
}
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
this.id = id
this.subtreeId = subtreeId
this.renderViews = renderViews
}
public get renderObject(): Object3D {
return this.mesh
}
export class PointBatch extends PrimitiveBatch {
protected primitive: Points
protected drawRanges: DrawRanges = new DrawRanges()
public get geometryType(): GeometryType {
return this.renderViews[0].geometryType
}
public get materials(): Material[] {
return this.mesh.material as Material[]
public get bounds(): Box3 {
if (!this.primitive.geometry.boundingBox)
this.primitive.geometry.computeBoundingBox()
return this.primitive.geometry.boundingBox
}
public get groups(): DrawGroup[] {
return this.geometry.groups
public get minDrawCalls(): number {
return this.materials.length
}
public getCount() {
return this.geometry.attributes.position.array.length / 3
public get triCount(): number {
return 0
}
public setBatchMaterial(material: Material) {
this.batchMaterial = material
public get pointCount(): number {
return this.getCount()
}
public onUpdate(deltaTime: number) {
deltaTime
if (this.needsFlatten) {
this.flattenDrawGroups()
this.needsFlatten = false
}
if (this.needsShuffle) {
this.shuffleDrawGroups()
this.needsShuffle = false
}
public get lineCount(): number {
return 0
}
public onRender(renderer: WebGLRenderer) {
renderer
}
public setVisibleRange(...ranges: BatchUpdateRange[]) {
/** Entire batch needs to NOT be drawn */
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
this.geometry.setDrawRange(0, 0)
/** We unset the 'visible' flag, otherwise three.js will still run pointless buffer binding commands*/
this.mesh.visible = false
return
}
/** Entire batch needs to BE drawn */
if (ranges.length === 1 && ranges[0] === AllBatchUpdateRange) {
this.geometry.setDrawRange(0, this.getCount())
this.mesh.visible = true
return
}
/** Parts of the batch need to be visible. We get the min/max offset and total count */
let minOffset = Infinity
let maxOffset = 0
ranges.forEach((range) => {
minOffset = Math.min(minOffset, range.offset)
maxOffset = Math.max(maxOffset, range.offset)
})
this.geometry.setDrawRange(
minOffset,
maxOffset - minOffset + ranges.find((val) => val.offset === maxOffset).count
)
this.mesh.visible = true
}
public getVisibleRange(): BatchUpdateRange {
/** Entire batch is visible */
if (this.geometry.groups.length === 1 && this.mesh.visible)
return AllBatchUpdateRange
/** Entire batch is hidden */
if (!this.mesh.visible) return NoneBatchUpdateRange
/** Parts of the batch are visible */
return {
offset: this.geometry.drawRange.start,
count: this.geometry.drawRange.count
}
}
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) => {
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false
)
})
if (transparentOrHiddenGroup) {
return {
offset: 0,
count: transparentOrHiddenGroup.start
}
}
/** Entire batch is opaque */
return AllBatchUpdateRange
}
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) => {
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false ||
this.materials[value.materialIndex].colorWrite === false
)
})
if (transparentOrHiddenGroup) {
return {
offset: 0,
count: transparentOrHiddenGroup.start
}
}
/** Entire batch is opaque */
return AllBatchUpdateRange
}
public getTransparent(): BatchUpdateRange {
/** Look for a transparent group */
const transparentGroup = this.groups.find((value) => {
return Materials.isTransparent(this.materials[value.materialIndex])
})
/** Look for a hidden group */
const hiddenGroup = this.groups.find((value) => {
return this.materials[value.materialIndex].visible === false
})
/** If there is a transparent group return it's range */
if (transparentGroup) {
return {
offset: transparentGroup.start,
count:
hiddenGroup !== undefined
? hiddenGroup.start
: this.getCount() - transparentGroup.start
}
}
/** Entire batch is not transparent */
return NoneBatchUpdateRange
}
public getStencil(): BatchUpdateRange {
/** If there is a single group and it's material writes to stencil, return all */
if (this.groups.length === 1) {
if (this.materials[0].stencilWrite === true) return AllBatchUpdateRange
}
const stencilGroup = this.groups.find((value) => {
return this.materials[value.materialIndex].stencilWrite === true
})
if (stencilGroup) {
return {
offset: stencilGroup.start,
count: stencilGroup.count
}
}
/** No stencil group */
return NoneBatchUpdateRange
}
public setBatchBuffers(...range: 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
/** 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.geometry.attributes['gradientIndex'].array.length
: len,
shiftedIndex
)
minGradientIndex = Math.min(minGradientIndex, minMaxIndices.minIndex)
maxGradientIndex = Math.max(maxGradientIndex, minMaxIndices.maxIndex)
}
}
}
if (minGradientIndex < Infinity && maxGradientIndex > 0)
this.updateGradientIndexBuffer()
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
super()
this.id = id
this.subtreeId = subtreeId
this.renderViews = renderViews
}
public setDrawRanges(...ranges: BatchUpdateRange[]) {
@ -269,185 +65,35 @@ export default class PointBatch implements Batch {
this.materials.push(uniqueMaterials[k])
}
const sortedRanges = ranges.sort((a, b) => {
return a.offset - b.offset
})
this.primitive.geometry.groups = this.drawRanges.integrateRanges(
this.groups,
this.materials,
ranges
)
for (let i = 0; i < sortedRanges.length; i++) {
const materialIndex = this.materials.indexOf(sortedRanges[i].material)
const collidingGroup = this.getDrawRangeCollision(sortedRanges[i])
if (collidingGroup) {
collidingGroup.materialIndex = this.materials.indexOf(sortedRanges[i].material)
} else {
const includingGroup = this.geDrawRangeInclusion(sortedRanges[i])
if (includingGroup && includingGroup.materialIndex !== materialIndex) {
this.geometry.groups.splice(this.geometry.groups.indexOf(includingGroup), 1)
if (includingGroup.start === sortedRanges[i].offset) {
this.geometry.addGroup(
sortedRanges[i].offset,
sortedRanges[i].count,
materialIndex
)
this.geometry.addGroup(
sortedRanges[i].offset + sortedRanges[i].count,
includingGroup.count - sortedRanges[i].count,
includingGroup.materialIndex
)
} else if (
sortedRanges[i].offset + sortedRanges[i].count ===
includingGroup.start + includingGroup.count
) {
this.geometry.addGroup(
includingGroup.start,
includingGroup.count - sortedRanges[i].count,
includingGroup.materialIndex
)
this.geometry.addGroup(
sortedRanges[i].offset,
sortedRanges[i].count,
materialIndex
)
} else {
this.geometry.addGroup(
includingGroup.start,
sortedRanges[i].offset - includingGroup.start,
includingGroup.materialIndex
)
this.geometry.addGroup(
sortedRanges[i].offset,
sortedRanges[i].count,
materialIndex
)
this.geometry.addGroup(
sortedRanges[i].offset + sortedRanges[i].count,
includingGroup.count -
(sortedRanges[i].count + sortedRanges[i].offset - includingGroup.start),
includingGroup.materialIndex
)
}
}
}
}
let count = 0
this.geometry.groups.forEach((value) => (count += value.count))
this.groups.forEach((value) => (count += value.count))
if (count !== this.getCount()) {
Logger.error(`Draw groups invalid on ${this.id}`)
}
this.setBatchBuffers(...ranges)
this.needsFlatten = true
}
this.cleanMaterials()
private getDrawRangeCollision(range: BatchUpdateRange): {
start: number
count: number
materialIndex?: number
} {
if (this.geometry.groups.length > 0) {
for (let i = 0; i < this.geometry.groups.length; i++) {
if (
range.offset === this.geometry.groups[i].start &&
range.count === this.geometry.groups[i].count
) {
return this.geometry.groups[i]
}
}
return null
}
return null
}
private geDrawRangeInclusion(range: BatchUpdateRange): {
start: number
count: number
materialIndex?: number
} {
if (this.geometry.groups.length > 0) {
for (let i = 0; i < this.geometry.groups.length; i++) {
if (
range.offset >= this.geometry.groups[i].start &&
range.offset + range.count <=
this.geometry.groups[i].start + this.geometry.groups[i].count
) {
return this.geometry.groups[i]
}
}
return null
}
return null
}
private flattenDrawGroups() {
const materialOrder = []
this.geometry.groups.reduce((previousValue, currentValue) => {
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
previousValue.push(currentValue.materialIndex)
}
return previousValue
}, materialOrder)
const grouped = []
for (let k = 0; k < materialOrder.length; k++) {
grouped.push(
this.geometry.groups.filter((val) => {
return val.materialIndex === materialOrder[k]
})
)
}
this.geometry.groups = []
for (let matIndex = 0; matIndex < grouped.length; matIndex++) {
const matGroup = grouped[matIndex].sort((a, b) => {
return a.start - b.start
})
for (let k = 0; k < matGroup.length; ) {
let offset = matGroup[k].start
let count = matGroup[k].count
let runningCount = matGroup[k].count
let n = k + 1
for (; n < matGroup.length; n++) {
if (offset + count === matGroup[n].start) {
offset = matGroup[n].start
count = matGroup[n].count
runningCount += matGroup[n].count
} else {
const group = {
start: matGroup[k].start,
count: runningCount,
materialIndex: matGroup[k].materialIndex,
id: matGroup[k].id
}
this.geometry.groups.push(group)
break
}
}
if (n === matGroup.length) {
const group = {
start: matGroup[k].start,
count: runningCount,
materialIndex: matGroup[k].materialIndex,
id: matGroup[k].id
}
this.geometry.groups.push(group)
}
k = n
}
}
if (this.drawCalls > this.minDrawCalls + 2) {
this.needsShuffle = true
} else {
this.geometry.groups.sort((a, b) => {
return a.start - b.start
})
const transparentOrHiddenGroup = this.geometry.groups.find(
const transparentOrHiddenGroup = this.groups.find(
(value) =>
this.materials[value.materialIndex].transparent === true ||
this.materials[value.materialIndex].visible === false
)
if (transparentOrHiddenGroup) {
for (
let k = this.geometry.groups.indexOf(transparentOrHiddenGroup);
k < this.geometry.groups.length;
let k = this.groups.indexOf(transparentOrHiddenGroup);
k < this.groups.length;
k++
) {
const material = this.materials[this.geometry.groups[k].materialIndex]
const material = this.materials[this.groups[k].materialIndex]
if (material.transparent !== true && material.visible !== false) {
this.needsShuffle = true
break
@ -457,123 +103,50 @@ export default class PointBatch implements Batch {
}
}
private shuffleDrawGroups() {
const groups = this.geometry.groups
.sort((a, b) => {
return a.start - b.start
})
.slice()
this.geometry.groups.sort((a, b) => {
const materialA: Material = (this.mesh.material as Array<Material>)[
a.materialIndex
]
const materialB: Material = (this.mesh.material as Array<Material>)[
b.materialIndex
]
const visibleOrder = +materialB.visible - +materialA.visible
const transparentOrder = +materialA.transparent - +materialB.transparent
if (visibleOrder !== 0) return visibleOrder
return transparentOrder
})
const materialOrder = []
groups.reduce((previousValue, currentValue) => {
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
previousValue.push(currentValue.materialIndex)
}
return previousValue
}, materialOrder)
const grouped = []
for (let k = 0; k < materialOrder.length; k++) {
grouped.push(
groups.filter((val) => {
return val.materialIndex === materialOrder[k]
})
)
}
const sourceIBO: BufferAttribute = this.geometry.index
const targetIBOData: Uint16Array | Uint32Array = (
sourceIBO.array as Uint16Array | Uint32Array
).slice()
const newGroups = []
const scratchRvs = this.renderViews.slice()
scratchRvs.sort((a, b) => {
return a.batchStart - b.batchStart
})
let targetIBOOffset = 0
for (let k = 0; k < grouped.length; k++) {
const materialGroup = grouped[k]
const materialGroupStart = targetIBOOffset
let materialGroupCount = 0
for (let i = 0; i < (materialGroup as []).length; i++) {
const start = materialGroup[i].start
const count = materialGroup[i].count
const subArray = (sourceIBO.array as Uint16Array | Uint32Array).subarray(
start,
start + count
)
targetIBOData.set(subArray, targetIBOOffset)
let rvTrisCount = 0
for (let m = 0; m < scratchRvs.length; m++) {
if (
scratchRvs[m].batchStart >= start &&
scratchRvs[m].batchEnd <= start + count
) {
scratchRvs[m].setBatchData(
this.id,
targetIBOOffset + rvTrisCount,
scratchRvs[m].batchCount
)
rvTrisCount += scratchRvs[m].batchCount
scratchRvs.splice(m, 1)
m--
}
}
targetIBOOffset += count
materialGroupCount += count
}
newGroups.push({
offset: materialGroupStart,
count: materialGroupCount,
materialIndex: materialGroup[0].materialIndex
})
}
this.geometry.groups = []
for (let i = 0; i < newGroups.length; i++) {
this.geometry.addGroup(
newGroups[i].offset,
newGroups[i].count,
newGroups[i].materialIndex
)
}
;(this.geometry.index.array as Uint16Array | Uint32Array).set(targetIBOData)
this.geometry.index.needsUpdate = true
const hiddenGroup = this.geometry.groups.find((value) => {
return this.mesh.material[value.materialIndex].visible === false
})
if (hiddenGroup) {
this.setVisibleRange({
offset: 0,
count: hiddenGroup.start
})
}
}
public resetDrawRanges() {
this.mesh.material = [this.batchMaterial]
this.mesh.visible = true
this.geometry.clearGroups()
this.geometry.addGroup(0, this.getCount(), 0)
this.geometry.setDrawRange(0, Infinity)
this.primitive.material = [this.batchMaterial]
}
public buildBatch() {
protected getCurrentIndexBuffer(): BufferAttribute {
return this.primitive.geometry.index
}
protected getNextIndexBuffer(): BufferAttribute {
return new BufferAttribute(
(this.primitive.geometry.index.array as Uint16Array | Uint32Array).slice(),
this.primitive.geometry.index.itemSize
)
}
protected shuffleMaterialOrder(a: DrawGroup, b: DrawGroup): number {
const materialA: Material = this.materials[a.materialIndex]
const materialB: Material = this.materials[b.materialIndex]
const visibleOrder = +materialB.visible - +materialA.visible
const transparentOrder = +materialA.transparent - +materialB.transparent
if (visibleOrder !== 0) return visibleOrder
return transparentOrder
}
protected updateGradientIndexBufferData(
start: number,
end: number,
value: number
): { minIndex: number; maxIndex: number } {
const data = this.gradientIndexBuffer
;(data.array as Float32Array).fill(value, start, end)
this.gradientIndexBuffer.updateRange = {
offset: start,
count: end - start
}
this.gradientIndexBuffer.needsUpdate = true
this.primitive.geometry.attributes['gradientIndex'].needsUpdate = true
return {
minIndex: start,
maxIndex: end
}
}
public buildBatch(): void {
let attributeCount = 0
for (let k = 0; k < this.renderViews.length; k++) {
attributeCount +=
@ -605,18 +178,46 @@ export default class PointBatch implements Batch {
this.renderViews[k].disposeGeometry()
}
this.makePointGeometry(index, position, color)
this.mesh = new Points(this.geometry, this.batchMaterial)
this.mesh.material = [this.batchMaterial]
this.mesh.geometry.addGroup(0, this.getCount(), 0)
this.mesh.uuid = this.id
this.mesh.layers.set(
const geometry = this.makePointGeometry(index, position, color)
this.primitive = new Points(geometry, this.batchMaterial)
this.primitive.material = [this.batchMaterial]
this.primitive.geometry.addGroup(0, this.getCount(), 0)
this.primitive.uuid = this.id
this.primitive.layers.set(
this.renderViews[0].geometryType === GeometryType.POINT
? ObjectLayers.STREAM_CONTENT_POINT
: ObjectLayers.STREAM_CONTENT_POINT_CLOUD
)
}
protected makePointGeometry(
index: Int32Array,
position: Float64Array,
color: Float32Array
): BufferGeometry {
const geometry = new BufferGeometry()
geometry.setAttribute('position', new Float32BufferAttribute(position, 3))
geometry.setAttribute('color', new Float32BufferAttribute(color, 3))
if (position.length >= 65535 || index.length >= 65535) {
geometry.setIndex(new Uint32BufferAttribute(index, 1))
} else {
geometry.setIndex(new Uint16BufferAttribute(index, 1))
}
const buffer = new Float32Array(position.length / 3)
this.gradientIndexBuffer = new Float32BufferAttribute(buffer, 1)
this.gradientIndexBuffer.setUsage(DynamicDrawUsage)
geometry.setAttribute('gradientIndex', this.gradientIndexBuffer)
geometry.computeBoundingSphere()
geometry.computeBoundingBox()
Geometry.updateRTEGeometry(geometry, position)
return geometry
}
public getRenderView(index: number): NodeRenderView {
for (let k = 0; k < this.renderViews.length; k++) {
if (
@ -627,7 +228,6 @@ export default class PointBatch implements Batch {
}
}
}
public getMaterialAtIndex(index: number): Material {
for (let k = 0; k < this.renderViews.length; k++) {
if (
@ -635,103 +235,18 @@ export default class PointBatch implements Batch {
index < this.renderViews[k].batchEnd
) {
const rv = this.renderViews[k]
const group = this.geometry.groups.find((value) => {
const group = this.groups.find((value) => {
return (
rv.batchStart >= value.start &&
rv.batchStart + rv.batchCount <= value.count + value.start
)
})
if (!Array.isArray(this.mesh.material)) {
return this.mesh.material
} else {
if (!group) {
Logger.warn(`Malformed material index!`)
return null
}
return this.mesh.material[group.materialIndex]
if (!group) {
Logger.warn(`Malformed material index!`)
return null
}
return this.materials[group.materialIndex]
}
}
}
public getMaterial(rv: NodeRenderView): Material {
for (let k = 0; k < this.geometry.groups.length; k++) {
try {
if (
rv.batchStart >= this.geometry.groups[k].start &&
rv.batchEnd <= this.geometry.groups[k].start + this.geometry.groups[k].count
) {
return this.materials[this.geometry.groups[k].materialIndex]
}
} catch (e) {
Logger.error('Failed to get material')
}
}
}
private makePointGeometry(
index: Int32Array,
position: Float64Array,
color: Float32Array
): BufferGeometry {
this.geometry = new BufferGeometry()
this.geometry.setAttribute('position', new Float32BufferAttribute(position, 3))
this.geometry.setAttribute('color', new Float32BufferAttribute(color, 3))
if (position.length >= 65535 || index.length >= 65535) {
this.geometry.setIndex(new Uint32BufferAttribute(index, 1))
} else {
this.geometry.setIndex(new Uint16BufferAttribute(index, 1))
}
const buffer = new Float32Array(position.length / 3)
this.gradientIndexBuffer = new Float32BufferAttribute(buffer, 1)
this.gradientIndexBuffer.setUsage(DynamicDrawUsage)
this.geometry.setAttribute('gradientIndex', this.gradientIndexBuffer)
this.updateGradientIndexBufferData(0, buffer.length, 0)
this.updateGradientIndexBuffer()
this.geometry.computeBoundingSphere()
this.geometry.computeBoundingBox()
Geometry.updateRTEGeometry(this.geometry, position)
return this.geometry
}
private updateGradientIndexBufferData(
start: number,
end: number,
value: number
): { minIndex: number; maxIndex: number } {
const data = this.gradientIndexBuffer
;(data.array as Float32Array).fill(value, start, end)
this.gradientIndexBuffer.updateRange = {
offset: start,
count: end - start
}
this.gradientIndexBuffer.needsUpdate = true
this.geometry.attributes['gradientIndex'].needsUpdate = true
return {
minIndex: start,
maxIndex: end
}
}
private updateGradientIndexBuffer(rangeMin?: number, rangeMax?: number) {
this.gradientIndexBuffer.updateRange = {
offset: rangeMin !== undefined ? rangeMin : 0,
count:
rangeMin !== undefined && rangeMax !== undefined ? rangeMax - rangeMin + 1 : -1
}
this.gradientIndexBuffer.needsUpdate = true
this.geometry.attributes['gradientIndex'].needsUpdate = true
}
public purge() {
this.renderViews.length = 0
this.geometry.dispose()
this.batchMaterial.dispose()
this.mesh = null
}
}

Просмотреть файл

@ -0,0 +1,410 @@
import { Material, Object3D, BufferGeometry, BufferAttribute, Box3 } from 'three'
import { NodeRenderView } from '../..'
import {
AllBatchUpdateRange,
Batch,
BatchUpdateRange,
GeometryType,
NoneBatchUpdateRange
} from './Batch'
import { 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
}
export abstract class PrimitiveBatch implements Batch {
public id: string
public subtreeId: string
public renderViews: NodeRenderView[]
public batchMaterial: Material
protected abstract primitive: Primitive
protected gradientIndexBuffer: BufferAttribute
protected needsShuffle: boolean = false
abstract get geometryType(): GeometryType
abstract get bounds(): Box3
abstract get minDrawCalls(): number
abstract get triCount(): number
abstract get pointCount(): number
abstract get lineCount(): number
public get materials(): Material[] {
return this.primitive.material as Material[]
}
public get groups(): DrawGroup[] {
return this.primitive.geometry.groups
}
public get renderObject(): Object3D {
return this.primitive
}
public get drawCalls(): number {
return this.groups.length
}
public get vertCount(): number {
return this.primitive.geometry.attributes.position.count
}
public getCount(): number {
return this.primitive.geometry.index.count
}
public setBatchMaterial(material: Material): void {
this.batchMaterial = material
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public onUpdate(deltaTime: number) {
if (this.needsShuffle) {
this.shuffleDrawGroups()
this.needsShuffle = false
}
}
public setVisibleRange(...ranges: BatchUpdateRange[]) {
/** Entire batch needs to NOT be drawn */
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
this.primitive.geometry.setDrawRange(0, 0)
/** We unset the 'visible' flag, otherwise three.js will still run pointless buffer binding commands*/
this.primitive.visible = false
return
}
/** Entire batch needs to BE drawn */
if (ranges.length === 1 && ranges[0] === AllBatchUpdateRange) {
this.primitive.geometry.setDrawRange(0, this.getCount())
this.primitive.visible = true
return
}
/** Parts of the batch need to be visible. We get the min/max offset and total count */
let minOffset = Infinity
let maxOffset = 0
ranges.forEach((range) => {
minOffset = Math.min(minOffset, range.offset)
maxOffset = Math.max(maxOffset, range.offset)
})
this.primitive.geometry.setDrawRange(
minOffset,
maxOffset - minOffset + ranges.find((val) => val.offset === maxOffset).count
)
this.primitive.visible = true
}
public getVisibleRange(): BatchUpdateRange {
/** Entire batch is visible */
if (this.primitive.geometry.groups.length === 1 && this.primitive.visible)
return AllBatchUpdateRange
/** Entire batch is hidden */
if (!this.primitive.visible) return NoneBatchUpdateRange
/** Parts of the batch are visible */
return {
offset: this.primitive.geometry.drawRange.start,
count: this.primitive.geometry.drawRange.count
}
}
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) => {
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false
)
})
if (transparentOrHiddenGroup) {
return {
offset: 0,
count: transparentOrHiddenGroup.start
}
}
/** Entire batch is opaque */
return AllBatchUpdateRange
}
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) => {
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false ||
this.materials[value.materialIndex].colorWrite === false
)
})
if (transparentOrHiddenGroup) {
return {
offset: 0,
count: transparentOrHiddenGroup.start
}
}
/** Entire batch is opaque */
return AllBatchUpdateRange
}
public getTransparent(): BatchUpdateRange {
/** Look for a transparent group */
const transparentGroup = this.groups.find((value) => {
return Materials.isTransparent(this.materials[value.materialIndex])
})
/** Look for a hidden group */
const hiddenGroup = this.groups.find((value) => {
return this.materials[value.materialIndex].visible === false
})
/** If there is a transparent group return it's range */
if (transparentGroup) {
return {
offset: transparentGroup.start,
count:
hiddenGroup !== undefined
? hiddenGroup.start
: this.getCount() - transparentGroup.start
}
}
/** Entire batch is not transparent */
return NoneBatchUpdateRange
}
public getStencil(): BatchUpdateRange {
/** If there is a single group and it's material writes to stencil, return all */
if (this.groups.length === 1) {
if (this.materials[0].stencilWrite === true) return AllBatchUpdateRange
}
const stencilGroup = this.groups.find((value) => {
return this.materials[value.materialIndex].stencilWrite === true
})
if (stencilGroup) {
return {
offset: stencilGroup.start,
count: stencilGroup.count
}
}
/** No stencil group */
return NoneBatchUpdateRange
}
public setBatchBuffers(...range: 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
/** 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)
}
/** 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 (minGradientIndex < Infinity && maxGradientIndex > 0)
this.updateGradientIndexBuffer()
}
protected cleanMaterials() {
const materialsInUse = [
...Array.from(
new Set(this.groups.map((value) => this.materials[value.materialIndex]))
)
]
let k = 0
while (this.materials.length > materialsInUse.length) {
if (!materialsInUse.includes(this.materials[k])) {
this.materials.splice(k, 1)
this.groups.forEach((value: DrawGroup) => {
if (value.materialIndex > k) value.materialIndex--
})
k = 0
continue
}
k++
}
}
protected abstract getCurrentIndexBuffer(): BufferAttribute
protected abstract getNextIndexBuffer(): BufferAttribute
protected abstract shuffleMaterialOrder(a: DrawGroup, b: DrawGroup): number
private shuffleDrawGroups() {
const groups = this.primitive.geometry.groups.slice()
groups.sort(this.shuffleMaterialOrder.bind(this))
const materialOrder = []
groups.reduce((previousValue, currentValue) => {
if (previousValue.indexOf(currentValue.materialIndex) === -1) {
previousValue.push(currentValue.materialIndex)
}
return previousValue
}, materialOrder)
const grouped = []
for (let k = 0; k < materialOrder.length; k++) {
grouped.push(
groups.filter((val) => {
return val.materialIndex === materialOrder[k]
})
)
}
const sourceIBO: BufferAttribute = this.getCurrentIndexBuffer()
const targetIBO: BufferAttribute = this.getNextIndexBuffer()
const sourceIBOData: Uint16Array | Uint32Array = sourceIBO.array as
| Uint16Array
| Uint32Array
const targetIBOData: Uint16Array | Uint32Array = targetIBO.array as
| Uint16Array
| Uint32Array
const newGroups = []
const scratchRvs = this.renderViews.slice()
scratchRvs.sort((a, b) => {
return a.batchStart - b.batchStart
})
let targetIBOOffset = 0
for (let k = 0; k < grouped.length; k++) {
const materialGroup = grouped[k]
const materialGroupStart = targetIBOOffset
let materialGroupCount = 0
for (let i = 0; i < (materialGroup as []).length; i++) {
const start = materialGroup[i].start
const count = materialGroup[i].count
const subArray = sourceIBOData.subarray(start, start + count)
targetIBOData.set(subArray, targetIBOOffset)
let rvTrisCount = 0
for (let m = 0; m < scratchRvs.length; m++) {
if (
scratchRvs[m].batchStart >= start &&
scratchRvs[m].batchEnd <= start + count
) {
scratchRvs[m].setBatchData(
this.id,
targetIBOOffset + rvTrisCount,
scratchRvs[m].batchCount
)
rvTrisCount += scratchRvs[m].batchCount
scratchRvs.splice(m, 1)
m--
}
}
targetIBOOffset += count
materialGroupCount += count
}
newGroups.push({
offset: materialGroupStart,
count: materialGroupCount,
materialIndex: materialGroup[0].materialIndex
})
}
this.primitive.geometry.groups = []
for (let i = 0; i < newGroups.length; i++) {
this.primitive.geometry.addGroup(
newGroups[i].offset,
newGroups[i].count,
newGroups[i].materialIndex
)
}
this.primitive.geometry.setIndex(targetIBO)
this.primitive.geometry.index.needsUpdate = true
const hiddenGroup = this.primitive.geometry.groups.find((value) => {
return this.primitive.material[value.materialIndex].visible === false
})
if (hiddenGroup) {
this.setVisibleRange({
offset: 0,
count: hiddenGroup.start
})
}
// console.log('Final -> ', this.id, this.groups.slice())
}
protected abstract updateGradientIndexBufferData(
start: number,
end: number,
value: number
): { minIndex: number; maxIndex: number }
protected updateGradientIndexBuffer(rangeMin?: number, rangeMax?: number): void {
this.gradientIndexBuffer.updateRange = {
offset: rangeMin !== undefined ? rangeMin : 0,
count:
rangeMin !== undefined && rangeMax !== undefined ? rangeMax - rangeMin + 1 : -1
}
this.gradientIndexBuffer.needsUpdate = true
this.primitive.geometry.attributes['gradientIndex'].needsUpdate = true
}
public abstract setDrawRanges(...ranges: BatchUpdateRange[])
public resetDrawRanges(): void {
this.primitive.visible = true
this.primitive.geometry.clearGroups()
this.primitive.geometry.addGroup(0, this.getCount(), 0)
this.primitive.geometry.setDrawRange(0, Infinity)
}
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 purge(): void {
this.renderViews.length = 0
this.primitive.geometry.dispose()
this.batchMaterial.dispose()
this.primitive = null
}
}

Просмотреть файл

@ -12,7 +12,7 @@ import {
import { SpeckleText } from '../objects/SpeckleText'
import { ObjectLayers } from '../../IViewer'
import { DrawGroup } from './InstancedMeshBatch'
import { DrawGroup } from './Batch'
import Materials from '../materials/Materials'
export default class TextBatch implements Batch {
@ -50,6 +50,12 @@ export default class TextBatch implements Batch {
this.subtreeId = subtreeId
this.renderViews = renderViews
}
public get pointCount(): number {
return 0
}
public get lineCount(): number {
return 0
}
public get geometryType(): GeometryType {
return GeometryType.TEXT

Просмотреть файл

@ -1,6 +1,6 @@
import { Vector3 } from 'three'
import { Extension } from './core-extensions/Extension'
import { UpdateFlags } from '../..'
import { UpdateFlags } from '../../IViewer'
export class ExplodeExtension extends Extension {
private explodeTime = -1

Просмотреть файл

@ -11,7 +11,6 @@ import {
} from 'three'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js'
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js'
import MeshBatch from '../batching/MeshBatch'
import { Geometry } from '../converter/Geometry'
import SpeckleGhostMaterial from '../materials/SpeckleGhostMaterial'
import SpeckleLineMaterial from '../materials/SpeckleLineMaterial'
@ -21,6 +20,7 @@ import { ISectionProvider } from './core-extensions/Providers'
import { SectionToolEvent } from './SectionTool'
import { GeometryType } from '../batching/Batch'
import { ObjectLayers } from '../../IViewer'
import { MeshBatch } from '../batching/MeshBatch'
import Logger from 'js-logger'
export enum PlaneId {

Просмотреть файл

@ -12,12 +12,9 @@ import {
UpdateFlags,
ViewerEvent
} from '../../IViewer'
import Materials, {
DisplayStyle,
MaterialOptions,
RenderMaterial,
StencilOutlineType
} from '../materials/Materials'
import Materials, { DisplayStyle, RenderMaterial } from '../materials/Materials'
import { StencilOutlineType } from '../../IViewer'
import { MaterialOptions } from '../materials/MaterialOptions'
import { TreeNode } from '../tree/WorldTree'
export interface SelectionExtensionOptions {

Просмотреть файл

@ -0,0 +1,7 @@
import { StencilOutlineType } from '../../IViewer'
export interface MaterialOptions {
stencilOutlines?: StencilOutlineType
pointSize?: number
depthWrite?: number
}

Просмотреть файл

@ -13,6 +13,7 @@ import SpeckleGhostMaterial from './SpeckleGhostMaterial'
import SpeckleTextMaterial from './SpeckleTextMaterial'
import { SpeckleMaterial } from './SpeckleMaterial'
import SpecklePointColouredMaterial from './SpecklePointColouredMaterial'
import { MaterialOptions } from '../../IViewer'
export interface RenderMaterial {
id: string
@ -30,18 +31,6 @@ export interface DisplayStyle {
opacity?: number
}
export interface MaterialOptions {
stencilOutlines?: StencilOutlineType
pointSize?: number
depthWrite?: number
}
export enum StencilOutlineType {
NONE,
OVERLAY,
OUTLINE_ONLY
}
export enum FilterMaterialType {
GHOST,
GRADIENT,

Просмотреть файл

@ -3,8 +3,6 @@
import { speckleLineVert } from './shaders/speckle-line-vert'
import { speckleLineFrag } from './shaders/speckle-line-frag'
import { ShaderLib, Vector3, IUniform, Material } from 'three'
import { Matrix4 } from 'three'
import { Geometry } from '../converter/Geometry'
import { ExtendedLineMaterial, Uniforms } from './SpeckleMaterial'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'

Просмотреть файл

@ -13,7 +13,8 @@ import {
UniformsUtils
} from 'three'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { MaterialOptions, StencilOutlineType } from './Materials'
import { StencilOutlineType } from '../../IViewer'
import { MaterialOptions } from './MaterialOptions'
class SpeckleUserData {
toJSON() {

Просмотреть файл

@ -17,7 +17,11 @@ import {
import { BatchObject } from '../batching/BatchObject'
import Materials from '../materials/Materials'
import { TopLevelAccelerationStructure } from './TopLevelAccelerationStructure'
import InstancedMeshBatch, { DrawGroup } from '../batching/InstancedMeshBatch'
import {
DrawGroup,
INSTANCE_GRADIENT_BUFFER_STRIDE,
INSTANCE_TRANSFORM_BUFFER_STRIDE
} from '../batching/Batch'
import { ObjectLayers } from '../../IViewer'
import Logger from 'js-logger'
@ -152,21 +156,20 @@ export default class SpeckleInstancedMesh extends Group {
this.groups[k].start,
this.groups[k].start + this.groups[k].count
),
InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
INSTANCE_TRANSFORM_BUFFER_STRIDE
)
group.geometry.setAttribute(
'gradientIndex',
new InstancedBufferAttribute(
gradientBuffer.subarray(
this.groups[k].start / InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE,
this.groups[k].start / INSTANCE_TRANSFORM_BUFFER_STRIDE,
(this.groups[k].start + this.groups[k].count) /
InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
INSTANCE_TRANSFORM_BUFFER_STRIDE
),
InstancedMeshBatch.INSTANCE_GRADIENT_BUFFER_STRIDE
INSTANCE_GRADIENT_BUFFER_STRIDE
)
)
group.count =
this.groups[k].count / InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE
group.count = this.groups[k].count / INSTANCE_TRANSFORM_BUFFER_STRIDE
group.instanceMatrix.needsUpdate = true
group.layers.set(ObjectLayers.STREAM_CONTENT_MESH)
group.frustumCulled = false
@ -193,8 +196,7 @@ export default class SpeckleInstancedMesh extends Group {
if (group) {
const instance: InstancedMesh = this.instances[this.groups.indexOf(group)]
instance.setMatrixAt(
(rv.batchStart - group.start) /
InstancedMeshBatch.INSTANCE_TRANSFORM_BUFFER_STRIDE,
(rv.batchStart - group.start) / INSTANCE_TRANSFORM_BUFFER_STRIDE,
batchObject.transform
)

Просмотреть файл

@ -0,0 +1,106 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Draw Ranges > Boundary Ranges 1`] = `
[
{
"count": 2095,
"materialIndex": 1,
"start": 0,
},
]
`;
exports[`Draw Ranges > Boundary Ranges 2`] = `
[
{
"count": 2094,
"materialIndex": 0,
"start": 0,
},
{
"count": 1,
"materialIndex": 1,
"start": 2094,
},
]
`;
exports[`Draw Ranges > Boundary Ranges 3`] = `
[
{
"count": 2094,
"materialIndex": 0,
"start": 0,
},
{
"count": 1,
"materialIndex": 1,
"start": 2094,
},
]
`;
exports[`Draw Ranges > Mixed Ranges 1`] = `
[
{
"count": 36,
"materialIndex": 0,
"start": 0,
},
{
"count": 36,
"materialIndex": 1,
"start": 36,
},
{
"count": 108,
"materialIndex": 0,
"start": 72,
},
{
"count": 1395,
"materialIndex": 1,
"start": 180,
},
{
"count": 6,
"materialIndex": 0,
"start": 1575,
},
{
"count": 32766,
"materialIndex": 1,
"start": 1581,
},
]
`;
exports[`Draw Ranges > Multiple Materials 1`] = `
[
{
"count": 36,
"materialIndex": 3,
"start": 0,
},
{
"count": 257,
"materialIndex": 4,
"start": 36,
},
{
"count": 1246,
"materialIndex": 1,
"start": 293,
},
{
"count": 540,
"materialIndex": 0,
"start": 1539,
},
{
"count": 32268,
"materialIndex": 2,
"start": 2079,
},
]
`;

Просмотреть файл

@ -0,0 +1,155 @@
import { expect, describe, it } from 'vitest'
import { DrawRanges } from '../src/modules/batching/DrawRanges'
import SpeckleBasicMaterial from '../src/modules/materials/SpeckleBasicMaterial'
import { DrawGroup } from '../src/modules/batching/Batch'
const material0 = new SpeckleBasicMaterial({ color: 0xff0000 })
const material1 = new SpeckleBasicMaterial({ color: 0x00ff00 })
describe('Draw Ranges', () => {
it('Boundary Ranges', () => {
const drawRange = new DrawRanges()
let groups = [
{
start: 0,
count: 2095,
materialIndex: 0
} as DrawGroup
]
groups = drawRange.integrateRanges(
groups,
[material0, material1],
[
{
offset: 0,
count: 2095,
material: material1
}
]
)
expect(groups).toMatchSnapshot()
groups = drawRange.integrateRanges(
groups,
[material0, material1],
[
{
offset: 0,
count: 2094,
material: material0
}
]
)
expect(groups).toMatchSnapshot()
groups = [
{
start: 0,
count: 2095,
materialIndex: 0
} as DrawGroup
]
groups = drawRange.integrateRanges(
groups,
[material0, material1],
[
{
offset: 2094,
count: 1,
material: material1
}
]
)
expect(groups).toMatchSnapshot()
})
it('Mixed Ranges', () => {
let groups = [
{
start: 0,
count: 216,
materialIndex: 0
} as DrawGroup,
{
start: 216,
count: 1323,
materialIndex: 0
} as DrawGroup,
{
start: 1539,
count: 540,
materialIndex: 0
} as DrawGroup,
{
start: 2079,
count: 32268,
materialIndex: 0
} as DrawGroup
]
const drawRange = new DrawRanges()
groups = drawRange.integrateRanges(
groups,
[material0, material1],
[
{ offset: 36, count: 36, material: material1 },
{
offset: 180,
count: 1395,
material: material1
},
{
offset: 1581,
count: 32766,
material: material1
}
]
)
expect(groups).toMatchSnapshot()
})
it('Multiple Materials', () => {
const material2 = new SpeckleBasicMaterial({ color: 0x0000ff })
const material3 = new SpeckleBasicMaterial({ color: 0x0000ff })
const material4 = new SpeckleBasicMaterial({ color: 0x0000ff })
let groups = [
{
start: 0,
count: 216,
materialIndex: 3
} as DrawGroup,
{
start: 216,
count: 1323,
materialIndex: 1
} as DrawGroup,
{
start: 1539,
count: 540,
materialIndex: 0
} as DrawGroup,
{
start: 2079,
count: 32268,
materialIndex: 2
} as DrawGroup
]
const drawRange = new DrawRanges()
groups = drawRange.integrateRanges(
groups,
[material0, material1, material2, material3, material4],
[
{
offset: 36,
count: 257,
material: material4
}
]
)
expect(groups).toMatchSnapshot()
})
})

Просмотреть файл

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "test"]
}

Просмотреть файл

@ -17,6 +17,6 @@
"checkJs": false,
"declaration": true
},
"include": ["./src/**/*"],
"include": ["./src/**/*", "test"],
"exclude": ["dist"]
}

Просмотреть файл

@ -0,0 +1,12 @@
/// <reference types="vitest" />
// Configure Vitest (https://vitest.dev/config/)
import { defineConfig } from 'vite'
export default defineConfig({
test: {
environment: 'jsdom'
// globals: true,
}
})

479
yarn.lock
Просмотреть файл

@ -10607,6 +10607,15 @@ __metadata:
languageName: node
linkType: hard
"@jest/schemas@npm:^29.6.3":
version: 29.6.3
resolution: "@jest/schemas@npm:29.6.3"
dependencies:
"@sinclair/typebox": ^0.27.8
checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93
languageName: node
linkType: hard
"@jest/source-map@npm:^27.5.1":
version: 27.5.1
resolution: "@jest/source-map@npm:27.5.1"
@ -13772,6 +13781,13 @@ __metadata:
languageName: node
linkType: hard
"@sinclair/typebox@npm:^0.27.8":
version: 0.27.8
resolution: "@sinclair/typebox@npm:0.27.8"
checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1
languageName: node
linkType: hard
"@sindresorhus/is@npm:^4.0.0":
version: 4.6.0
resolution: "@sindresorhus/is@npm:4.6.0"
@ -14568,6 +14584,7 @@ __metadata:
"@types/three": ^0.136.0
"@typescript-eslint/eslint-plugin": ^5.39.0
"@typescript-eslint/parser": ^5.39.0
"@vitest/ui": ^1.4.0
camera-controls: ^1.33.1
core-js: ^3.21.1
eslint: ^8.11.0
@ -14575,6 +14592,7 @@ __metadata:
flat: ^5.0.2
hold-event: ^0.1.0
js-logger: 1.6.1
jsdom: ^24.0.0
lodash-es: ^4.17.21
prettier: ^2.5.1
rainbowvis.js: ^1.0.1
@ -14590,6 +14608,7 @@ __metadata:
troika-three-text: 0.47.2
typescript: ^4.5.4
underscore: 1.13.6
vitest: ^1.4.0
languageName: unknown
linkType: soft
@ -18467,6 +18486,77 @@ __metadata:
languageName: node
linkType: hard
"@vitest/expect@npm:1.4.0":
version: 1.4.0
resolution: "@vitest/expect@npm:1.4.0"
dependencies:
"@vitest/spy": 1.4.0
"@vitest/utils": 1.4.0
chai: ^4.3.10
checksum: aca8b0b592bc020febb08767a230a2f4118453904972df06b2a34c76a669e8eb260ddfd8b0cdc152e93dbf21e0d7ae98bdf09fa80477b21145ac21c01fc5a0d3
languageName: node
linkType: hard
"@vitest/runner@npm:1.4.0":
version: 1.4.0
resolution: "@vitest/runner@npm:1.4.0"
dependencies:
"@vitest/utils": 1.4.0
p-limit: ^5.0.0
pathe: ^1.1.1
checksum: 41a847d1ba916c64e482a69342222a40b81014ad06da7ccb7d8cd4119c5718564c22b00367574803642e6ca6ab5678509073b5c460bedc2b385d4e990351012f
languageName: node
linkType: hard
"@vitest/snapshot@npm:1.4.0":
version: 1.4.0
resolution: "@vitest/snapshot@npm:1.4.0"
dependencies:
magic-string: ^0.30.5
pathe: ^1.1.1
pretty-format: ^29.7.0
checksum: fe495661d682534b41f3ac373c017ac84c04263087a715307715bb41a69c307d6f237b18cc8195bc22d82bf38a1a1a773b0c99715d5de5f40c6169e916b8f4a4
languageName: node
linkType: hard
"@vitest/spy@npm:1.4.0":
version: 1.4.0
resolution: "@vitest/spy@npm:1.4.0"
dependencies:
tinyspy: ^2.2.0
checksum: 3b1c422760e5840e9e4aa804de7619f3d14e0b364b29f8f030df528206b84c5706792c5d680ac668077d5663f8648a17a783df11cd90ddf2a11000359f1a6286
languageName: node
linkType: hard
"@vitest/ui@npm:^1.4.0":
version: 1.4.0
resolution: "@vitest/ui@npm:1.4.0"
dependencies:
"@vitest/utils": 1.4.0
fast-glob: ^3.3.2
fflate: ^0.8.1
flatted: ^3.2.9
pathe: ^1.1.1
picocolors: ^1.0.0
sirv: ^2.0.4
peerDependencies:
vitest: 1.4.0
checksum: 5a508fd80fcdf87acf4697ae8836cf4dcdd5300fd1574a400a62f5242a6bb4028b3002d60db20f84c3e3e4b4ae110b33194c31219330fffe205e5d9fbf1c65c8
languageName: node
linkType: hard
"@vitest/utils@npm:1.4.0":
version: 1.4.0
resolution: "@vitest/utils@npm:1.4.0"
dependencies:
diff-sequences: ^29.6.3
estree-walker: ^3.0.3
loupe: ^2.3.7
pretty-format: ^29.7.0
checksum: 5b54e36e5ad236da1da548d31e3b080d1798179f787cfb9ac512d03e59401f8baaa96fec15b6de6b969ee0e057660289109a69a39bd988e89aa72625b5f78484
languageName: node
linkType: hard
"@volar/language-core@npm:1.10.1, @volar/language-core@npm:~1.10.0":
version: 1.10.1
resolution: "@volar/language-core@npm:1.10.1"
@ -20264,6 +20354,13 @@ __metadata:
languageName: node
linkType: hard
"acorn-walk@npm:^8.3.2":
version: 8.3.2
resolution: "acorn-walk@npm:8.3.2"
checksum: 3626b9d26a37b1b427796feaa5261faf712307a8920392c8dce9a5739fb31077667f4ad2ec71c7ac6aaf9f61f04a9d3d67ff56f459587206fc04aa31c27ef392
languageName: node
linkType: hard
"acorn@npm:8.10.0, acorn@npm:^8.9.0":
version: 8.10.0
resolution: "acorn@npm:8.10.0"
@ -22723,6 +22820,21 @@ __metadata:
languageName: node
linkType: hard
"chai@npm:^4.3.10":
version: 4.4.1
resolution: "chai@npm:4.4.1"
dependencies:
assertion-error: ^1.1.0
check-error: ^1.0.3
deep-eql: ^4.1.3
get-func-name: ^2.0.2
loupe: ^2.3.6
pathval: ^1.1.1
type-detect: ^4.0.8
checksum: 9ab84f36eb8e0b280c56c6c21ca4da5933132cd8a0c89c384f1497f77953640db0bc151edd47f81748240a9fab57b78f7d925edfeedc8e8fc98016d71f40c36e
languageName: node
linkType: hard
"chalk@npm:^2.0.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
@ -22868,6 +22980,15 @@ __metadata:
languageName: node
linkType: hard
"check-error@npm:^1.0.3":
version: 1.0.3
resolution: "check-error@npm:1.0.3"
dependencies:
get-func-name: ^2.0.2
checksum: e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399
languageName: node
linkType: hard
"cheerio-select@npm:^1.5.0":
version: 1.6.0
resolution: "cheerio-select@npm:1.6.0"
@ -24514,6 +24635,15 @@ __metadata:
languageName: node
linkType: hard
"cssstyle@npm:^4.0.1":
version: 4.0.1
resolution: "cssstyle@npm:4.0.1"
dependencies:
rrweb-cssom: ^0.6.0
checksum: 4b2fdd81c565b1f8f24a792f85d3a19269a2f201e731c3fe3531d7fc78b4bc6b31906ed17aba7edba7b1c8b7672574fc6c09fe925556da3a9a9458dbf8c4fa22
languageName: node
linkType: hard
"csstype@npm:^3.1.0":
version: 3.1.0
resolution: "csstype@npm:3.1.0"
@ -24748,6 +24878,16 @@ __metadata:
languageName: node
linkType: hard
"data-urls@npm:^5.0.0":
version: 5.0.0
resolution: "data-urls@npm:5.0.0"
dependencies:
whatwg-mimetype: ^4.0.0
whatwg-url: ^14.0.0
checksum: 5c40568c31b02641a70204ff233bc4e42d33717485d074244a98661e5f2a1e80e38fe05a5755dfaf2ee549f2ab509d6a3af2a85f4b2ad2c984e5d176695eaf46
languageName: node
linkType: hard
"dataloader@npm:2.1.0, dataloader@npm:^2.0.0":
version: 2.1.0
resolution: "dataloader@npm:2.1.0"
@ -24913,6 +25053,13 @@ __metadata:
languageName: node
linkType: hard
"decimal.js@npm:^10.4.3":
version: 10.4.3
resolution: "decimal.js@npm:10.4.3"
checksum: 796404dcfa9d1dbfdc48870229d57f788b48c21c603c3f6554a1c17c10195fc1024de338b0cf9e1efe0c7c167eeb18f04548979bcc5fdfabebb7cc0ae3287bae
languageName: node
linkType: hard
"decompress-response@npm:^6.0.0":
version: 6.0.0
resolution: "decompress-response@npm:6.0.0"
@ -24938,6 +25085,15 @@ __metadata:
languageName: node
linkType: hard
"deep-eql@npm:^4.1.3":
version: 4.1.3
resolution: "deep-eql@npm:4.1.3"
dependencies:
type-detect: ^4.0.0
checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f
languageName: node
linkType: hard
"deep-equal-in-any-order@npm:^1.1.15":
version: 1.1.17
resolution: "deep-equal-in-any-order@npm:1.1.17"
@ -25445,6 +25601,13 @@ __metadata:
languageName: node
linkType: hard
"diff-sequences@npm:^29.6.3":
version: 29.6.3
resolution: "diff-sequences@npm:29.6.3"
checksum: f4914158e1f2276343d98ff5b31fc004e7304f5470bf0f1adb2ac6955d85a531a6458d33e87667f98f6ae52ebd3891bb47d420bb48a5bd8b7a27ee25b20e33aa
languageName: node
linkType: hard
"diff@npm:5.0.0":
version: 5.0.0
resolution: "diff@npm:5.0.0"
@ -26059,7 +26222,7 @@ __metadata:
languageName: node
linkType: hard
"entities@npm:^4.5.0":
"entities@npm:^4.4.0, entities@npm:^4.5.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7
@ -27669,6 +27832,13 @@ __metadata:
languageName: node
linkType: hard
"fflate@npm:^0.8.1":
version: 0.8.2
resolution: "fflate@npm:0.8.2"
checksum: 29470337b85d3831826758e78f370e15cda3169c5cd4477c9b5eea2402261a74b2975bae816afabe1c15d21d98591e0d30a574f7103aa117bff60756fa3035d4
languageName: node
linkType: hard
"figures@npm:^3.0.0":
version: 3.2.0
resolution: "figures@npm:3.2.0"
@ -28343,7 +28513,7 @@ __metadata:
languageName: node
linkType: hard
"get-func-name@npm:^2.0.0":
"get-func-name@npm:^2.0.0, get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2":
version: 2.0.2
resolution: "get-func-name@npm:2.0.2"
checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b
@ -29585,6 +29755,15 @@ __metadata:
languageName: node
linkType: hard
"html-encoding-sniffer@npm:^4.0.0":
version: 4.0.0
resolution: "html-encoding-sniffer@npm:4.0.0"
dependencies:
whatwg-encoding: ^3.1.1
checksum: 3339b71dab2723f3159a56acf541ae90a408ce2d11169f00fe7e0c4663d31d6398c8a4408b504b4eec157444e47b084df09b3cb039c816660f0dd04846b8957d
languageName: node
linkType: hard
"html-entities@npm:^2.3.2":
version: 2.3.3
resolution: "html-entities@npm:2.3.3"
@ -32789,6 +32968,40 @@ __metadata:
languageName: node
linkType: hard
"jsdom@npm:^24.0.0":
version: 24.0.0
resolution: "jsdom@npm:24.0.0"
dependencies:
cssstyle: ^4.0.1
data-urls: ^5.0.0
decimal.js: ^10.4.3
form-data: ^4.0.0
html-encoding-sniffer: ^4.0.0
http-proxy-agent: ^7.0.0
https-proxy-agent: ^7.0.2
is-potential-custom-element-name: ^1.0.1
nwsapi: ^2.2.7
parse5: ^7.1.2
rrweb-cssom: ^0.6.0
saxes: ^6.0.0
symbol-tree: ^3.2.4
tough-cookie: ^4.1.3
w3c-xmlserializer: ^5.0.0
webidl-conversions: ^7.0.0
whatwg-encoding: ^3.1.1
whatwg-mimetype: ^4.0.0
whatwg-url: ^14.0.0
ws: ^8.16.0
xml-name-validator: ^5.0.0
peerDependencies:
canvas: ^2.11.2
peerDependenciesMeta:
canvas:
optional: true
checksum: 180cf672c1f5e4375fd831b6990c453b4c22b540619abe7a0a3ed0d18eca1171dea9f25739bc06dfea26d1c0d71c7ac26e62fc9a2d9b1657003fc8fd1bf6f9f4
languageName: node
linkType: hard
"jsesc@npm:^2.5.1":
version: 2.5.2
resolution: "jsesc@npm:2.5.2"
@ -33983,6 +34196,15 @@ __metadata:
languageName: node
linkType: hard
"loupe@npm:^2.3.6, loupe@npm:^2.3.7":
version: 2.3.7
resolution: "loupe@npm:2.3.7"
dependencies:
get-func-name: ^2.0.1
checksum: 96c058ec7167598e238bb7fb9def2f9339215e97d6685d9c1e3e4bdb33d14600e11fe7a812cf0c003dfb73ca2df374f146280b2287cae9e8d989e9d7a69a203b
languageName: node
linkType: hard
"lower-case-first@npm:^2.0.2":
version: 2.0.2
resolution: "lower-case-first@npm:2.0.2"
@ -37258,6 +37480,13 @@ __metadata:
languageName: node
linkType: hard
"nwsapi@npm:^2.2.7":
version: 2.2.7
resolution: "nwsapi@npm:2.2.7"
checksum: cab25f7983acec7e23490fec3ef7be608041b460504229770e3bfcf9977c41d6fe58f518994d3bd9aa3a101f501089a3d4a63536f4ff8ae4b8c4ca23bdbfda4e
languageName: node
linkType: hard
"nyc@npm:^15.0.1, nyc@npm:^15.1.0":
version: 15.1.0
resolution: "nyc@npm:15.1.0"
@ -37810,6 +38039,15 @@ __metadata:
languageName: node
linkType: hard
"p-limit@npm:^5.0.0":
version: 5.0.0
resolution: "p-limit@npm:5.0.0"
dependencies:
yocto-queue: ^1.0.0
checksum: 87bf5837dee6942f0dbeff318436179931d9a97848d1b07dbd86140a477a5d2e6b90d9701b210b4e21fe7beaea2979dfde366e4f576fa644a59bd4d6a6371da7
languageName: node
linkType: hard
"p-locate@npm:^3.0.0":
version: 3.0.0
resolution: "p-locate@npm:3.0.0"
@ -38141,6 +38379,15 @@ __metadata:
languageName: node
linkType: hard
"parse5@npm:^7.1.2":
version: 7.1.2
resolution: "parse5@npm:7.1.2"
dependencies:
entities: ^4.4.0
checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713
languageName: node
linkType: hard
"parseurl@npm:^1.3.2, parseurl@npm:^1.3.3, parseurl@npm:~1.3.2, parseurl@npm:~1.3.3":
version: 1.3.3
resolution: "parseurl@npm:1.3.3"
@ -40060,6 +40307,17 @@ __metadata:
languageName: node
linkType: hard
"pretty-format@npm:^29.7.0":
version: 29.7.0
resolution: "pretty-format@npm:29.7.0"
dependencies:
"@jest/schemas": ^29.6.3
ansi-styles: ^5.0.0
react-is: ^18.0.0
checksum: 032c1602383e71e9c0c02a01bbd25d6759d60e9c7cf21937dde8357aa753da348fcec5def5d1002c9678a8524d5fe099ad98861286550ef44de8808cc61e43b6
languageName: node
linkType: hard
"pretty-hrtime@npm:^1.0.3":
version: 1.0.3
resolution: "pretty-hrtime@npm:1.0.3"
@ -40739,6 +40997,13 @@ __metadata:
languageName: node
linkType: hard
"punycode@npm:^2.3.1":
version: 2.3.1
resolution: "punycode@npm:2.3.1"
checksum: bb0a0ceedca4c3c57a9b981b90601579058903c62be23c5e8e843d2c2d4148a3ecf029d5133486fb0e1822b098ba8bba09e89d6b21742d02fa26bda6441a6fb2
languageName: node
linkType: hard
"puppeteer-core@npm:^2.1.1":
version: 2.1.1
resolution: "puppeteer-core@npm:2.1.1"
@ -42403,6 +42668,13 @@ __metadata:
languageName: node
linkType: hard
"rrweb-cssom@npm:^0.6.0":
version: 0.6.0
resolution: "rrweb-cssom@npm:0.6.0"
checksum: 182312f6e4f41d18230ccc34f14263bc8e8a6b9d30ee3ec0d2d8e643c6f27964cd7a8d638d4a00e988d93e8dc55369f4ab5a473ccfeff7a8bab95b36d2b5499c
languageName: node
linkType: hard
"run-applescript@npm:^7.0.0":
version: 7.0.0
resolution: "run-applescript@npm:7.0.0"
@ -42549,6 +42821,15 @@ __metadata:
languageName: node
linkType: hard
"saxes@npm:^6.0.0":
version: 6.0.0
resolution: "saxes@npm:6.0.0"
dependencies:
xmlchars: ^2.2.0
checksum: d3fa3e2aaf6c65ed52ee993aff1891fc47d5e47d515164b5449cbf5da2cbdc396137e55590472e64c5c436c14ae64a8a03c29b9e7389fc6f14035cf4e982ef3b
languageName: node
linkType: hard
"scheduler@npm:^0.23.0":
version: 0.23.0
resolution: "scheduler@npm:0.23.0"
@ -42912,6 +43193,13 @@ __metadata:
languageName: node
linkType: hard
"siginfo@npm:^2.0.0":
version: 2.0.0
resolution: "siginfo@npm:2.0.0"
checksum: 8aa5a98640ca09fe00d74416eca97551b3e42991614a3d1b824b115fc1401543650914f651ab1311518177e4d297e80b953f4cd4cd7ea1eabe824e8f2091de01
languageName: node
linkType: hard
"sigmund@npm:^1.0.1":
version: 1.0.1
resolution: "sigmund@npm:1.0.1"
@ -43551,6 +43839,13 @@ __metadata:
languageName: node
linkType: hard
"stackback@npm:0.0.2":
version: 0.0.2
resolution: "stackback@npm:0.0.2"
checksum: 2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99
languageName: node
linkType: hard
"standard-as-callback@npm:^2.1.0":
version: 2.1.0
resolution: "standard-as-callback@npm:2.1.0"
@ -44892,6 +45187,27 @@ __metadata:
languageName: node
linkType: hard
"tinybench@npm:^2.5.1":
version: 2.6.0
resolution: "tinybench@npm:2.6.0"
checksum: a621ac66ac17ec5da7e9ac10b3c27040e58c3cd843ccedd8e1e3fab5702d6337b80d02b7bfbf420ab5f029dcb7895657fb80ce21181896e170fa4e6d2c2eebc4
languageName: node
linkType: hard
"tinypool@npm:^0.8.2":
version: 0.8.2
resolution: "tinypool@npm:0.8.2"
checksum: b0993207b89ab8ab565e1eb03287aa3f15bc648c2e1da889bcfad003244271a5efe5c215d8074c3b8798ae7ea9c54678b6c9b09e7e5c8e82285177792e7ac30a
languageName: node
linkType: hard
"tinyspy@npm:^2.2.0":
version: 2.2.1
resolution: "tinyspy@npm:2.2.1"
checksum: 170d6232e87f9044f537b50b406a38fbfd6f79a261cd12b92879947bd340939a833a678632ce4f5c4a6feab4477e9c21cd43faac3b90b68b77dd0536c4149736
languageName: node
linkType: hard
"tippy.js@npm:^6.3.7":
version: 6.3.7
resolution: "tippy.js@npm:6.3.7"
@ -45012,6 +45328,15 @@ __metadata:
languageName: node
linkType: hard
"tr46@npm:^5.0.0":
version: 5.0.0
resolution: "tr46@npm:5.0.0"
dependencies:
punycode: ^2.3.1
checksum: 8d8b021f8e17675ebf9e672c224b6b6cfdb0d5b92141349e9665c14a2501c54a298d11264bbb0b17b447581e1e83d4fc3c038c929f3d210e3964d4be47460288
languageName: node
linkType: hard
"tr46@npm:~0.0.3":
version: 0.0.3
resolution: "tr46@npm:0.0.3"
@ -45367,7 +45692,7 @@ __metadata:
languageName: node
linkType: hard
"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5":
"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5, type-detect@npm:^4.0.8":
version: 4.0.8
resolution: "type-detect@npm:4.0.8"
checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15
@ -46889,6 +47214,21 @@ __metadata:
languageName: node
linkType: hard
"vite-node@npm:1.4.0, vite-node@npm:^1.4.0":
version: 1.4.0
resolution: "vite-node@npm:1.4.0"
dependencies:
cac: ^6.7.14
debug: ^4.3.4
pathe: ^1.1.1
picocolors: ^1.0.0
vite: ^5.0.0
bin:
vite-node: vite-node.mjs
checksum: 1abbeac935a5e1e3b6161974ae28b6a3ec88766b06bc082ab3f2883345ee74f5643f3004df1c7808c2b52d445ffbd067bb79a1a3920c2eaa7ca8084b11b7de46
languageName: node
linkType: hard
"vite-node@npm:^0.33.0":
version: 0.33.0
resolution: "vite-node@npm:0.33.0"
@ -46905,21 +47245,6 @@ __metadata:
languageName: node
linkType: hard
"vite-node@npm:^1.4.0":
version: 1.4.0
resolution: "vite-node@npm:1.4.0"
dependencies:
cac: ^6.7.14
debug: ^4.3.4
pathe: ^1.1.1
picocolors: ^1.0.0
vite: ^5.0.0
bin:
vite-node: vite-node.mjs
checksum: 1abbeac935a5e1e3b6161974ae28b6a3ec88766b06bc082ab3f2883345ee74f5643f3004df1c7808c2b52d445ffbd067bb79a1a3920c2eaa7ca8084b11b7de46
languageName: node
linkType: hard
"vite-plugin-checker@npm:^0.6.1":
version: 0.6.1
resolution: "vite-plugin-checker@npm:0.6.1"
@ -47245,6 +47570,56 @@ __metadata:
languageName: node
linkType: hard
"vitest@npm:^1.4.0":
version: 1.4.0
resolution: "vitest@npm:1.4.0"
dependencies:
"@vitest/expect": 1.4.0
"@vitest/runner": 1.4.0
"@vitest/snapshot": 1.4.0
"@vitest/spy": 1.4.0
"@vitest/utils": 1.4.0
acorn-walk: ^8.3.2
chai: ^4.3.10
debug: ^4.3.4
execa: ^8.0.1
local-pkg: ^0.5.0
magic-string: ^0.30.5
pathe: ^1.1.1
picocolors: ^1.0.0
std-env: ^3.5.0
strip-literal: ^2.0.0
tinybench: ^2.5.1
tinypool: ^0.8.2
vite: ^5.0.0
vite-node: 1.4.0
why-is-node-running: ^2.2.2
peerDependencies:
"@edge-runtime/vm": "*"
"@types/node": ^18.0.0 || >=20.0.0
"@vitest/browser": 1.4.0
"@vitest/ui": 1.4.0
happy-dom: "*"
jsdom: "*"
peerDependenciesMeta:
"@edge-runtime/vm":
optional: true
"@types/node":
optional: true
"@vitest/browser":
optional: true
"@vitest/ui":
optional: true
happy-dom:
optional: true
jsdom:
optional: true
bin:
vitest: vitest.mjs
checksum: e7141c0ecc629c350d8c718051fb19219ca88a100d335fedbe481c4320f285380e3235316a69a330514c34663fcafa37c801151162d0a538e92821e7faad71a6
languageName: node
linkType: hard
"void-elements@npm:^3.1.0":
version: 3.1.0
resolution: "void-elements@npm:3.1.0"
@ -47778,6 +48153,15 @@ __metadata:
languageName: node
linkType: hard
"w3c-xmlserializer@npm:^5.0.0":
version: 5.0.0
resolution: "w3c-xmlserializer@npm:5.0.0"
dependencies:
xml-name-validator: ^5.0.0
checksum: 593acc1fdab3f3207ec39d851e6df0f3fa41a36b5809b0ace364c7a6d92e351938c53424a7618ce8e0fbaffee8be2e8e070a5734d05ee54666a8bdf1a376cc40
languageName: node
linkType: hard
"wait-on@npm:>=7.2.0":
version: 7.2.0
resolution: "wait-on@npm:7.2.0"
@ -47946,6 +48330,13 @@ __metadata:
languageName: node
linkType: hard
"webidl-conversions@npm:^7.0.0":
version: 7.0.0
resolution: "webidl-conversions@npm:7.0.0"
checksum: f05588567a2a76428515333eff87200fae6c83c3948a7482ebb109562971e77ef6dc49749afa58abb993391227c5697b3ecca52018793e0cb4620a48f10bd21b
languageName: node
linkType: hard
"webpack-cli@npm:^4.6.0":
version: 4.9.2
resolution: "webpack-cli@npm:4.9.2"
@ -48145,6 +48536,15 @@ __metadata:
languageName: node
linkType: hard
"whatwg-encoding@npm:^3.1.1":
version: 3.1.1
resolution: "whatwg-encoding@npm:3.1.1"
dependencies:
iconv-lite: 0.6.3
checksum: f75a61422421d991e4aec775645705beaf99a16a88294d68404866f65e92441698a4f5b9fa11dd609017b132d7b286c3c1534e2de5b3e800333856325b549e3c
languageName: node
linkType: hard
"whatwg-fetch@npm:^3.4.1":
version: 3.6.2
resolution: "whatwg-fetch@npm:3.6.2"
@ -48166,6 +48566,23 @@ __metadata:
languageName: node
linkType: hard
"whatwg-mimetype@npm:^4.0.0":
version: 4.0.0
resolution: "whatwg-mimetype@npm:4.0.0"
checksum: f97edd4b4ee7e46a379f3fb0e745de29fe8b839307cc774300fd49059fcdd560d38cb8fe21eae5575b8f39b022f23477cc66e40b0355c2851ce84760339cef30
languageName: node
linkType: hard
"whatwg-url@npm:^14.0.0":
version: 14.0.0
resolution: "whatwg-url@npm:14.0.0"
dependencies:
tr46: ^5.0.0
webidl-conversions: ^7.0.0
checksum: 4b5887e50f786583bead70916413e67a381d2126899b9eb5c67ce664bba1e7ec07cdff791404581ce73c6190d83c359c9ca1d50711631217905db3877dec075c
languageName: node
linkType: hard
"whatwg-url@npm:^5.0.0":
version: 5.0.0
resolution: "whatwg-url@npm:5.0.0"
@ -48301,6 +48718,18 @@ __metadata:
languageName: node
linkType: hard
"why-is-node-running@npm:^2.2.2":
version: 2.2.2
resolution: "why-is-node-running@npm:2.2.2"
dependencies:
siginfo: ^2.0.0
stackback: 0.0.2
bin:
why-is-node-running: cli.js
checksum: 50820428f6a82dfc3cbce661570bcae9b658723217359b6037b67e495255409b4c8bc7931745f5c175df71210450464517cab32b2f7458ac9c40b4925065200a
languageName: node
linkType: hard
"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5":
version: 1.1.5
resolution: "wide-align@npm:1.1.5"
@ -48598,6 +49027,13 @@ __metadata:
languageName: node
linkType: hard
"xml-name-validator@npm:^5.0.0":
version: 5.0.0
resolution: "xml-name-validator@npm:5.0.0"
checksum: 86effcc7026f437701252fcc308b877b4bc045989049cfc79b0cc112cb365cf7b009f4041fab9fb7cd1795498722c3e9fe9651afc66dfa794c16628a639a4c45
languageName: node
linkType: hard
"xml@npm:^1.0.0, xml@npm:^1.0.1":
version: 1.0.1
resolution: "xml@npm:1.0.1"
@ -48911,6 +49347,13 @@ __metadata:
languageName: node
linkType: hard
"yocto-queue@npm:^1.0.0":
version: 1.0.0
resolution: "yocto-queue@npm:1.0.0"
checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801
languageName: node
linkType: hard
"z-schema@npm:~5.0.2":
version: 5.0.5
resolution: "z-schema@npm:5.0.5"